2013年5月31日金曜日

Android : Navigation Drawer を使う

画面の左側にオーバーレイでアプリの主なオプションを表示するパネル。
通常は隠れていて、画面の左端からスワイプするか、トップレベルにいるならアクションバーのアイコンをタップすることで表示される。


http://developer.android.com/design/patterns/navigation-drawer.html より

Navigation Drawer を使う前に、Navigation Drawer デザインガイドにあるこのパターンのユースケースとデザイン原則をきちんと理解すること。


Drawer Layout を作成する

support package にある DrawerLayout を利用する。

DrawerLayout をルートビューとし、その中にメインのコンンテンツを表示するビューと、NavigationDrawer として利用するビューを入れる。
  1. <android.support.v4.widget.DrawerLayout  
  2.     xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:id="@+id/drawer_layout"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent">  
  6.     <!-- The main content view -->  
  7.     <FrameLayout  
  8.         android:id="@+id/content_frame"  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="match_parent" />  
  11.     <!-- The navigation drawer -->  
  12.     <ListView android:id="@+id/left_drawer"  
  13.         android:layout_width="240dp"  
  14.         android:layout_height="match_parent"  
  15.         android:layout_gravity="start"  
  16.         android:choiceMode="singleChoice"  
  17.         android:divider="@android:color/transparent"  
  18.         android:dividerHeight="0dp"  
  19.         android:background="#111"/>  
  20. </android.support.v4.widget.DrawerLayout>  

DrawerLayout を利用するうえでいくつか注意点がある
  • メインのコンテンツ用のビュー(上の例だとFrameLayout)は、DrawerLayout の最初のビューでなければならない
  • メインのコンテンツ用のビュー の layout_width と layout_height は match_parent にする
  • NavigationDrawer 用のビュー(上の例だとListView)は、layout_gravity で horizontal gravity を指定しなければならない
    RTL言語をサポートするなら left ではなく start を使う
  • NavigationDrawer 用のビューは dp 単位で幅を指定し、縦は patch_parent にする
    横幅は 320dp 以上にはしない



Drawer List を初期化する

Activity で最初に行うことは、Navigation Drawer のアイテムの初期化。
例えば ListView を利用するなら Adapter をセットする。

  1. public class MainActivity extends Activity {  
  2.     private String[] mPlanetTitles;  
  3.     private ListView mDrawerList;  
  4.     private DrawerLayout mDrawerLayout;  
  5.     ...  
  6.   
  7.     @Override  
  8.     public void onCreate(Bundle savedInstanceState) {  
  9.         super.onCreate(savedInstanceState);  
  10.         setContentView(R.layout.activity_main);  
  11.   
  12.         mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);  
  13.   
  14.         mPlanetTitles = getResources().getStringArray(R.array.planets_array);  
  15.         mDrawerList = (ListView) findViewById(R.id.left_drawer);  
  16.   
  17.         // Set the adapter for the list view  
  18.         mDrawerList.setAdapter(new ArrayAdapter<String>(this,  
  19.                 R.layout.drawer_list_item, mPlanetTitles));  
  20.         // Set the list's click listener  
  21.         mDrawerList.setOnItemClickListener(new DrawerItemClickListener());  
  22.   
  23.         ...  
  24.     }  
  25. }  



Navigation Drawer アイテムのクリックを処理する

Navigation Drawer アイテムがクリックされたときに何をするかはアプリの構造によるが、ここではメインコンテンツ用のビューにセットする Fragment を切り替えている。

  1. private class DrawerItemClickListener implements ListView.OnItemClickListener {  
  2.     @Override  
  3.     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {  
  4.         selectItem(position);  
  5.     }  
  6. }  
  7.   
  8. /** Swaps fragments in the main content view */  
  9. private void selectItem(int position) {  
  10.     // Create a new fragment and specify the planet to show based on position  
  11.     Fragment fragment = new PlanetFragment();  
  12.     Bundle args = new Bundle();  
  13.     args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);  
  14.     fragment.setArguments(args);  
  15.   
  16.     // Insert the fragment by replacing any existing fragment  
  17.     FragmentManager fragmentManager = getFragmentManager();  
  18.     fragmentManager.beginTransaction()  
  19.                    .replace(R.id.content_frame, fragment)  
  20.                    .commit();  
  21.   
  22.     // Highlight the selected item, update the title, and close the drawer  
  23.     mDrawerList.setItemChecked(position, true);  
  24.     setTitle(mPlanetTitles[position]);  
  25.     mDrawerLayout.closeDrawer(mDrawerList);  
  26. }  
  27.   
  28. @Override  
  29. public void setTitle(CharSequence title) {  
  30.     mTitle = title;  
  31.     getActionBar().setTitle(mTitle);  
  32. }  



オープン・クローズイベント時に処理を行う

Drawer が開いたり閉じたりしたのを検知するには、DrawerLayout の setDrawerListener() を使って DrawerLayout.DrawerListener をセットする。
Drawer の開閉時にはそれぞれリスナーの onDrawerOpened() と onDrawerClosed() が呼ばれる。

アプリに ActionBar があるなら、DrawerLayout.DrawerListener よりもそれを implements した ActionBarDrawerToggle が便利。

Navigation Drawer デザインガイドにあるように、Drawer が開いている状態では、タイトルを変えたり、メインコンテンツに関係する Action Item を削除したりすべき。

以下では、ActionBarDrawerToggle を使って、タイトルと Action Item を編集している。
また、利用している R.drawable.ic_drawer 画像は Android_Navigation_Drawer_Icon_20130516.zip からダウンロードできる

  1. public class MainActivity extends Activity {  
  2.     private DrawerLayout mDrawerLayout;  
  3.     private ActionBarDrawerToggle mDrawerToggle;  
  4.     private CharSequence mDrawerTitle;  
  5.     private CharSequence mTitle;  
  6.     ...  
  7.   
  8.     @Override  
  9.     public void onCreate(Bundle savedInstanceState) {  
  10.         super.onCreate(savedInstanceState);  
  11.         setContentView(R.layout.activity_main);  
  12.         ...  
  13.   
  14.         mTitle = mDrawerTitle = getTitle();  
  15.         mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);  
  16.         mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,  
  17.                 R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close) {  
  18.   
  19.             /** Called when a drawer has settled in a completely closed state. */  
  20.             public void onDrawerClosed(View view) {  
  21.                 setTitle(mTitle);  
  22.                 invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()  
  23.             }  
  24.   
  25.             /** Called when a drawer has settled in a completely open state. */  
  26.             public void onDrawerOpened(View drawerView) {  
  27.                 setTitle(mDrawerTitle);  
  28.                 invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()  
  29.             }  
  30.         };  
  31.   
  32.         // Set the drawer toggle as the DrawerListener  
  33.         mDrawerLayout.setDrawerListener(mDrawerToggle);  
  34.     }  
  35.   
  36.     /* Called whenever we call invalidateOptionsMenu() */  
  37.     @Override  
  38.     public boolean onPrepareOptionsMenu(Menu menu) {  
  39.         // If the nav drawer is open, hide action items related to the content view  
  40.         boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);  
  41.         menu.findItem(R.id.action_websearch).setVisible(!drawerOpen);  
  42.         return super.onPrepareOptionsMenu(menu);  
  43.     }  
  44. }  



アプリアイコンでオープン・クローズする

ActionBar のアプリアイコンをタッチして Navigation Drawer を開閉させるには ActionBarDrawerToggle を使えば簡単にできる。
ActionBarDrawerToggle のコンストラクタでは以下の5つが必要。
  • Drawer を持っている Activity
  • DrawerLayout
  • Drawer アイコン(UPアイコンの代わりに表示される)の drawable リソースID
  • Drawer を開くというアクションの説明(アクセシビリティ用)の string リソースID
  • Drawer を閉じるというアクションの説明(アクセシビリティ用)の string リソースID
  1. public class MainActivity extends Activity {  
  2.     private DrawerLayout mDrawerLayout;  
  3.     private ActionBarDrawerToggle mDrawerToggle;  
  4.     ...  
  5.   
  6.     public void onCreate(Bundle savedInstanceState) {  
  7.         ...  
  8.   
  9.         mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);  
  10.         mDrawerToggle = new ActionBarDrawerToggle(  
  11.                 this,                  /* host Activity */  
  12.                 mDrawerLayout,         /* DrawerLayout object */  
  13.                 R.drawable.ic_drawer,  /* nav drawer icon to replace 'Up' caret */  
  14.                 R.string.drawer_open,  /* "open drawer" description */  
  15.                 R.string.drawer_close  /* "close drawer" description */  
  16.                 ) {  
  17.   
  18.             /** Called when a drawer has settled in a completely closed state. */  
  19.             public void onDrawerClosed(View view) {  
  20.                 setTitle(mTitle);  
  21.             }  
  22.   
  23.             /** Called when a drawer has settled in a completely open state. */  
  24.             public void onDrawerOpened(View drawerView) {  
  25.                 setTitle(mDrawerTitle);  
  26.             }  
  27.         };  
  28.   
  29.         // Set the drawer toggle as the DrawerListener  
  30.         mDrawerLayout.setDrawerListener(mDrawerToggle);  
  31.   
  32.         getActionBar().setDisplayHomeAsUpEnabled(true);  
  33.         getActionBar().setHomeButtonEnabled(true);  
  34.     }  
  35.   
  36.     @Override  
  37.     protected void onPostCreate(Bundle savedInstanceState) {  
  38.         super.onPostCreate(savedInstanceState);  
  39.         // Sync the toggle state after onRestoreInstanceState has occurred.  
  40.         mDrawerToggle.syncState();  
  41.     }  
  42.   
  43.     @Override  
  44.     public void onConfigurationChanged(Configuration newConfig) {  
  45.         super.onConfigurationChanged(newConfig);  
  46.         mDrawerToggle.onConfigurationChanged(newConfig);  
  47.     }  
  48.   
  49.     @Override  
  50.     public boolean onOptionsItemSelected(MenuItem item) {  
  51.         // Pass the event to ActionBarDrawerToggle, if it returns  
  52.         // true, then it has handled the app icon touch event  
  53.         if (mDrawerToggle.onOptionsItemSelected(item)) {  
  54.           return true;  
  55.         }  
  56.         // Handle your other action bar items...  
  57.   
  58.         return super.onOptionsItemSelected(item);  
  59.     }  
  60.   
  61.     ...  
  62. }  



Action Bar Sherlock で使う

MainActivity.java の変更
  • invalidateOptionsMenu() の代わりに supportInvalidateOptionsMenu() を使う
  • getActionBar() の代わりに getSupportActionBar() を使う
  • MenuItem のインポートを Action Bar Sherlock のものにする


ライブラリの変更
  • extras/android/support/v4/src/java/android/support/v4/app/ActionBarDrawerToggle.java
  • extras/android/support/v4/src/honeycomb/android/support/v4/app/ActionBarDrawerToggleHoneycomb.java
をコピーして、

ActionBarDrawerToggle.java の変更
  • IMPL を常に ActionBarDrawerToggleImplHC を使うように変更
  • ActionBarDrawerToggleImpl の setActionBarUpIndicator() と setActionBarUpIndicator() の Activity を SherlockActivity や SherlockFragmentActivity に変更
  • ActionBarDrawerToggleImplHC の setActionBarUpIndicator() と setActionBarUpIndicator() も同様
  • ActionBarDrawerToggle のコンストラクタの Activity も同様
  • MenuItem のインポートを Sherlock のものに変更
    1. public boolean onOptionsItemSelected(MenuItem item) {  
    2.     if (item != null && item.getItemId() == ID_HOME && mDrawerIndicatorEnabled) {  
    3.         if (mDrawerLayout.isDrawerVisible(GravityCompat.START)) {  
    4.             mDrawerLayout.closeDrawer(GravityCompat.START);  
    5.         } else {  
    6.             mDrawerLayout.openDrawer(GravityCompat.START);  
    7.         }  
    8.     }  
    9.     return false;  
    10. }  
    1. public boolean onOptionsItemSelected(MenuItem item) {  
    2.     if (item != null && item.getItemId() == ID_HOME && mDrawerIndicatorEnabled) {  
    3.         if (mDrawerLayout.isDrawerVisible(GravityCompat.START)) {  
    4.             mDrawerLayout.closeDrawer(GravityCompat.START);  
    5.         } else {  
    6.             mDrawerLayout.openDrawer(GravityCompat.START);  
    7.         }  
    8.         return true;  
    9.     }  
    10.     return false;  
    11. }  
    にする(最初の if に入ったら true を返す)

    # サンプルで
    1. @Override  
    2. public boolean onOptionsItemSelected(MenuItem item) {  
    3.     // Pass the event to ActionBarDrawerToggle, if it returns  
    4.     // true, then it has handled the app icon touch event  
    5.     if (mDrawerToggle.onOptionsItemSelected(item)) {  
    6.       return true;  
    7.     }  
    8.     // Handle your other action bar items...  
    9.   
    10.     return super.onOptionsItemSelected(item);  
    11. }  
    # としているのに1つ目の if は常に false とかひどいバグでござる。。。


ActionBarDrawerToggleHoneycomb.java の変更
  • setActionBarUpIndicator() と setActionBarUpIndicator() の Activity を SherlockActivity や SherlockFragmentActivity に変更
  • activity.getActionBar() を activity.getSupportActionBar() に変更





0 件のコメント:

コメントを投稿