(三十一) NavigationView 原理分析

版权声明:本文为博主原创文章,未经博主允许不得转载。

本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。

一、NavigationView 使用

1.demo

Navigation 主要是配合 DrawerLayout 进行使用,作为 DrawerLayout 的侧滑菜单。Navigation 有分为上下两个部分,上部分是头部,下部分是菜单。

MainActivity :

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

Activity 不需要写什么代码即可实现一个简单的 demo。

activity_main.xml:

"1.0" encoding="utf-8"?>
.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.xiaoyue.navigation.MainActivity">

    "wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    .support.design.widget.NavigationView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:headerLayout="@layout/head"
        app:menu="@menu/view_menu">

    .support.design.widget.NavigationView>

.support.v4.widget.DrawerLayout>

NavigationView 在这边作为 DrawerLayout 的侧滑菜单,属性 app:headerLayout 与 app:menu 分别是头部的布局文件菜单的配置文件

head.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:gravity="center"
    android:layout_height="match_parent">
    <ImageView
        android:id="@+id/ivAvatar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingTop="15dp"
        android:src="@mipmap/ic_account_circle_white_48dp" />

    <TextView
        android:id="@+id/tvNickName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="15dp"
        tools:text="JohnTsai"
        android:text="登录"
        android:gravity="center"
        android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
LinearLayout>

头部的布局文件。

view_menu.xml:


<menu xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:id="@+id/nav_me"
            android:title="我"
            android:icon="@mipmap/ic_mine_gray_24"
            />
    <group>
        <item
            android:id="@+id/nav_friend"
            android:title="好友"
            android:icon="@mipmap/ic_friends_gray_24"/>
        <item
            android:id="@+id/nav_notification"
            android:title="通知"
            android:icon="@mipmap/ic_launcher"/>
        <item
            android:id="@+id/nav_message"
            android:title="私信"
            android:icon="@mipmap/ic_messages_gray_24"
            />
    group>

    <group
        android:id="@+id/group_manage">
        <item
            android:id="@+id/nav_manage"
            android:title="应用管理"
            android:icon="@mipmap/ic_app_management_gray_24"/>
    group>
menu>

位于 /res/menu 下,配置后会自动加载生成菜单。

效果:
(三十一) NavigationView 原理分析_第1张图片

上面红色区域是头部,下面是菜单部分。(头部图像由于截图的原因,较模糊)

这里会发现,下边菜单的所有图标都是灰色的,这个不是由于图标本身就是灰色的原因。通知这个菜单采用了安卓机器人的绿色小图标,结果显示出来的还是灰色的,这是安卓绘制的原因造成。

2.添加图标颜色

修改 activity_main.xml:

"1.0" encoding="utf-8"?>
.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.xiaoyue.navigation.MainActivity">

    "wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    .support.design.widget.NavigationView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:headerLayout="@layout/head"
        app:menu="@menu/view_menu"
        app:itemIconTint="@color/colorAccent">

    .support.design.widget.NavigationView>

.support.v4.widget.DrawerLayout>

为 NavigationView 添加一个属性 app:itemIconTint。

效果:
(三十一) NavigationView 原理分析_第2张图片

这样图标是拥有颜色,但是所有的图标颜色全部变化成我们设置的颜色,这是谷歌提供的,但不是我们想要的。

如果说要显示其本来颜色的话,需要在在 MainActivity 中进行修改。
修改 MainActivity:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
        navigationView.setItemIconTintList(null);
    }
}

在代码中调用 navigationView.setItemIconTintList(null) 即可设置显示为原来的颜色。

二、NavigationView 源码

1.NavigationView 继承 FrameLayout

NavigationView 的父类是 ScrimInsetsFrameLayout, ScrimInsetsFrameLayout 的父类是 FrameLayout,所以 NavigationView 最终也是继承于 FrameLayout。

2.采用 MVP 模式

这是 NavigationView 的两个成员变量,mPresenter 就是 MVP 中的 P 层。(MVP 不懂的可以看一下这个,传送门:http://blog.csdn.net/lmj623565791/article/details/46596109)

    private final NavigationMenu mMenu;
    private final NavigationMenuPresenter mPresenter = new NavigationMenuPresenter();

NavigationView 本身作为 C 层,主要是在构造函数中进行初始化初始化 NavigationView 本身样式,并没有进行数据的获取等操作。

NavigationView 构造函数:

    public NavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        ...
        mPresenter.setId(PRESENTER_NAVIGATION_VIEW_ID);
        mPresenter.initForMenu(context, mMenu);
        mPresenter.setItemIconTintList(itemIconTint);
        if (textAppearanceSet) {
            mPresenter.setItemTextAppearance(textAppearance);
        }
        mPresenter.setItemTextColor(itemTextColor);
        mPresenter.setItemBackground(itemBackground);
        mMenu.addMenuPresenter(mPresenter);
        addView((View) mPresenter.getMenuView(this));

        if (a.hasValue(R.styleable.NavigationView_menu)) {
            inflateMenu(a.getResourceId(R.styleable.NavigationView_menu, 0));
        }

        if (a.hasValue(R.styleable.NavigationView_headerLayout)) {
            inflateHeaderView(a.getResourceId(R.styleable.NavigationView_headerLayout, 0));
        }

        a.recycle();
    }

NavigationView 通过 mPresenter 早先定义好的接口去获取数据,本身不与 M 层直接进行交互。NavigationView 的显示是通过 addView((View) mPresenter.getMenuView(this)) 直接添加进来的

NavigationMenuPresenter 就是 P 层,获取数据,并刷新视图,负责 NavigationView 与数据之间的交互。

NavigationMenuPresenter 的 getMenuView:

    public MenuView getMenuView(ViewGroup root) {
        if (mMenuView == null) {
            //NavigationView 的菜单部分
            mMenuView = (NavigationMenuView) mLayoutInflater.inflate(
                    R.layout.design_navigation_menu, root, false);
            if (mAdapter == null) {
                mAdapter = new NavigationMenuAdapter();
            }
            //NavigationView 的头部
            mHeaderLayout = (LinearLayout) mLayoutInflater
                    .inflate(R.layout.design_navigation_item_header,
                            mMenuView, false);
            mMenuView.setAdapter(mAdapter);
        }
        return mMenuView;
    }

分别看一下 NavigationView 的菜单部分和头部的布局。

design_navigation_menu.xml:

.support.design.internal.NavigationMenuView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/design_navigation_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/design_navigation_padding_bottom"
        android:clipToPadding="false"
        android:scrollbars="vertical"/>

菜单部分布局就是一个 NavigationMenuView,在 NavigationMenuPresenter 的 getMenuView 中获取到这个 NavigationMenuView,并返回到 NavigationView 的构造方法中,同时直接添加到 NavigationView 中,即 NavigationView 下套了一层 NavigationMenuView

点进去查看 NavigationMenuView 类,可以发现继承于 RecyclerView。那后面的用法就很明确了,添加 mAdapter,点进去也可以发现 NavigationMenuAdapter 就是 RecyclerView.Adapter< ViewHolder >。

NavigationMenuPresenter 的 getMenuView 中还有一个布局 design_navigation_item_header。

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" />

这就是一个 LinearLayout ,(注意:在 getMenuView 并没有把头部加到了 NavigationMenuView 里面)在 NavigationMenuAdapter 里面添加到 NavigationMenuView 中。

上张图展示一下 NavigationView 的布局架构:

(三十一) NavigationView 原理分析_第3张图片

最外层红色是 NavigationView 。
NavigationView 里面套了一层蓝色的 NavigationMenuView(RecyclerView 的子类),NavigationMenuView 里面加载的是一个 Adapter,Adapter 有多种 ViewType。上面黄色部分是一个 LinearLayout (),里面套了层黑色的是我么自己的布局;下面一部分是一些菜单的 item 。头部跟菜单都属于 Adapter。

3.加载菜单数据

返回到 NavigationView 的构造函数中继续往下。
NavigationView 构造函数:

    public NavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        ...

        if (a.hasValue(R.styleable.NavigationView_menu)) {
            inflateMenu(a.getResourceId(R.styleable.NavigationView_menu, 0));
        }

        if (a.hasValue(R.styleable.NavigationView_headerLayout)) {
            inflateHeaderView(a.getResourceId(R.styleable.NavigationView_headerLayout, 0));
        }

        a.recycle();
    }

在最后进行判断是否在引用的时候添加了头部和菜单,有添加的话进行重新刷新。

点击查看 inflateMenu 代码:
NavigationView 的 inflateMenu:

    public void inflateMenu(int resId) {
        mPresenter.setUpdateSuspended(true);
        //获取菜单数据
        getMenuInflater().inflate(resId, mMenu);
        mPresenter.setUpdateSuspended(false);
        //刷新 adapter
        mPresenter.updateMenuView(false);
    }

resId 即我们在布局文件设置的 app:menu 属性的值,在 getMenuInflater().inflate(resId, mMenu) 中对菜单的配置文件进行解析,获取到菜单数据。

你可能感兴趣的:(高级UI)