wtorek, 19 sierpnia 2014

Pojedynek z Androidem - odc.2 aktywności, layouty i widoki

W oczekiwaniu na Windows 9 Preview (wg. nieoficjalnych informacji pod koniec września) drugi post analizujący przeciwnika - Androida. Z kolejnego obejrzanego szkolenia wynotowałem sobie to i owo (szczegóły poniżej). Wrażenia? Aktywności dają może i większą symetrię, ale też i duże rozdrobnienie, w Windows cykl życia obejmuje wielostronicową aplikację jako całość, w Androidzie aktywność to jakby jedna strona w tej samej lub innej aplikacji.  Layouty te najpotrzebniejsze raczej są. Pisząc w XAML najczęściej używa się StackPanel i Grid w połączeniu z relatywnym podejściem (np. marginesy, padding, wyrównanie, rozciąganie). W Androidzie przy bardziej złożonych układach zagnieżdżanie liniowych layoutów w sobie może być mało wydajne, zaleca się więc jawny relatywny layout. Grida na początku nie było. Brak bindowania między kodem a XML przenosi nas w dawne czasy, ale idzie wytrzymać. Czasami daje się też zauważyć mniejszą wygodę w języku Java w stosunku do C#. Wrażenia ze studiów wciąż pozostają aktualne, aczkolwiek teraz do imitacji delegatów można używać anonimowych implementacji ze –> więc nie jest już tak źle.  Trzeba pisać zwykle więcej kodu niż w C#. Nie ma nowoczesnego async, brakuje prostoty asynchronicznego WinRT API. Sam cykl życia w porównaniu do Windows z jednej strony podobny, z drugiej czasami wołanie przez system trzech metod przy starcie aplikacji wydaje się trochę sztuczne. Okno dialogowe w Androidzie już wpływa na życie aktywności, a także …. obrócenie ekranu!  Nie wiem czym zostało to podyktowane, aby po obrocie odtwarzać od zera całą aktywność, może to wynika z jakiejś dawniejszej chęci oszczędzania lub braku wiary w automatyczne przeskalowanie layoutu po obrocie?  Plusem może być automatyczne zapamiętywanie stanu radiobuttonów (czemu akurat ich?) pomiędzy różnymi życiami aktywności.  Przekazywanie parametrów między różnymi ekranami w tej samej aplikacji bardziej skomplikowane niż w Windows (gdzie mamy po prostu nawigację z obiektem), ale za to stanowi również odpowiednik komunikacji między różnymi aplikacjami (w Windows mamy od tego np. kontrakty, rozszerzenia, taski, uruchamianie aplikacji z poziomu innej).  Mechanizm ten jest w Androidzie uniwersalny dla aktywności wbudowanych i własnych. Jednak wstawienie obrazka z kamery jest trochę bardziej skomplikowane i mniej intuicyjne niż w Windows (choć idea zamiany pliku na Uri jest wspólna). Ewidentnym plusem jest natomiast możliwość użycia kamerki internetowej do emulacji kamery w emulatorze (póki co w Windows nie ma takiej możliwości). A i na koniec - Android Studio może i da się polubić (bardziej przypomina Visual Studio niż Eclipse, całkiem sporo kreatorów, kontekstowych poleceń, podpowiedzi, analiz).

Aktywności

aktywność – intro

  • główna klasa UI, zwykle full-screen
  • status & navigation bar (zarządzane przez system)
  • anatomia
    • action bar
    • action overflow (przycisk menu)
    • obszar layout-u (obszar, w którym umieszczamy kontrolki)
    • widoki (= kontrolki)
    • zawartość pochodzi z zasobów (ładowanie w nadpisanej metodzie)
      • onCreateOptionsMenu (res\menu  main.xml)
      • onCreate - widoki  (res\layout activity_main.xml)
      • tłumaczenia (res\values strings.xml)
    • context – this (zwykle do metod np.  Toast.makeText(this, “aaa”, Toast.LENGTH_LONG))
    • menu onClick
      • menu XML:  android:onClick(metoda),   metoda(MenuItem item) w aktywności (klasie Activity, co stwarza trudności przy fragmentach), kompilator nie weryfikuje
      • nadpisanie metody onOptionsItemSelected(MenuItem item), zwracamy true by zaznaczyć, że przechwyciliśmy element menu,  item.getItemId(), R.id.action_other
    • wyjście z aktywności:  finish()

resource

  • @ - odwołanie do zasobu z XML
  • @id - zasób typu id
  • @+id – zasób zostanie utworzony, jeśli do tej pory nie istniał
  • title – dymek jako @string/nazwa (właściwość Android Studio, większa stabilność w finalnej wersji)

Android Studio

  • Code –> Override methods
  • Design/Text – przełączanie designera dla zasobu XML na tryb tekstowy
  • żarówka/Alt + Enter –> Extract string resource
  • Alt+Enter –> dodanie importu

Java

  • @override – adnotacja (= atrybut) informująca kompilator, że mamy zamiar nadpisać metodę (zostanie sprawdzone, czy taka metoda jest dostępna w hierarchii klas)

aktywności

  • aplikacje w praktyce składają się z wielu aktywności (każda z nich ma swój layout oraz menu)
  • w głównej aktywności opcje w menu otwierające inne aktywności (nie można wyświetlić ich bezpośrednio, odpowiada za to zawsze Package Manager, nawet jeśli obie aktywności znajdują się w tej samej aplikacji)
  • często wyświetlane są ekrany innych aplikacji, by wykonać jakiegoś taska
  • wszystkie aktywności muszą być wymienione w AndroidManifest.xml (nawet jak są spoza)
    • <activity android:name=”.MainActivity” …/>
    • <activity android:name=”.Activity2” …/>
    • podczas instalacji tworzona jest tablica wejść
  • wyświetlanie aktywności – metoda startActivity(pakiet, komponent)

Android Studio

  • Dodawanie nowej aktywności do stworzonego już projektu (nazwa pakietu –> menu kontekstowe –> New –> Android Component)
    • typy komponentów: Activity, Application, Service, Broadcast Receiver, Remote Interface
    • label - nazwa na pasku akcyjnym aktywności, trzymana w manifeście aktywności
    • onCreate(Bundle savedInstanceState), w wygenerowanym kodzie nie jak w głównej aktywności setContentView czy nadpisanej metody onCreateOptionsMenu (nie ma kodu ładującego layout i zasoby menu)
    • tworzymy ręcznie plik zasobów layoutu (layout –> menu kontekstowe –> New –> Layout resource file), klikając na “…” w property gridzie tworzymy resource z napisem
    • możliwość refaktoryzacji nazwy klucza w XML (menu kontekstowe –> Refactor –> Rename)
    • ręcznie dopisujemy w kodzie aktywności setContentView
    • w AndroidManifest.xml zostały dopisane nowo dodane aktywności
    • do dodatkowych aktywności tworzymy ręcznie pliki z zasobami dla menu
  • Podwójne kliknięcie zamyka zakładkę eksplorera

Intents – sposób dostępu do komponentów Androida

Intent intent = new Intent(this, Activity2.class);

startActivity(intent);

Java

  • .class  - jak typeof() w C#

Klawisz/przycisk Back - powrót do poprzedniej aktywności

finish() - zamyka bieżącą aktywność, następuje powrót do aktywności, którą ją otworzyła, przy pierwszej aktywności mamy wyjście z aplikacji (proces jednak trwa, bo jest zarządzany przez system)

Życie

  • w Androidzie nie myślimy o procesie, tylko o życiu komponentów np. aktywności
  • komponenty wykonują się w ramach procesu
  • nie zarządzamy życiem procesu, tylko życiem komponentów

 

Layouty i widoki

Widok - komponent UI np. Button, TextView, EditText

ViewGroup - złożony z wielu View np. ListView, ScrollView

Layout – specjalizowany ViewGroup

  • opisuje pozycjonowanie widoków View
  • zwykle jest korzeniem UI
  • często jest warstwą w ramach innego
  • definiowanie
    • zasoby (preferowane)
    • kod
  • pozycjonowanie
    • generalized, relative
    • unikamy absolutnego!

<LinearLayout …>

<TextView>

</LinearLayout>

lub

LinearLayout layout = new LinearLayout(…);   setContentView(layout);

Rodzaje

  • LinearLayout - horyzontalnie lub wertykalnie
    • kontener:  orientation=horizontal
    • elementy
      • layout_gravity=left (ułożenie po lewej, ułożenie samego elementu, natomiast właściwość gravity – sposób ułożenia zawartości elementu np. wyrównanie tekstu do prawej w TextView), layout_weight=1
      • layout_gravity=right, layout_weight=4
      • totalna waga layoutu: 1 + 4 = 5, proporcje w wypełnieniu szerokości kontenera:  20% : 80%
      • layout:height/width=fill_parent/match_parent(synonim fill_parent od API 8)/wrap_content(tyle przestrzeni, ile potrzeba), w przypadku ustawienia weight ustawiamy na zero
    • zagnieżdżenie większej liczby liniowych layoutów może spodowodować trudności z renderowaniem, lepiej użyć RelativeLayout
  • RelativeLayout - pozycjonuje widoki relatywnie w stosunku do innych
    • elementy
      • id=@+id/view1,  layout_alignParentTop=true,  layout_alignParentLeft=true (umieszczenie w lewym górnym rogu)
      • id=@+id/view2, layout_alignParentTop=true, layout_toRightOf=@id/view1 (umieszczenie u góry, z prawej strony elementu view1)
      • id=@+id/view3, layout_centerHorizontal=true, layout_below=@id/view1 (umieszczenie w drugim rzędzie na całej szerokości)
      • layout_marginTop=”12dp” [… –> Resources –> Dimension (values/dimens.xml)]
      • layout_margin [all, left, top, right, bottom]
  • GridLayout - dodany w API Level 14 (Android 4)
    • kontener: columnCount=8, rowCount=7
    • element
      • layout_row=2 (od zera), layout_column=5, layout_rowSpan=1, layout_columnSpan=2

Android Studio

  • przy tworzeniu nowego pliku zasobu z layoutem możliwość wyboru rodzaju roota (np. RelativeLayout)
  • element z przybornika przeciągać można na designer (mała precyzja) albo na drzewo layoutu po prawej
  • po podwójnym kliknięciu elementu TextView na designerze możemy ustawić id oraz text, możliwość utworzenia zasobu
  • nie dostarcza automatycznego bindowania między UI a kodem

//wołane w onCreate() aktywności

void setupUiEvents()  {

Button button = (Button) findViewById(R.id.topSectionButton);

button.setOnClickListener(/*implementacja interfejsu View.OnClickListener - może cała aktywność*/this);

}

void handleButton1Click(Button button) {

//…

textView.setText(“aaa”);

}

@Override

public void onClick(View view) {   //obsługuje wiele przycisków

Button button = (Button) view;

int id = button.getId();

switch(id)

handleButton1Click((Button) view);

}

Można lepiej.

Java nie ma delegatów jak w C#, ale można je zasymulować.

Większość przypadków obsługujemy za pomocą klas zagnieżdżonych (trzymają referencję – i instancyjny wskaźnik this  - do klas je zawierających i mogą wywoływać ich metody) i anonimowych (prosta składnia do implementacji interfejsu – Java przechwytuje callbacki przy pomocy interfejsu, który w wielu przypadkach zawiera jedną metodę).

button.setOnClickListener(new View.OnClickListener() {

@override

public void  onClick(View view) {

         handleButton1Click((Button)view);

}

});

Cykl życia aktywności

Zarządzanie zasobami

  • limity pamięci (bez stronicowania)
  • oszczędność baterii
  • na poziomie komponentów
    • dostęp aktywności do zasobów zależy od interakcji z użytkownikiem)
      • aktywność traci dostęp do procesora, jeśli użytkownik zacznie korzystać z innej aktywności
      • aktywność może stracić dostęp do zasobów pamięci, jeśli użytkownik zacznie korzystać z innej aktywności
      • aplikacja (z jedną lub wieloma aktywnościami) wykonuje się w ramach procesu, jeśli uruchomiony zostanie inny proces i stwierdzone zostanie, że w dotychczasowym procesie żaden komponent nie ma dostępu do zasobów, to jest on zamykany.
      • historia ekranów jak stos, możliwy powrót za pomocą klawisza Back (działający poprzednio proces może zostać ponownie uruchomiony, a ostatnio używana jego aktywność jest notyfikowana o przywrócenie zasobów)

Stany aktywności

  • Running (znany także jako Active lub Resumed)
    • aktywność jest na foregroundzie
    • pełny dostęp do zasobów
  • Paused (np. częściowe przykrycie przez okno dialogowe)
    • aktywność jest widoczna, ale nie jest w foregroundzie
    • zasoby pamięci są zachowane
    • ograniczona zdolność do wykonywania się
    • brak możliwości zniszczenia
  • Stopped (całościowe przysłonięcie przez inną aktywność)
    • aktywność nie jest widoczna
    • możliwość utraty zasobów pamięci
    • ograniczona zdolność do wykonywania się
    • duże prawdopodobieństwo zniszczenia

Callbacki dla stanów aktywności

  • onCreate –> onStart –> onResume –> Running  - uruchamianie aktywności
  • Running –> onPause (co zwalniamy?)  -> Paused –> onResume (co przywracamy ?) –> Running
  • Running  -> onPause –> Paused (szybko przy przysłonięciu inną aktywnością) –> czy jest możliwy restart
    • tak:  onSaveInstanceState –> onStop –> Stopped
    • nie:  onStop –> Stopped
  • Stopped
    • powrót:   onRestart –> onStart –> onResume –> Running
    • zniszczenie:   onDestroy

@Override

protected void onStart() {

}

@Override

protected void onResume() {

}

@Override

protected void onPause() {

}

@Override

protected void onSaveInstanceState(Bundle outState) {

}

@Override

protected void onStop() {

}

@Override

protected void onDestroy() {

}

Android Studio

odpowiednik #region w VS

//<editor-fold desc=”aaa”>

//<editor-fold>

Uruchomienie aplikacji z główną aktywnością

  • MainActivity: onCreate –> MainActivity: onStart –> MainActivity: onResume

Przełączanie aktywności

  • MainActivity: onPause –> Activity2: onCreate –> Activity2: onStart –> Activity2: onResume –> MainActivity: onSaveInstanceState –> MainActivity: onStop

Powrót do poprzedniej aktywności

  • Activity3: onPause –> Activity2: onStart –> Activity2: onResume –> Activity3: onStop –> Activity3: onDestroy

Wpływ orientacji ekranu urządzenia na stan aktywności

  • całkowite zniszczenie aktywności (konieczność zapisania jej stanu) [running –> paused –> stopped –> destroyed]
  • całkowite odtworzenie aktywności (konieczność przywrócenia stanu)

Obrót emulatora: Ctrl + F11

Jeszcze krótsze podpięcie zdarzenia

button1.setOnClickListener((view) –> { onButton1Click((Button) view);  });

Ustawienie tekstu

mTextView.setText(“abc”);

Ożywienie kontrolki

button.setEnabled(true);

Java

  • pole static final - imitacja const w C#

Zapamiętanie informacji o stanie przycisku

outState.putBoolean(button1EnabledState, button1.isEnabled());

Odtwarzanie stanu w onCreate (przy ponownym odtworzeniu aktywności)

if (savedInstanceState != null)  {

boolean isEnabled = savedInstanceState.getBoolean(button1EnabledState, false);

}

Android Studio

  • Alt + Enter - wygenerowanie metody z kontekstowego menu
  • Tab - możliwość zmian nazw, typów w świeżo wygenerowanej metodzie
  • Code –> Generate –> Getter (dla zaznaczonego pola w klasie)

Ze względu na niewiadomy czas życia należy używać getterów dla elementów ekranu:

TextView mTextView;

public TextView getTextView() {

if (mTextView == null)

      mTextView = (TextView) findViewById(R.id.textView);

return mTextView;

}

Radio-buttony automatycznie pamiętają swój stan pomiędzy różnymi życiami aktywności.

Aktywności z rezultatami (własne i wbudowane np. kamera)

Emulator

  • emulacja kamery za pomocą kamery internetowej

startActivityForResult - wyświetlenie aktywności i zapytanie o wynik

callback  onActivityResult w aktywności wywołującej

setResult - ustawienie wyniku w aktywności wołanej

finish – zamknięcie wywołanej aktywności i wywołanie callbacku z wynikiem w aktywności wywołującej

Ten sam mechanizm dla aktywności naszych i wbudowanych

ImageView – do wyświetlania obrazka

Wywołanie aktywności zwracającej wynik:

Intent intent = new Intent(this, Activity2.class);

startActivityForResult(intent, ACTIVITY2_REQUEST_CODE /*int 1000*/);

EditText - pole tekstowe

  • android:inputType (np. textPersonName, textEmailAddress)
  • android:hint

Android Studio

  • Ctrl + Shift + Enter - próba sensownego zakończenia linii np. ;, if () {}

Zwracanie wyniku z aktywności

Extras - wartości dodane do Intent, parametry

public static final String PERSON_NAME_EXTRA = “personNameExtra”;

private void Xxx(Button view) {

String personName = getEditTextValue(R.id.name_edittext);

Intent resultIntent = new Intent();

resultIntent.putExtra(PERSON_NAME_EXTRA, personName);

setResult(RESULT_OK, resultIntent);

finish();

}

Android Studio

  • Code –> Override methods

Obsługa wyniku z wywołanej aktywności

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent resultIntent)  {

switch(requestCode) {

       case  ACTIVITY2_REQUEST_CODE:

             handleActivity2Result(resultCode, resultIntent);

             break;

}

}

private void handleActivity2Result(int resultCode, Intent resultIntent)  {

if (resultCode == RESULT_OK)  {

         String personName = resultIntent.getStringExtra(Activity2.PERSON_NAME_EXTRA);

         …

}

else  {

        …  //cancel

}

}

Naciśnięcie przycisku Back na otworzonej aktywności z rezultatem powoduje zwrócenie statusu RESULT_CANCEL.

Korzystanie z wbudowanych aktywności z rezultatem

http://developer.android.com/reference/android/provider/MediaStore.html

Przekazanie zdjęcia z kamery

PhotoHelper:

public static Uri generateTimeStampPhotoFileUri() {

Uri photoFileUri = null;

File outputDir = getPhotoDirectory();

if (outputDir != null) {

       …

      File photoFile = new File(outputDir, photoFileName);

      photoFileUri = Uri.fromFile(photoFile);

}

return photoFileUri;

}

public static File getPhotoDirectory()  {

File outputDir = null;

//karta SD

String externalStorageState = Environment.getExternalStorageState();

if (externalStorageState.equals(Environment.MEDIA_MOUNTED)) {

         File pictureDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);

         outputDir = new File(pictureDir, “xxx”);

         if (!outputDir.exists())  {

                 if (!outputDir.mkdirs())  {

                          outputDir = null;

                 }

         }

}

return outputDir;

}

public static void addPhotoToMediaStoreAndDisplayThumbnail(String pathName, Activity activity, ImageView imageView)  {

        //cloujure dla callbacków

  final ImageView thumbnailImageView = imageView;

  final  Activity thumbnailActivity = activity;

  String[] filesToScan = {pathName};

  MediaScannerConnection.scanFile(thumbnailActivity, filesToScan, null,

         (filePath, uri) –> {

                  //wątek nie-UI

                   long id = ContentUris.parseId(uri);

                   ContentResolver contentResolver = thumbnailActivity.getContentResolver();

                   final Bitmap thumbnail = MediaStore.Images.Thumbnails.getThumbnail(

                   contentResolver, id, MediaStore.Images.Thumbnails.MINI_KIND, null);

                   thumbnailActivity.runOnUiThread(() –> {

                            thumbnailImageView.setImageBitmap(thumbnail);

                   });

         });

}

Aktywność wywołująca:

void handleTakePictureButton(Button button)  {

        mPhotoPathUri = PhotoHelper.generateTimeStampPhotoFileUri();

       Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

        intent.putExtra(MediaStore.EXTRA_OUTPUT, mPhotoPathUri);

        startActivityForResult(intent, TAKE_PICTURE_REQUEST_CODE /*1010*/);

}

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent resultIntent)  {

switch(requestCode) {

       case  TAKE_PICTURE_REQUEST_CODE:

             handleTakePictureResult(resultCode, resultIntent);

             break;

}

}

private void handleTakePictureResult(int resultCode, Intent resultIntent)  {

if (resultCode == RESULT_OK)  {

         String photoPathName = mPhotoPathUri.getPath();

         PhotoHelper.addPhotoToMediaStoreAndDisplayThumbnail(photoPathName, this, getThumbnailImageView());

}

else  {

        mPhotoPathUri = null;

        …  //cancel

}

}

Konfiguracja kamery w emulatorze

Android Virtual Device Manager –> Android Virtual Devices –> Edit:   Front Camera i Back Camera (None, Emulated, Webcam())

Brak komentarzy: