Material Design な Navigation Drawer を目指す(2)

前回の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"/>

Related Post