ListViewにはaddFooterViewというメソッドがありますが、NavigationViewにはそのようなメソッドはありません。
また、NavigationViewにExpandableListViewなどの他のViewGroupを用いたい、もしくは自由にカスタマイズしたい時があります。
今回はNavigationViewの下部に固定フッターを付けたいという状況下で話をします。
NavigationViewは入れ子にできる
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.NavigationView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true">
<RelativeLayout
android:id="@+id/nav_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 後述 -->
</RelativeLayout>
</android.support.design.widget.NavigationView>
通常、NavigationViewには子を持たせずapp:menu
attributeを用いてmenuをinflateします。
app:menu
attribute を用い、NavigationViewの子としてフッターのViewを持たせ、それに対してandroid:layout_gravity="bottom"
を指定してあげれば、フッターが実現したように見えますが、フッターと重なったドロワーアイテムが見えなくなってしまいます。
今回はその解決法として独自ListView, Adapter等を作っていきます。
少し大変になってしまいますが、よりカスタマイズ性が高くなるでしょう。
NavigationViewの中身のレイアウトを作る
まず、NavigationViewの中身のレイアウトを作ります。
今回は、各MenuのアイテムはListViewを用いて表示し、フッターにはFrameLayoutを使います。
もちろんListViewや、FrameLayoutに関してはどんなViewGroupでもViewでも構いません。
なお、以下レイアウトに関しては公式ガイドラインに従います。
<RelativeLayout
android:id="@+id/nav_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/nav_header"
layout="@layout/nav_header" />
<ListView
android:id="@+id/nav_menu_items"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/nav_footer"
android:layout_below="@id/nav_header"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp" />
<FrameLayout
android:id="@id/nav_footer"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_alignParentBottom="true" />
</RelativeLayout>
Navigation Itemのレイアウトを作る
次に、NavigationItemのレイアウトを作ります。
今回は、一般的なDrawerのMenuの様に、アイコンとタイトルを持たせます。
ここももちろん自由にレイアウトを組むことができます。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/top_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginStart="16dp"
tools:src="@drawable/ic_menu_camera"
android:contentDescription="@null" />
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="32dp"
android:layout_toEndOf="@id/icon"
tools:text="Title"
android:textColor="#000000"
android:textSize="14sp" />
</RelativeLayout>
NavigationListAdapter 及び NavigationItemの実装
あらかたレイアウトを組むことができたので各クラスを作っていきます。
まずはMenuの各アイテムを表すNavigationItemクラス。
public class NavigationItem {
@IdRes
private final int id;
private final Drawable icon;
private final String title;
public NavigationItem(@IdRes int id, Drawable icon, String title) {
this.id = id;
this.icon = icon;
this.title = title;
}
public int getId() {
return id;
}
public Drawable getIcon() {
return icon;
}
public String getTitle() {
return title;
}
}
特に言うことはありませんね。
id
, icon
, title
を持ちます。
次にカスタムAdapterを作ります。
public class NavigationListAdapter extends ArrayAdapter<NavigationItem> {
private final Context context;
private final int resource;
private final List<NavigationItem> items;
private final LayoutInflater inflater;
private int selectedPosition = -1;
public NavigationListAdapter(@NonNull Context context, @NonNull List<NavigationItem> items) {
// 先程作成したItemのレイアウトを用いる
super(context, R.layout.nav_item, items);
this.context = context;
this.items = items;
this.resource = R.layout.nav_item;
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
View v;
if (convertView != null) {
v = convertView;
} else {
v = inflater.inflate(resource, null);
}
RelativeLayout topLayout = v.findViewById(R.id.topLayout);
ImageView icon = v.findViewById(R.id.icon);
TextView text = v.findViewById(R.id.title);
NavigationItem item = items.get(position);
icon.setImageDrawable(item.getIcon());
text.setText(item.getTitle());
// Itemが選択されているかどうかで条件分岐
if (position == selectedPosition) {
// 背景、アイコン、テキストのカラーを変更
icon.setColorFilter(ContextCompat.getColor(context, R.color.colorPrimary), PorterDuff.Mode.SRC_IN);
topLayout.setBackgroundResource(R.drawable.selected_nav_item);
text.setTextColor(ContextCompat.getColor(context, R.color.colorPrimary));
} else {
icon.setColorFilter(Color.GRAY, PorterDuff.Mode.SRC_IN);
topLayout.setBackgroundColor(Color.TRANSPARENT);
text.setTextColor(ContextCompat.getColor(context, android.R.color.primary_text_light));
}
return v;
}
// 選択しているアイテムをidで変更する
public void changeSelectedItemById(int id) {
for (int i = 0; i < items.size(); i++) {
NavigationItem item = items.get(i);
if (item.getId() == id) {
this.selectedPosition = i;
}
}
notifyDataSetInvalidated();
}
}
詳細はコード内のコメントに書いています。
例によってNavigationのItemをクリックしたときは背景色や、アイコン、タイトルの色を変えないといけないため、その処理を書いています。
また、Itemがクリックされたときの背景のDrawableとしてselected_nav_item.xmlを定義します
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:colorControlHighlight">
<item android:drawable="@color/selectedNavigationItemBackground"/>
</ripple>
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/selectedNavigationItemBackground"/>
</selector>
V21以上に対して、Ripple Effectを追加するためにレイアウトを分けています。
また、colors.xmlにその色を追加しておきます。
<color name="selectedNavigationItemBackground">#E8E8E8</color>
Activityで使用する
最後にDrawerを用いるActivityでの処理を記述します。
今回の範囲に関係ない処理は省略しています。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
final DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
ViewGroup navHeader = (LinearLayout) findViewById(R.id.nav_header);
ListView navListView = (ListView) findViewById(R.id.nav_menu_items);
View navFooter = findViewById(R.id.nav_footer);
// NavigationItemのリストを取得
final List<NavigationItem> navItems = ...
final NavigationListAdapter adapter = new NavigationListAdapter(this, navItems);
navListView.setAdapter(adapter);
adapter.changeSelectedItemById(R.id.nav_main);
// 区切り線を消す
navListView.setDivider(null);
navListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
drawer.closeDrawer(GravityCompat.START);
performNavigationItemSelectedAction(navItems.get(position).getId());
adapter.changeSelectedItemById(position);
}
});
}
// navigationItemがクリックされた時のアクションを記述する
private void performNavigationItemSelectedAction(@IdRes int id) {
switch (id) {
case R.id.nav_main:
break;
}
}
}
こちらもあらからコメントに詳細を書いています。
何らかの方法を用いてnavItems
を取得してください。
例えばimenuのxmlをパースしてNavigationItemに変換するなどです。
他にも方法はあると思いますが、今回はこのようにしてNavigationDrawerの下部に固定フッターを追加しました。