Kotlin 自定义 NavigationView

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 中:

  • HORIZONTAL = 0
  • VERTICAL = 1

因此下面的.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) {}
})

Kotlin 自定义 NavigationView_第1张图片
Kotlin 自定义 NavigationView_第2张图片

通过实例化 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)
            }
        }
    }
}

Kotlin 自定义 NavigationView_第3张图片

你可能感兴趣的:(——,Kotlin)