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.

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())

wtorek, 5 sierpnia 2014

Windows Phone 8.1 Update 1 na żywo - foldery, live lock screen oraz tajna niespodzianka - accessory apps (np. dla zegarków) !

Od dzisiaj mogę się cieszyć Windows Phone 8.1 Update 1 w wersji Preview. Aktualizacja z WP 8.1 Preview (a właściwie dwie aktualizacje - patrz tutaj) przebiegła sprawnie i bez żadnych problemów.

Kafelkowe foldery przykładowo prezentują się tak:

Domyślnie nie ma utworzonych żadnych folderów. Przeciągając jeden kafelek na inny aktywuje się tworzenie folderu. Również poprzez przeciąganie dodajemy kafelki do folderu lub wyciągamy z niego. Folder zamknięty jest reprezentowany w postaci kafelka złożonego z mniejszych kafelków, które co jakiś czas się zmieniają, dodatkowo również same mogą się zmieniać (żywy kafelek będący konglomeracją żywych kafelków). Naciskając folder otwieramy go, naciskając poza jego obszarem zamykamy.

Z Windows Phone Store dodatkowo pobrałem sobie aplikację Microsoftu do zmiany wygaszacza ekranu na bardziej niebanalny:

Wybrany przeze mnie ekran z deszczowym tłem i ukośnymi napisami wygląda elegancko, przy czym akurat przy takim zestawieniu czasami tło wskakuje później od napisów (ale to jest beta).

A, i uwaga - tajna, niewymieniana nigdzie wcześniej bomba – accessory apps, czyli aplikacje przesyłające pewne informacje z telefonu będącego w kieszeni do naszej, elektronicznej garderoby (np. zegarka, paska fitness). Telefon pokazuje link do oficjalnej strony Microsoftu Accessory apps and notifications, która ogólnie wyjaśnia tę ideę. Nie mamy dokładnie sprecyzowanych informacji, kiedy taka elektroniczna garderoba się pojawi, choć chodzą słuchy że być może na jesieni zobaczymy zegarek. Portal neowin wyczaił wpis na Twitterze pokazujący zdjęcie z nieznanym jeszcze na obecny moment API.

Przy okazji pojawienia się nowej wersji systemu dostajemy także zaktualizowane Windows Phone 8.1 SDK z dodatkowymi obrazami emulatorów dla WP 8.1 U1.  Po instalacji widzimy nowe pozycje z nazwą “U1”.

image

Przy okazji dodam, że sam Visual Studio 2013 także doczekał się finalnej wersji Update 3, a także mamy m.in nowe CTP 2 dla aplikacji hybrydowych w Apache Cordova.

Pozostałe linki