sobota, 30 sierpnia 2014

Pojedynek z Androidem - odc.3 fragmenty, adaptacja, ActionBar, panel nawigacyjny, intencje jawne i niejawne

Po pewnej przerwie kolejny odcinek, w którym przypatruję się Androidowi.  Nie piszę z pozycji hiper fana, aczkolwiek jeśli zauważę jakieś plusy z mojego punktu widzenia, to o nich wspomnę.  Dziś pewne kawałki wiedzy na pięć tematów:

  • Fragmenty - co to takiego? Jak dla mnie odpowiednik “user control”, ale nie tylko, bo można dziedziczyć także bo bardziej specjalistycznych osiągając różne rodzaje nawigacji. Jak dla mnie podpinanie danych na liście trochę archaiczne w postaci jawnego w kodzie adaptera,  jeśli porównać z deklaratywnym data bindingiem XAML czy Angular.  Podpinanie zdarzeń z fragmentu do aktywności też IMHO porażka, ale w Javie nie ma zdarzeń. Trzeba więc jawnie implementować interfejsy fragmentu w aktywności i wywoływać obiekt hostującej aktywności z poziomu fragmentu. Przy scenariuszu master-details trzeba wszystko wiązać jawnie w kodzie, raczej nie ma czegoś takiego fajnego jak element databinding. Przekazywanie dynamicznie parametrów do fragmentu infrastrukturalnie jest w postaci słownika.  Dużym plusem jest przenośność fragmentów na różne rodzaje urządzeń, smartfony i tablety (podobnie jak całego Androida). Aczkolwiek w WP 8.1 i Windows 8.1 również możemy bez żadnych problemów tworzyć przenośne kontrolki w XAML (jest już binarna zgodność), nie wspominając już o pełnoprawnym wsparciu dla HTML5 i JavaScript.
  • Dynamiczna adaptacja dla różnych urządzeń - sporym plusem pojęcie niezależnego piksela, zasoby są automatyczne ładowane w zależności od cech urządzenia np. szerokość ekranu w niezależnych pikselach, a także np. orientacji ekranu. Automatyczne ładowanie zasobów realizowane także przez konwencję ma Windows / Windows Phone np. na gęstość pikseli (nie ma warunków na rozmiary ani orientację, w aplikacjach XAML jest API, które ułatwia zrobić behavior, w HTML5 można używać media queries, aczkolwiek ze względów wydajnościowych czasami lepiej rozdzielić pewne ekrany między Windows a Windows Phone, jak zrobili twórcy WinJS 2.1 na WP).
  • ActionBar - jak dla mnie pewna wypadkowa pasków aplikacyjnych Windows i Windows Phone. Wyświetlanie ikon najczęściej używanych akcji, a rzadziej już w menu na końcu bez ikon podobnie jak w WP, tylko że w górnym pasku i niekoniecznie muszą to być maksymalnie cztery ikony.  Idea podziału paska z komendami na górny i dolny kojarzy mi się trochę z kolei z dwoma paskami w Windows. Przy czym górny pasek w Android to ikona i tytuł aplikacji, a dolny – ikony akcji i menu. Windows górny pasek ma służyć do nawigacji, nie ma marnotrawienia miejsca na tytuł i ikonę aplikacji.  Android ma specjalny panel boczny do nawigacji, o czym w następnym punkcie. Dwa paski pojawiają się w Android na wąskich ekranach, w rodzinie Windows są tylko w Windows, który jest obecnie od tabletów wzwyż (większe ekrany to więcej pasków). W universal apps ten sam kod spowoduje że pozycje w drugiej sekcji na Windows Phone zostaną umieszczone w menu bez ikon, Android w zależności od szerokości ekranu może podzielić pasek na dwa i w zależności od miejsca niemieszczące się pozycje przesuwa do menu.  Wiązanie obsługi/budowania ActionBar z fragmentami, obok samych już aktywności wydaje mi się zaciemnieniem obrazu, ale może przez brak eventów nie dało się zrobić lepiej.
  • Navigation Drawer - co to jest? “Nawigacyjna szuflada” w prostym tłumaczeniu całkiem trafnie oddaje istotę rzeczy. Z mojej perspektywy nazwałbym to raczej wjeżdżanym panelem. Lewy jest polecany do nawigacji i do ogólnych funkcji aplikacji, prawy do funkcjonalności danego ekranu. W wielu nowoczesnych aplikacjach Web lewe, rozwijane menu jest obecnie popularne. Taki motyw odnajdziemy także w niektórych aplikacjach Windows Store np. Mail.  Prawy wyjeżdżany panel to w Windows 8.x dość popularny motyw - “Charms Bar”, panel właściwości, do współdzielenia danych między aplikacjami, drukowania itp.  O ile jednak API w Windows jest proste, o tyle w Android poprawne oprogramowanie menu bocznego z prostą listą to już kilka klas Javy, trzeba pamiętać o różnych synchronizacjach, przeładowaniach.
  • Korzystanie z aktywności pomiędzy różnymi aplikacjami  - odpowiednik znanych z Windows/Windows Phone: nawigacji pomiędzy stronami w ramach jednej aplikacji,  tasków dla systemowych aplikacji, klasy launcher-a, kontraktów, deklaracji,  pewnych GUID-ów w manifestach.  Android ma jednolite zasady na tworzenie komunikacji między stronami tej samej aplikacji, a także pomiędzy dwiema aplikacjami, zarówno firm trzecich jak i systemowych.  Może to być pewnym plusem, choć korzystanie z API Windows/Windows Phone nie uważam za trudne.  W Android jest ciekawe podejście, że aplikacja to zbiór aktywności, i jak wywołamy jakąś z nich z poziomu innej app-ki, to nie wstaje cała aplikacja. W Windows/Windows Phone aplikacja to jednak całość, która może być aktywowana na bardzo wiele różnych sposobów, także przez inne aplikacje.

A poniżej bardziej szczegółowe notatki na omówione na wspomniane dziś tematy.

Fragmenty

Dzielą UI na sekcje

Fragment

  • grupa elementów UI (mogą być takie same jak w aktywności) + ich zachowanie
  • tworzy logiczną jednostkę UI
  • pozwala na reorganizację UI na różnych urządzeniach
    • można używać statycznych plików layoutu
    • może być dynamicznie włączany do kodu
  • koncepcyjnie podobny do user control
  • kluczowa większość zachowań w nawigacji
    • nawigacja oparta na stronach
    • nawigacja pomiędzy zakładkami
    • nawigacja oparta o listę (np. rozwijana lista z górnego menu w aplikacji pocztowej)

Wsparcie

Tworzenie

  • Klasa dziedzicząca po klasie Fragment
  • Wyświetlana zawartość
    • nadpisanie metody onCreateView i/lub onCreate
    • często opis layoutu w pliku XML (podobnie jak w aktywności)
    • specjalizowane klasy dziedziczące po Fragment ułatwiają pewne przypadki
  • Dołączenie fragmentu do aktywności
    • Element <fragment> w XML z layoutem aktywności
      • Atrybut class identyfikuje pełną kwalifikowaną nazwę klasy fragmentu do utworzenia
    • Możemy dołączyć do widoku dowolną ilość fragmentów
  • Kompilator od API 11
  • Alternatywnie:  FragmentActivity + API 4 – 10

public class XFragment extends Fragment  {

string[] mStrings;

TextView mXTextView;

@Override

public void onCreate(Bundle savedInstanceState)  {

       super.onCreate(savedInstanceState);

       mStrings = getResources().getStringArray(R.array.x_strings);

}

@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)  {

        View theView = inflater.inflate(R.layout.fragment_x, container, false);  //ostatni parametr: attachToRoot

        mXTextView = (TextView) theView.findViewById(R.id.x);

        return theView;

}

public void setItem(int index)  {

        mXTextView.setText(mStrings[index]);

}

}

arrays.xml

<?xml version=”1.0” encoding=”utf-8”?>

<resources>

<string-array name=”x_strings”>

         <item>@string/android_fragments_title</item>

         <item>@string/android_intents_title</item>

</string-array>

</resources>

public class YFragment extends ListFragment  {

       @Override

       public void onActivityCreated(Bundle savedInstanceState)  {

              super.onActivityCreated(savedInstanceState);

              String[] strings = getResources().getStringArray(R.array.y_strings);

              ArrayAdapter<String> yAdapter = new ArrayAdapter<String>(getActivity(),

              android.R.layout.list_item_1, strings);

             setListAdapter(yAdapter);

       }

}

Android Studio

  • Designer –> Palette –> Custom –> <fragment> –> okno wyboru Fragments
  • utworzony fragment wstawiamy do rootLayout

<LinearLayout …>

        <fragment

                   android:layout_width=”wrap_content”

                   android: layout_height=”0dp”

                  android:name=”com.abc.fgh.XFragment”

                  android:id=”@+id/xFragment”

                 android:layout_weight=”1”

</LinearLayout>

Struktura aktywności zawierającej fragmenty

  • FragmentManager
  • Wewnątrz fragmentu layout z widokami
  • Fragment spodziewa się, że aktywność zaimplementuje pewne interfejsy

public interface OnXSelectionChangeListener  {

public void onXSelectionChanged(int index);

}

Uzupełniamy

public class YFragment extends ListFragment  {

        @Override

        public void onListItemClick(ListView l, View v, int position, long id)  {

                OnXSelectionChangeListener listener = (OnXSelectionChangeListener) getActivity();

                listener.onCourseSelectionChanged(position);

        }

        …

}

public class MainActivity extends Activity

implements OnXSelectionChangeListener   {

         …

         @Override

          public void onXSelectionChanged(int index)  {

                   FragmentManager fm = getFragmentManager();

                   XFragment xFragment = (XFragment) fm.findFragmentById(R.id.xFragment);

                  xFragment.setItem(index);

          }         

}

Można tak zrobić listview-details (po wybraniu pozycji z listy zmieniamy dane we fragmencie szczegółów)

Fragmenty z bardziej typowego punktu widzenia dla Androida

Android Studio

  • New Project:  Navigation Type (None, Fixed Tabs + Swipe, Scrollable Tabs + Swipe, Dropdown)

Wybieramy  Scrollable Tabs + Swipe

activity_main.xml

<android.support.v4.view.ViewPager …>

       <android.support.v4.view.PagerTitleStrip … />

</android.support.v4.view.ViewPager>

public class MainActivity extends FragmentActivity  {

          SectionsPagerAdapter mSectionsPagerAdapter;

          ViewPager mViewPager;

          @Override

          protected void onCreate(Bundle savedInstanceState)  {

                   super.onCreate(savedInstanceState);

                    setContentView(R.layout.activity_main);

                    mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());

                   //adapter zwracający fragmenty dla każdej sekcji

                    mViewPager = (ViewPager) findViewById(R.id.pager);

                    mViewPager.setAdapter(mSectionsPagerAdapter);

          }

@Override

  public boolean onCreateOptionsMenu(Menu menu)  {

          …

}

       public class SectionsPagerAdapter extends FragmentPagerAdapter  {

                 public SectionsPagerAdapter(FragmentManager fm)  {

                       super(fm);

                 }

                 @Override

                 public Fragment getItem(int position)  {

                           Fragment fragment = new DummySectionFragment();

                           Bundle args = new Bundle();

                           args.putInt(DummySectionFragment.ARG_SECTION_NUMBER, position + 1);

                           fragment.setArguments(args);

                           return fragment;

                 }

         @Override

          public int getCount()  {

                    return 3;

         }

         @Override

         public CharSequence getPageTitle(int position)  {

                    Locale l  = Locale.getDefault();

                    switch (position)  {

                             case 0:

                                      return “Sekcja 1”.toUpperCase(l);

                             case  1:

                                      return “Sekcja 2”.toUpperCase(l);

                              case 2:

                                       return “Sekcja 3”. toUpperCase(l);

                    }

                    return null;

         }

       }

       public static class DummySectionFragment extends Fragment {

              …

       }

}

Usuwamy z layout  fragment_main_dummy.xml  i inne niż podstawowe values

Tworzymy zasób dla layoutu fragmentu

public class CourseFragment extends Fragment  {

         …

         @Override

         public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)  {

                  View theView = inflater.inflate(R.layout.fragment_course_info, container, false);

                   Bundle arguments = getArguments();

                   if (arguments != null)  {

                             String courseTitle = arguments.get(COURSE_TITLE);

                              …

                   }

                   return theView;

         }

}

FragmentPagerAdapter - zakłada statyczną zawartość fragmentu

FragmentStatePagerAdapter - zakłada dynamiczną zawartość fragmentu

public class CoursePagerAdapter extends FragmentPagerAdapter  {

         String[] titles;

         public CoursePagerAdapter(FragmentManager fm, Context context)  {

                 super(fm);

                 Resources  resources = context.getResources();

                 titles = resources.getStringArray(R.array.course_titles);

         }

  @Override

         public Fragment getItem(int i)  {

                  Bundle arguments = new Bundle();

                  arguments.putString(CourseFragment.COURSE_TITLE, titles[i]);

                  CourseFragment courseFragment  = new CourseFragment();

                  courseFragment.setArguments(arguments);

                   return courseFragment;

         }

         @Override

         public  CharSequence getPageTitle(int position)  {

                  return  titlesShort[position];

         }

         @Override

          public int getCount()  {

                   return  titles.length;

          }

}

Android Studio

  • menu podręczne:  Creating constructor matching super

public class MainActivity extends FragmentActivity  {

         CoursePagerAdapter mCoursePagerAdapter;

          …

          @Override

          protected void onCreate(Bundle savedInstanceState)   {

                    …

                   mCoursePagerAdapter = new CoursePagerAdapter(getSupportFragmentManager(),  this);

                   mViewPager = (ViewPager) findViewById(R.id.pager);

                   mViewPager.setAdapter(mCoursePagerAdapter);

          }

}

 

Dynamiczna adaptacja dla różnych urządzeń

Wsparcie adaptacji przez Android

  • zarządzanie layoutem
  • fragmenty
  • niezależność od gęstości
  • dynamiczny wybór zasobów

Niezależność od gęstości

  • Niezależne od gęstości piksele
    • Znormalizowana jednostka zgodna z  wyświetlaczem 160dpi
    • Przechwycenie przez Android translacji pomiędzy niezależnym pikselem od urządzenia a fizycznym pikselem
    • dp

Tablet 1:  7’’,  720px x 1280px,  600dp  x 1000dp

Tablet 2:  7’’,  1080px x 1920px,  600dp x 1000dp

Dynamiczny wybór zasobu

Android Studio

  • New –> Android resource directory (nazwa folderu, rodzaj layoutu, kwalifikatory np. orientacja ekranu:  layout-land)

android:layout_height=”match_parent”

android:layout_marginLeft=”@dimen/activity_horizontal_margin”

style=”@android:style/TextAppearance.Holo.Medium” 

res/values/dimens.xml

<resources>

           <dimen name=”activity_horizontal_margin”>16dp</dimen>

</resources>

Dwa sposoby na powiązanie zasobów z rozmiarami ekranu

  • Grupy rozmiarów ekranu (dostępne na wszystkich urządzeniach, problematyczne, nie są ściśle określone, zbyt szeroko)
    • small
    • normal
    • large
    • xlarge
  • Kwalifikatory rozmiarów ekranu (dostępne od API Level 13 / Android 3.2, ścisła kontrola, w oparciu o dp specyficzne rozmiary)

Android Studio

  • New –> Android resource directory –> kwalifikatory:
    • Smallest Screen Width (najmniejszy rozmiar urządzenia niezależnie od orientacji)
    • Screen Width (szerokość urządzenia od lewej do prawej w bieżącej orientacji) np. layout-w600dp
    • Size: Small, Normal, Large, X-Large np. layout-large

Problem – duplikacja plików w folderach layout-*

Aliasy zasobów (jeden folder layout z kilkoma wersjami zasobów, więcej folderów values-* z plikami ref.xml)

values-land/refs.xml

<resources>

        <item name=”activity_main” type=”layout”>@layout/activity_main_wide</item>

</resources>

 

Menu –> ActionBar

ActionBar

  • od API Level 11
  • używa tych samych zasobów i metod co menu
  • centrum interakcji
  • najczęściej używane akcje
  • rzadziej używane akcje przenosić do menu (unikać zagnieżdżonego)

Koncepcyjnie kojarzy się z paskiem aplikacji z Windows Phone, gdzie rzadziej używane opcje też trafiają do menu

Zachowania

  • podstawowe jako menu (onCreateOptionsMenu,  onOptionsItemSelected)
  • atrybut pozycji showAsAction
    • always (zawsze na pasku akcji)
    • ifRoom (zawsze, jeśli jest miejsce, inaczej trafia do “action overfow”)
    • withText (umieszcza tekst z ikoną, jeśli jest na to miejsce)
    • wiele wartości oddzielamy |

<menu xmlns:android=”…”>

       <item

              android:id=”@+id/action_edit”

              android:title=”@string/action_edit”

              android:icon=”@drawable/ic_action_edit”   //używana tylko na pasku

              android:showAsAction=”always|withText” />       

</menu>

Fragment może przechwytywać i dodawać pozycje do ActionBar

  • domyślnie tylko Activity jest podłączona do pozycji ActionBar
  • aby Fragment miał dostęp do akcji wołamy metodę setHasOptionsMenu
  • Fragment ma dostęp do pozycji ActionBar
    • metoda Fragmentu onOptionsItemSelected jest wołana przed jej odpowiednikiem w Activity
    • jeśli Fragment zwróci false, to wołana jest metoda z Activity
  • Fragment może dodawać pozycje do ActionBar
    • pozycje Fragmentu pojawiają się po pozycjach Activity
    • ActionBar automatycznie aktualizuje się przy dodawaniu i usuwaniu Fragmentów

public class XFragment extends Fragment  {

      …

@Override

public void onCreate(Bundle savedInstanceState)  {

        super.onCreate(savedInstanceState);

        setHasOptionsMenu(true);

}

 

@Override

public boolean onOptionsItemSelected(MenuItem item)  {

         boolean handled = true;

         switch (item.getItemId()) {

                   case R.id.action_view:

                           …

                           break;

        }        

         return handled;

}

 

@Override

public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)  {

         if(flag)

                 inflater.inflate(R.menu.course_refs, menu);

}      

}

To samo menu na smartfonie pokaże mniej pozycji na pasku akcji, a na tablecie więcej

Specjalne zachowania

  • Ikona ActionBar jako przycisk “Up” lub “Home”
  • Podział ActionBar na 2 części
    • na wąskich ekranach, ikony i menu przesunięte do paska na dole ekranu (u góry pasek z ikoną i tytułem)
    • na szerokich ekranach pozostaje pojedynczy pasek

Przycisk Home:

Aktywność

onCreate

ActionBar actionBar = getActionBar();

actionBar.setTitle(title);

actionBar.setDisplayHomeAsUpEnabled(true);

Manifest  (od Android 4.1)

<activity 

       android:name=”xxx”

       android:label=”@string/title_avtivity_xxx”

       android:parentActivityName=”yyy” >

       <meta-data

               android:name=”android.support.PARENT_ACTIVITY”

               android:value=”yyy”/>

  </activity>

Podział paska na dwa:

<activity …

          android:uiOptions=”splitActionBarWhenNarrow”  … >

Jeśli chcemy na każdej aktywności w aplikacji atrybut definiujemy na poziomie aplikacji.

ActionBar ładowany jest z właściwościami

  • wyświetlanie w formie zakładek
  • nawigacja z listą dropdown (na tytule)
  • właściwości do autoukrywania

http://developer.android.com/design/patterns/actionbar.html 

Główny element, na który należy zwrócić uwagę przy pisaniu aplikacji

 

Nawigacja

Navigation drawer

DrawerLayout

  • korzeń layoutu aktywności
  • dwoje dzieci
  • pierwsze dziecko: główne View
  • drugie dziecko: wyciągany panel
    • layout_gravity - pozycja po wyciągnięciu
    • layout_gravity - left lub start dla nawigacji dla całej aplikacji
  • zachowanie “swipe to open”
  • przy stosowaniu nawigacji po całej aplikacji wszystkie layouty Activity potrzebują DrawerLayout
  • szerokość powinna zawierać się w granicach 240dp – 320dp

<android.support.v4.widget.DrawerLayout

         xmlns:android=”…”

         android.id=”@+id/drawer_layout”

         android:layout_width=”match_parent”

         android:layout_height=”match_parent”>        

         <!— Main –>

         <RelativeLayout …

                 android:layout_width=”match_parent”

                 android:layout_height=”match_parent”>

         </RelativeLayout>

         <!—Drawer –>

         <RelativeLayout …

                  android:layout_width=”240dp”

                  android:layout_gravity=”start”

                  android:background=”#000000”>

                  <TextView  …

                            android:textColor=”#FFFFFF”

                            android:text=”ABC”

                  />

         </RelativeLayout>

</android.support.v4.widget.DrawerLayout>

Android Studio

  • Ctrl + Alt + L:  Reformat Code

Powiązanie z ActionBar

  • Klasa ActionBarDrawerToggle wiąże panel nawigacyjny z paskiem akcji
    • Włączenie ikony na ActionBar do otwierania i zamykania panelu
      • Musimy wywołać ActionBar.setDisplayHomeAsUpEnabled
      • Musimy wywołać ActionBarDrawerToggle.syncState z onPostCreate aktywności
    • Przy otwartym panelu powinniśmy zdisablować każdą pozycję związaną z kontekstem ekranu
      • Nadpisujemy metodę onPrepareOptionsMenu w aktywności do ukrywania/deaktywacji opcji
      • na aktywności wywołujemy invalidateOptionsMenu do zaktualizowania menu
    • Activity potrzebuje informować ActionBarDrawerToggle o znaczących zdarzeniach
      • Potrzebujemy informować o wyborze użytkownika w onOptionsItemSelected
      • Potrzebujemy informować o zmianach layoutu w onConfigurationChanged (zmiana charakterystyki urządzenia)

http://bit.ly/NavDrawerIcons

FragmentStatePagerAdapter pozwala na zamianę zawartych fragmentów. Metoda notifyDataSetChanged informuje, że fragmenty muszą być przeładowane.

public class XAdapter extends FragmentStatePagerAdapter  {

         …

         private void XXX() {

                  …

                  notifyDataSetChanged();

        }

 

}

<android.support.v4.widget.DrawerLayout …>

        <android.support.v4.view.ViewPager …>

        </android.support.v4.view.ViewPager>

        <ListView …

              android:choiceMode=”singleChoice”

              android:divider=”@android:color/transparent”

              android:dividerHeight=”0dp”

              android:background=”#111”/>            

</android.support.v4.widget.DrawerLayout>

public class NavigationDrawerHelper  {

         DrawerLayout mDrawerLayout;

         ListView mDrawerListView;

         ActionBarDrawerToggle mDrawerToggle;

 

         public void init(Activity theActivity, ListView.OnItemClickListener listener)  {

                 mDrawerLayout = (DrawerLayout)  theActivity.findViewById(R.id.drawer_layout);

                 mDrawerListView  = (ListView) theActivity.findViewById(R.id.left_drawer);

               

                 String[] options = theActivity.getResources().getStringArray(R.array.navigation_drawer_options);

                 ArrayAdapter<String> navigationDrawerAdapter =

                        new ArrayAdapter<String>(theActivity, R.layout.drawer_option_item, options);

                 mDrawerListView.setAdapter(navigationDrawerAdapter);

                 mDrawerListView.setOnItemClickListener(listener);

                 mDrawerLayout.setDrawerShadow(R.drawable.draver_shadow, GravityCompat.START);

                 mDrawerListView.setItemChecked(0, true);

                 setupActionBar(theActivity);

           }

           private void setupActionBar(Activity theActivity)  {

                /*closure*/

                final Activity activity = theActivity;

                 ActionBar actionBar = theActivity.getActionBar();

                 actionBar.setDisplayHomeAsUpEnabled(true);

 

                 mDrawerToggle = new ActionBarDrawerToggle {

                        theActivity,

                        mDrawerLayout,

                        R.drawable.ic_drawer,

                        R.string.open_drawer_message,

                        R.string.close_drawer_message

                 }  {

                           @Override

                           public void onDrawerClosed(View drawerView) {

                                  activity.invalidateOptionsMenu();

                                  super.onDrawerClosed(drawerView);

                           }

                           @Override

                            public void onDrawerOpened(View drawerView) {

                                   activity.invalidateOptionsMenu();

                                   super.onDrawerOpened(drawerView);

                            }

                     }

                 };

           }         

 

           public void handleSelect(int option)  {

                  mDrawerListView.setItemChecked(option, true);

                  mDrawerLayout.closeDrawer(mDrawerListView);

           }

           public void handleOnPrepareOptionsMenu(Menu menu)  {

                   boolean itemVisible = ! mDrawerLayout.isDrawerOpen(mDrawerListView);

                    for (int index = 0;  index < menu.size();  index++)  {

                            MenuItem item = menu.getItem(index);

                            item.setEnabled(itemVisible);

                     }               

            }

            public void handleOptionsItemSelected(MenuItem item)  {

                    mDrawerToggle.onOptionsItemSelected(item);

            }

            public void syncState()  {

                    mDrawerToggle.syncState();

            }

           public void setSelection(int option)  {

                    mDrawerListView.setItemChecked(option, true);

           }

}

drawer_option_item.xml

<TextView

       …

       android:layout_width=”match_parent”

       android:layout_height=”wrap_content”

       android:textAppearance=”?android:attr/textAppearanceLarge”

       android:text=”XYZ”

       android:textColor=”#ffffff”

       android:gravity=”center | center_vertical”

       android:background=”?android:attr/activatedBackgroundIndicator”>

</TextView>

Navigation drawer najczęściej implementowany jako ListView

  • ListView odgrywa rolę menu
  • implementacja ListView.OnItemClickListener
  • przy otwieraniu ListView powinno wskazywać aktualny wybór

MainActivity extends Activity {

         …

         @Override

         public void onItemClick(AdapterView<?> adapterView, View view,  int optionLib, long l)  {

                 mCoursePagerAdapter.setCourseLib(optionLib);

                 mNavigationDrawerHelper.handleSelect(optionLib);

         }

 

         @Override

         protected void onPostCreate(Bundle savedInstanceState)  {

                super.onPostCreate(savedInstanceState);

                mNavigationDrawerHelper.syncState();

         }

 

         @Override

         public boolean onPrepareOptionsMenu (Menu menu)  {

                 mNavigationDrawerHelper.handleOnPrepareOptionsMenu(menu);

                 return super.onPrepareOptionsMenu(menu);

         }

         

         @Override

         public boolean onOptionsItemSelected(MenuItem item)  {

                 mNavigationDrawerHelper.handleOnOptionsItemSelected(item);

                 return super.onOptionsItemSelected(item);

         }

 

          @Override

          public void onConfigurationChanged(Configuration newConfig)  {

                  mNavigationDrawerHelper.syncState();

                  super.onConfigurationChanged(newConfig);

          }

}

 

Używanie aktywności pomiędzy aplikacjami

Intent-y do wywoływania innych aktywności

  • jawne używają nazwy komponentu do identyfikacji celu
  • niejawne używają informacji do identyfikacji celu (wołający nie potrzebuje szczegółowej wiedzy o aktywności, która przechwyci żądanie)
  • docelową aktywnością może być każda aplikacja
  • aplikacja zawierająca docelową aktywność nie potrzebuje być uruchamiana

Aktywności używają filtrów Intent-ów do ogłaszania swoich funkcjonalności (capabilities) (zawierają reguły dla żądań, które aktywność może przechwycić)

Wywoływanie przez niejawne cele

<manifest …>

       <application …>

                <activity

                         android:name=”.XXX”  …>

                         <intent-filter>

                                   <action

                                           android:name=”com.ps.action.XX” />

                                   <category

                                           android:name=”android.intent.category.DEFAULT”/>

                         <intent-filter>

                </activity>

                <activity

                        android:name=”.YYY” … />

      </application>

</manifest>

Filtry

  • android.intent.action.CALL
  • android.media.action.IMAGE_CAPTURE
  • android.intent.action.VIEW    inne info
  • android.ps.action.XX

new Intent(“com.ps.action.XX”)  

Szukanie, która aktywność tak się zarejestrowała

Intencje i filtry intencji składają się maksymalnie z 3 części:

  • akcja
    • intencja: operacja do wykonania
    • filtr intencji:  operacja, która może być wykonana
  • dane
    • intencja: dane, na których zostanie wykonana operacja
    • filtr intencji:  typ lub format danych, na których operacja może być wykonana
  • kategoria
    • podział komponentów na podgrupy
    • z tendencją do używania częściej z filtrami intencji niż intencjami
    • Intent.CATEGORY_DEFAULT wymagana przez aktywności otrzymujące niejawne intencje
    • Intent.CATEGORY_LAUNCHER wymagana przez aktywności do pojawienia się w launcherze aplikacji

Wywołanie rozmowy telefonicznej

Intent intent = new Intent(Intent.ACTION_DIAL);

intent.setData(Uri.parse(“tel:123.456.7890”));

startActivity(intent);

Wyświetlenie strony Web

Intent intent = new Intent(Intent.ACTION_VIEW);

intent.setData(Uri.parse(“http://xxx.yyy”));

startActivity(intent);

Wywołanie niestandardowej akcji

<application …>

       <activity …>

                 <intent-filter>

                             <action android:name=”android.intent.action.MAIN” />

                             <category android:name=”android.intent.category.LAUNCHER” />

                  </intent-filter>

                  <intent-filters>

                             <action android:name=”com.xxx.action.BROWSE_COURSES”/>

                  </intent-filters>

         </activity>

         …

</application>

<uses-permission android:name=”android.permission.INTERNET” />

Intent intent = new Intent(“com.xxx.action.BROWSE_COURSES”);

intent.putExtra(“xxx”, y);

startActivity(intent);

Wszystkie aktywności, które wspierają startActivity muszą zawierać CATEGORY_DEFAULT.

Brak komentarzy: