Skip to content

First Aider – Tworzenie projektu

Wstęp

Ponieważ dawno nie napisałem nic bardziej technicznego, postanowiłem stworzyć krótką serię wpisów przedstawiających krok po kroku tworzenie prostej aplikacji na Androida. Będzie to swojego rodzaju tutorial, tudzież poradnik. Aplikacja otrzyma skrajnie prostą strukturę, aby maksymalnie uprościć pokazywanie poszczególnych elementów bez obawy o dokładanie dodatkowych trudności. Mimo wszystko postaramy się, żeby wyglądała chociaż w jakimś podstawowym stopniu przyzwoicie.

Będzie do aplikacja o nazwie First Aider. Składać będzie się z kilku elementów:

  • Pomoże krok po kroku przeprowadzić pierwszą pomoc, opisując i ilustrując odpowiednio każdy krok
  • Będzie zawierać:
    • Listę numerów alarmowych
    • Ważne wskazówki związane z udzielaniem pierwszej pomocy
    • Listę kompetentnych źródeł, z których można więcej się nauczyć na temat pierwszej pomocy

Forma tej serii zakłada, że osoba czytająca zna podstawy Androida!

Zaczynajmy

Zacznijmy więc od stworzenia projektu w Android Studio (w momencie pisania tego postu korzystam z wersji 2.3.1). W polu Application name wpisałem First Aidera w Company domain lukaszpusz.pl. Minimalne SDK ustawiłem na 5.0 (Lollipop). W kreatorze wybrałem Navigation Drawer Activity, dzięki czemu Android Studio wygeneruje za mnie menu wysuwane z lewej strony ekranu (a także parę innych elementów), które będzie umożliwiało użytkownikowi przemieszczanie się po aplikacji. Nazwę pozostawiłem jako MainActivity.

Jak widzimy Android Studio wygenerowało za nas całkiem sporo kodu. Po uruchomieniu aplikacji zobaczymy wysuwane menu z przykładowymi pozycjami, Button umieszczony w dolnym prawym rogu ekranu, toolbar, a także menu z pozycją Settings.

Zacznijmy od stworzenia nowego fragmentu. Nazwijmy go StepsFragment. Będzie on zawierał przesuwalną listę kroków, z których składa się cały proces udzielania pierwszej pomocy. Podczas tworzenia odznaczmy opcje Include fragment factory method? oraz Include interface callbacks?. 

Wygenerowany kod fragmentu powinien wyglądać następująco:

package pl.lukaszpusz.firstaider;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class StepsFragment extends Fragment {

    public StepsFragment() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_steps, container, false);
    }

}

Zmieńmy nieco metodę onCreateView(). Dodamy do niej obsługę obiektu RecyclerView oraz związanego z nim adaptera. Oba te elementy stworzymy w następnej kolejności. Po modyfikacjach metoda powinna wyglądać następująco:

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_steps, container, false);
        
        RecyclerView recyclerView = (RecyclerView) rootView.findViewById(R.id.steps_recyclerview);
        recyclerView.setHasFixedSize(true);

        final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
        linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);

        recyclerView.setLayoutManager(linearLayoutManager);
        StepsAdapter stepAdapter = new StepsAdapter();
        recyclerView.setAdapter(stepAdapter);
        
        return rootView;
    }

W kodzie powyższej metody odnosimy się to pewnego rodzaju widoku (RecyclerView), który rzekomo ma id steps_recyclerview. Dodajmy go więc do pliku wyglądu fragmentu:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="pl.lukaszpusz.firstaider.StepsFragment">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/steps_recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </android.support.v7.widget.RecyclerView>

</FrameLayout>

Przejdźmy teraz do pliku layoutu o nazwie app_bar_main.xml. Stwórzmy w nim kontener dla naszych fragmentów, a także wyrzućmy button z dołu ekranu, gdyż nie będziemy do używać:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="pl.lukaszpusz.firstaider.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <FrameLayout
        android:id="@+id/fragments_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="60dp" />

</android.support.design.widget.CoordinatorLayout>

W dalszym ciągu jednak nie mamy tajemniczego adaptera, o którym pisałem nieco wyżej. Będzie on odpowiedzialny za przesyłanie danych do widoku.

package pl.lukaszpusz.firstaider;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class StepsAdapter extends RecyclerView.Adapter<StepsAdapter.ViewHolder> {
    private String[] titles = {
            "Title 1", "Title 2", "Title 3"
    };
    private String[] descs = {
            "Example desc 1", "Example desc 2", "Example desc 3"
    };

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.step_card, parent, false);
        return new ViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.stepTitle.setText(titles[position]);
        holder.stepDesc.setText(descs[position]);
    }

    @Override
    public int getItemCount() {
        return titles.length;
    }

    class ViewHolder extends RecyclerView.ViewHolder {
        TextView stepTitle;
        TextView stepDesc;

        public ViewHolder(View itemView) {
            super(itemView);

            stepTitle = (TextView) itemView.findViewById(R.id.step_title_textview);
            stepDesc = (TextView) itemView.findViewById(R.id.step_desc_textview);
        }
    }
}

Póki co danymi będą tylko krótkie tablice stringów. Prawdziwe dane oraz ilustracje dodamy w końcowej fazie realizacji projektu. W projekcie chcę wykorzystać CardView. Widać odpowiednie odniesienia do niego w kodzie adaptera. Stwórzmy więc plik widoku opierający się na CardView i odpowiadający za to, jak będzie przedstawiony każdy poszczególny krok udzielania pierwszej pomocy. Tworzymy plik .xml o nazwie step_card:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="5dp"
        card_view:cardCornerRadius="4dp"
        card_view:cardUseCompatPadding="true">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="9dp"
            android:orientation="vertical">

            <TextView
                android:id="@+id/step_title_textview"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="8pt" />

            <FrameLayout
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:layout_marginBottom="5dp"
                android:layout_marginTop="5dp"
                android:background="#000000" />

            <TextView
                android:id="@+id/step_desc_textview"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />

        </LinearLayout>
    </android.support.v7.widget.CardView>
</LinearLayout>

Zajmijmy się teraz kodem naszej MainActivity. Wyrzućmy z niej niepotrzebne elementy. Na chwilę obecną będzie wyglądać ona następująco:

package pl.lukaszpusz.firstaider;

import android.os.Bundle;
import android.support.design.widget.NavigationView;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;

public class MainActivity extends AppCompatActivity
        implements NavigationView.OnNavigationItemSelectedListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawer.setDrawerListener(toggle);
        toggle.syncState();

        NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
        navigationView.setNavigationItemSelectedListener(this);
        
        StepsFragment stepsFragment = new StepsFragment();
        FragmentManager fragmentManager = getSupportFragmentManager();
        fragmentManager.beginTransaction().replace(R.id.fragments_container, stepsFragment)
                .commit();
    }

    @Override
    public void onBackPressed() {
        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        if (drawer.isDrawerOpen(GravityCompat.START)) {
            drawer.closeDrawer(GravityCompat.START);
        } else {
            super.onBackPressed();
        }
    }

    @SuppressWarnings("StatementWithEmptyBody")
    @Override
    public boolean onNavigationItemSelected(MenuItem item) {
        // Handle navigation view item clicks here.
        int id = item.getItemId();

        if (id == R.id.nav_camera) {
            // Handle the camera action
        } 
        
        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        drawer.closeDrawer(GravityCompat.START);
        return true;
    }
}

Aby całość mogła się skompilować, musimy dodać jeszcze odpowiednią zależność do pliku konfiguracyjnego gradle’a. Sekcja dependencies w moim przypadku wygląda następująco:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.android.support:support-v4:25.3.1'
    compile 'com.android.support:design:25.3.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'com.android.support:cardview-v7:25.3.1'
    testCompile 'junit:junit:4.12'
}

Po uruchomieniu naszym oczom ukaże się następujący efekt:

Nie jest on raczej zbyt imponujący, ale wbrew pozorom chcąc nadać mu ładniejszy wygląd, nie zostało dużo pracy. Konfiguracją dalszych elementów zajmiemy się w następnych wpisach. Później popracujemy nad dopieszczeniem warstwy widoku, a na samym końcu wprowadzimy realne dane do aplikacji oraz opublikujemy ją w Google Play. Całość będzie oczywiście również dostępna na moim GitHubie.

Facebooktwitterredditlinkedinmail
Published inMobile