NavigationView 的布局写法如下
.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer" />
查看NavigationView的源代码,发现左侧栏的布局是通过NavigationMenuPresenter这个类来实现的。源码的路径为: NavigationView -> NavigationMenuPresenter
public MenuView getMenuView(ViewGroup root) {
if (this.menuView == null) {
this.menuView = (NavigationMenuView)this.layoutInflater.inflate(layout.design_navigation_menu, root, false);
if (this.adapter == null) {
this.adapter = new NavigationMenuPresenter.NavigationMenuAdapter();
}
this.headerLayout = (LinearLayout)this.layoutInflater.inflate(layout.design_navigation_item_header, this.menuView, false);
this.menuView.setAdapter(this.adapter);
}
return this.menuView;
}
public NavigationMenuPresenter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch(viewType) {
case 0:
return new NavigationMenuPresenter.NormalViewHolder(NavigationMenuPresenter.this.layoutInflater, parent, NavigationMenuPresenter.this.onClickListener);
case 1:
return new NavigationMenuPresenter.SubheaderViewHolder(NavigationMenuPresenter.this.layoutInflater, parent);
case 2:
return new NavigationMenuPresenter.SeparatorViewHolder(NavigationMenuPresenter.this.layoutInflater, parent);
case 3:
return new NavigationMenuPresenter.HeaderViewHolder(NavigationMenuPresenter.this.headerLayout);
default:
return null;
}
}
private static class HeaderViewHolder extends NavigationMenuPresenter.ViewHolder {
public HeaderViewHolder(View itemView) {
super(itemView);
}
}
private static class SeparatorViewHolder extends NavigationMenuPresenter.ViewHolder {
public SeparatorViewHolder(LayoutInflater inflater, ViewGroup parent) {
super(inflater.inflate(layout.design_navigation_item_separator, parent, false));
}
}
private static class SubheaderViewHolder extends NavigationMenuPresenter.ViewHolder {
public SubheaderViewHolder(LayoutInflater inflater, ViewGroup parent) {
super(inflater.inflate(layout.design_navigation_item_subheader, parent, false));
}
}
private static class NormalViewHolder extends NavigationMenuPresenter.ViewHolder {
public NormalViewHolder(LayoutInflater inflater, ViewGroup parent, OnClickListener listener) {
super(inflater.inflate(layout.design_navigation_item, parent, false));
this.itemView.setOnClickListener(listener);
}
}
资源文件的位置: sdk路径\extras\android\support\design\res\layout
(1)layout.design_navigation_item_header
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/navigation_header_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="@dimen/design_navigation_separator_vertical_padding" />
(2)layout.design_navigation_item_subheader
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?attr/listPreferredItemHeightSmall"
android:gravity="center_vertical|start"
android:maxLines="1"
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
android:paddingRight="?attr/listPreferredItemPaddingRight"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textColor="?android:textColorSecondary"/>
(3)layout.design_navigation_item
.support.design.internal.NavigationMenuItemView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?attr/listPreferredItemHeightSmall"
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
android:paddingRight="?attr/listPreferredItemPaddingRight"
android:foreground="?attr/selectableItemBackground"
android:focusable="true"/>
HORIZONTAL 和 VERTICAL都是 int 型的常量,定义在 LinearLayoutCompat.java 中:
因此下面的.setOrientation(0) 代表水平布局,而这个ViewStub 也之会出现在水平位置
public NavigationMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.accessibilityDelegate = new AccessibilityDelegateCompat() {
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
info.setCheckable(NavigationMenuItemView.this.checkable);
}
};
this.setOrientation(0);
LayoutInflater.from(context).inflate(layout.design_navigation_menu_item, this, true);
this.iconSize = context.getResources().getDimensionPixelSize(dimen.design_navigation_icon_size);
this.textView = (CheckedTextView)this.findViewById(id.design_menu_item_text);
this.textView.setDuplicateParentStateEnabled(true);
ViewCompat.setAccessibilityDelegate(this.textView, this.accessibilityDelegate);
}
layout.design_navigation_menu_item
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<CheckedTextView
android:id="@+id/design_menu_item_text"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:drawablePadding="@dimen/design_navigation_icon_padding"
android:gravity="center_vertical|start"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"/>
<ViewStub
android:id="@+id/design_menu_item_action_area_stub"
android:inflatedId="@+id/design_menu_item_action_area"
android:layout="@layout/design_menu_item_action_area"
android:layout_width="wrap_content"
android:layout_height="match_parent"/>
merge>
layout.design_menu_item_action_area
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"/>
(4)layout.design_navigation_item_separator
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider"/>
FrameLayout>
从下面代码打印的日志,我们可以得出一份大概的 NavigationView 结构图,如下
NavigationView
|
|--headerLayout
|
|--menu
|
|--HeaderViewHolder
| |
| |--LinearLayout
| |
| |--AppCompatImageView
| |--AppCompatTextView
| |--AppCompatTextView
|
|--SubheaderViewHolder
| |
| |--AppCompatTextView
|
|--NormalViewHolder
| |
| |--NavigationMenuItemView
| |
| |--AppCompatCheckedTextView
| |--ViewStub
|
|--SeparatorViewHolder
|
|--FrameLayout
|
|--View
在知道了官方的 NavigationView 的大致结构之后,我们就可以通过反射,来修改其中的一些属性的值了。
val context by lazy { this } //这里使用了委托,表示只有使用到context才会执行该段代码
nav_view.setBackgroundColor(ContextCompat.getColor(context, android.R.color.holo_red_light))
nav_view.setNavigationItemSelectedListener(this)
val presenterField = nav_view.javaClass.getDeclaredField("presenter")
presenterField.isAccessible = true
val menuPresenter = presenterField.get(nav_view)
val menuViewField = menuPresenter.javaClass.getDeclaredField("menuView")
menuViewField.isAccessible = true
val menuView = menuViewField.get(menuPresenter) as NavigationMenuView
menuView.addOnChildAttachStateChangeListener(object : RecyclerView.OnChildAttachStateChangeListener {
override fun onChildViewAttachedToWindow(view: View) {
val viewHolder = menuView.getChildViewHolder(view)
Log.e("xxx1", viewHolder.javaClass.simpleName)
Log.w("xxx2", viewHolder.itemView.javaClass.simpleName)
//if(viewHolder != null && viewHolder.itemView != null)
when (viewHolder.javaClass.simpleName) {
/*
HeaderViewHolder, LinearLayout, 1(LinearLayout, 3[AppCompatImageView, AppCompatTextView, AppCompatTextView])
在 headerLayout 与 menu 之间,有且仅出现一次
*/
"HeaderViewHolder" -> {
if (viewHolder.itemView is LinearLayout) {
val layout = viewHolder.itemView as LinearLayout
layout.setBackgroundColor(ContextCompat.getColor(context, android.R.color.holo_blue_light))
Log.i("xxx3", layout.childCount.toString())
Log.d("xxx4", layout.getChildAt(0).javaClass.simpleName)
val child = layout.getChildAt(0) as LinearLayout
Log.v("xxx5", child.childCount.toString())
for (i in 0 until child.childCount) {
Log.v("xxx5", child.getChildAt(i).javaClass.simpleName)
}
}
}
/*
SubheaderViewHolder, AppCompatTextView
在 item 作为容器的组中出现:
*/
"SubheaderViewHolder" -> {
if (viewHolder.itemView is AppCompatTextView) {
val layout = viewHolder.itemView as AppCompatTextView
layout.setBackgroundColor(ContextCompat.getColor(context, android.R.color.holo_green_light))
}
}
/*
NormalViewHolder, NavigationMenuItemView, 2(AppCompatCheckedTextView, ViewStub)
在 item 作为列表子项中出现
*/
"NormalViewHolder" -> {
if (viewHolder.itemView is NavigationMenuItemView) {
val layout = viewHolder.itemView as NavigationMenuItemView
layout.setBackgroundColor(ContextCompat.getColor(context, android.R.color.holo_orange_light))
Log.i("xxx3", layout.childCount.toString())
for (i in 0 until layout.childCount) {
Log.d("xxx4", layout.getChildAt(i).javaClass.simpleName)
}
}
}
/*
SeparatorViewHolder, FrameLayout, 1(View)
仅在 SubheaderViewHolder 顶部出现,此外 SeparatorViewHolder 与其上留有一点空白,会透出 nav_view 的背景色
*/
"SeparatorViewHolder" -> {
if (viewHolder.itemView is FrameLayout) {
val layout = viewHolder.itemView as FrameLayout
val line = layout.getChildAt(0)
line.setBackgroundColor(ContextCompat.getColor(context, android.R.color.holo_purple))
line.layoutParams.height = 10
Log.i("xxx3", layout.childCount.toString())
Log.d("xxx4", layout.getChildAt(0).javaClass.simpleName)
}
}
else -> {
viewHolder.itemView.setBackgroundColor(ContextCompat.getColor(context, android.R.color.darker_gray))
}
}
}
override fun onChildViewDetachedFromWindow(view: View) {}
})
通过实例化 ViewStub,并动态添加固件可以改变列表的布局。
注意:viewHolder.itemView 默认是水平方向的,你可以修改 viewHolder.itemView 的 orientation 属性,但是修改之后 icon 和 titile 便会消失,从而只剩通过 ViewStub 动态添加的布局
"NormalViewHolder" -> {
if (viewHolder.itemView is NavigationMenuItemView) {
val layout = viewHolder.itemView as NavigationMenuItemView
layout.setBackgroundColor(ContextCompat.getColor(context, android.R.color.holo_orange_light))
Log.i("xxx3", layout.childCount.toString())
for (i in 0 until layout.childCount) {
val child = layout.getChildAt(i)
Log.d("xxx4", child.javaClass.simpleName)
if(child is ViewStub) {
val viewStub = child.inflate() as FrameLayout
val textview = TextView(context)
textview.text = "xx"
val layoutParam = FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
layoutParam.gravity = GravityCompat.START
viewStub.addView(textview, layoutParam)
}
}
}
}