前回のMaterial Design な Navigation Drawer を目指す(1)では、新しいウィジェット ToolBar と RecyclerView を利用して、シンプルなテキストのリストを表示する Drawer をご紹介しました。今回の記事では、Drawer 内に Header や Divider などの複数要素を含めた Drawer までをご紹介します。
各アイテムのレイアウトを設定する
今回作成した Drawer では、Header / Menu / SubHeader / Divider の4つのアイテムを表示します。アイテム毎にレイアウト.xmlを作成し、この際のマージンやテキストサイズなどは、Patterns > Navigation drawer で記載されているマージンを参考に設定しますが、特段明記されていない箇所は適当な値になっています。下記のXMLは menu 部分のレイアウトの例です。
res/layout/drawer_menu.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http: //schemas.android.com/apk/res/android"android: layout_width="match_parent"android: layout_height="48dp"android: layout_gravity="start"android:background="?android: attr/selectableItemBackground"> <ImageViewandroid: layout_width="20dp"android: layout_height="20dp"android: id="@+id/menu_icon"android: contentDescription="@string/desc_icon"android: layout_marginStart="16dp"android: layout_centerVertical="true" /> <TextViewandroid:layout_width="wrap_content"android: layout_height="wrap_content"android: textAppearance="? android:attr/textAppearanceMedium"android: id="@+id/menu_text"android:layout_marginStart="72dp"android:layout_marginEnd="16dp"android:textSize="14sp"android:textColor="#D9000000"android:gravity="center_vertical"android:layout_centerVertical="true" /> </RelativeLayout> |
アイテムの表示設定XMLを作成する
今回の複数アイテムを含む Drawer を作るに当たって、どのアイテムをどの順番でどの内容で表示するかを下記のようにXMLで設定してます。下記の例のような構成で menu アイテムの表示設定と、Drawer 全体の構成を設定するようにしました。
res/values/drawer_data.xml
<!-- アイテム種別 type --> <array name="type_menu"> <item name="key">type</item> <item name="value"> menu</item> </array>~~~<!-- 表示内容・設定 content --> <array name="menu_icon"> <item name="key">icon</item> <item name="value">@drawable/test_image_w100x100</item> </array> <array name="menu_text_1"> <item name="key">text</item> <item name="value">menu111</item> </array>~~~<!-- アイテム内の構造 item --> <array name="menu1"> <item>@array/type_menu</item> <item>@array/menu_icon</item> <item>@array/menu_text_1</item> </array>~~~<!-- ドロワーの全体構造 menu --> <array name="drawer_menu_list"> <item>@array/header</item> <item>@array/menu1</item> <item>@array/menu2</item> <item>@array/menu3</item> <item>@array/divider1</item> <item>@array/sub_header1</item> <item>@array/menu4</item> <item>@array/menu5</item> </array> |
表示設定XMLを変換して RecyclerView.Adapter に渡す
今回作成したXMLではアイテム内の構造情報を key-value 形式で保持するようにしているので、HashMap のリストに変換して RecyclerView.Adapter に渡しています。
// XMLTypedArray drawerMenuList = getResources(). obtainTypedArray(R.array.drawer_menu_list); int menuLength = drawerMenuList.length(); // RecyclerView. Adapter に渡すデータArrayList<HashMap<String, Object>> drawerMenuArr = new ArrayList<HashMap<String, Object>>(); for (int i = 0; i < menuLength ; i++) {TypedArray itemArr = getResources(). obtainTypedArray(drawerMenuList.getResourceId(i, 0)); int itemLength = itemArr.length(); HashMap<String,Object> content = new HashMap<String, Object>();drawerMenuArr.add(content); for (int j = 0; j < itemLength ; j++) { TypedArray contentArr = getResources(). obtainTypedArray(itemArr.getResourceId(j, 0)); // key-valueif(contentArr.getString(0). contains("icon")) { content.put(contentArr.getString(0), contentArr.getDrawable(1)); } else { content.put(contentArr.getString(0), contentArr.getString(1) ); }contentArr.recycle(); }itemArr.recycle();} |
複数アイテムを制御する RecyclerView.Adapter を用意
前回の SimpleDrawerAdapter ではテキスト1種を1箇所に表示するだけでしたが、今回の利用する Adapter ではテキストや画像リソースなど複数種類の設定を、それぞれレイアウト設定に合わせて表示・反映しています。getItemViewType()でアイテムの種類(header/menuなど)を判別できるので、あとは各メソッド内でViewTypeに合わせて処理を行います。
onCreateViewHolder:各アイテムのタイプに合わせて、ViewHolderを作成する
@Overridepublic DrawerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView;switch (viewType){case R.array.type_menu: // menu 用のレイアウトを利用する itemView = LayoutInflater.from(parent.getContext() ). inflate(R.layout.drawer_menu, parent, false);itemView.setClickable(true); break;~~~ } ViewHolder vh = new ViewHolder(itemView, viewType); return vh; } |
RecyclerView.ViewHolder:各アイテムのitemViewから各Viewを取得する
public static class ViewHolder extends RecyclerView.ViewHolder { public ViewHolder(View itemView, int viewType) { super(itemView);switch (viewType) { case R.array.type_menu: // menu 用レイアウトに含まれる、各Viewを取得mTextView = (TextView) itemView.findViewById(R.id.menu_text); mIconImageView = (ImageView) itemView.findViewById(R.id.menu_icon);break;~~~} } } |
onBindViewHolder:各アイテムのViewに、作成したデータをバインドする
public void onBindViewHolder (ViewHolder holder, int position) { HashMap<String, Object> menu = mDrawerMenuArr.get(position); switch (holder.getItemViewType()){case R.array.type_menu: // ViewHolder で取得したViewに表示するデータをバインド holder.mTextView.setText(menu.get("text"). toString() );holder.mIconImageView.setImageDrawable((Drawable) menu.get("icon"));break;~~~} } |
getItemViewType:各アイテムの種類(タイプ)を取得する
public int getItemViewType (int position) { HashMap<String, Object> menu = mDrawerMenuArr.get(position); switch (menu.get("type").toString() ) {case "menu":return R.array.type_menu;~~~} } |
Menu アイテムのタッチイベントを受け取り、コンテンツを切り替える
RecyclerView では OnItemTouchListener でタッチイベントを検知することができますが、 ListView#OnClickListener のように直接的には position の値が取得できません。
下記の例では、OnItemTouchListener で取得できるタッチ座標から、タッチしている View を取得し、その View から position の値を取得して、タッチした Menu アイテムに応じたテキストを表示しています。
// GestureDetectorを使って、 onSingleTapUpを検知 mGestureDetector = new GestureDetector(getApplicationContext (), new GestureDetector.SimpleOnGestureListener() { @Override public boolean onSingleTapUp(MotionEvent e) { return true;}}); mRecyclerView.addOnItemTouchListener (new RecyclerView.OnItemTouchListener() { @Overridepublic boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) { if (mGestureDetector.onTouchEvent(e)) { // onSingleTapUpの時に、タッチしているViewを取得 View childView = view.findChildViewUnder(e.getX(), e.getY() ); int potision = mRecyclerView.getChildPosition(childView); // タッチしているViewのデータを取得HashMap<String, Object> data = drawerMenuArr.get(potision); // Menu アイテムのみ if (data.get("type").toString().equals("menu")) { // 選択されたアイテムに応じてメインコンテンツを切替 TextView contentView = (TextView) findViewById(R.id.contentText); contentView.setText((String) data.get("text")); // ドロワー閉じるmDrawerLayout.closeDrawers(); return true;} }return false; } @Overridepublic void onTouchEvent(RecyclerView rv, MotionEvent e) { } }); |
ステータスバー透過させて、その下までドロワーを表示
ここまでで概ね Drawer としての体裁が整いましたが、最後にステータスバーの透過の設定をしてPatterns > Navigation drawerの Drawer により近づけたいと思います。
res/values-v21/styles.xml
<resources> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:windowTranslucentStatus"> true</item> </style> </resources> |
透過自体は、Style に windowTranslucentStatus=”true” を記述するだけですが、この設定だけではToolBar自体もステータスバーの下に潜り込んでしまいます。
これを回避する為、ToolBar の fitsSystemWindows=”true” を追記します。
<android.support.v7.widget. Toolbar~~~android: fitsSystemWindows="true"/> |