Android 自己实现 NavigationView [Design Support Library(1)]

转载请标明出处:
http://blog.csdn.net/lmj623565791/article/details/46405409
本文出自:【张鸿洋的博客】

一、概述

Google I/O 2015 给大家带来了Android Design Support Library,对于希望做md风格的app的来说,简直是天大的喜讯了~大家可以通过Android Design Support Library该文章对其进行了解,也可以直接在github上下载示例代码运行学习。为了表达我心中的喜悦,我决定针对该库写一系列的文章来分别介绍新增加的控件。

ok,那么首先介绍的就是NavigationView,本文将首先介绍该控件的使用,然后介绍如何自己去编写该控件。

注意下更新下as的SDK,然后在使用的过程中,在build.gradle中添加:

compile ‘com.android.support:design:22.2.0‘

在md风格的app中,例如如下风格的侧滑菜单非常常见:

技术分享

在之前的设计中,你可能需要考虑如何去布局实现,例如使用ListView;再者还要去设计Item的选中状态之类~~

but,现在,google提供了NavigationView,你只需要写写布局文件,这样的效果就ok了,并且兼容到Android 2.1,非常值得去体验一下。接下来我们来介绍如何去使用这个NavigationView

二、使用

使用起来very simple ,主要就是写写布局文件~

(一)布局文件

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
    android:id="@+id/id_drawer_layout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    >

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.Toolbar
            android:id="@+id/id_toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

        <TextView
            android:id="@+id/id_tv_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="HelloWorld"
            android:textSize="30sp"/>
    </RelativeLayout>

    <android.support.design.widget.NavigationView
        android:id="@+id/id_nv_menu"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="left"
        android:fitsSystemWindows="true"
        app:headerLayout="@layout/header_just_username"
        app:menu="@menu/menu_drawer"
        />

</android.support.v4.widget.DrawerLayout>

可以看到我们的最外层是DrawerLayout,里面一个content,一个作为drawer。我们的drawer为NavigationView
注意这个view的两个属性app:headerLayout="@layout/header_just_username"app:menu="@menu/menu_drawer",分别代表drawer布局中的header和menuitem区域,当然你可以根据自己的情况使用。

接下来看看header的布局文件和menu配置文件:


<?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="192dp"
                android:background="?attr/colorPrimaryDark"
                android:orientation="vertical"
                android:padding="16dp"
                android:theme="@style/ThemeOverlay.AppCompat.Dark">


    <TextView
        android:id="@+id/id_link"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="16dp"
        android:text="http://blog.csdn.net/lmj623565791"/>

    <TextView
        android:id="@+id/id_username"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/id_link"
        android:text="Zhang Hongyang"/>

    <ImageView
        android:layout_width="72dp"
        android:layout_height="72dp"
        android:layout_above="@id/id_username"
        android:layout_marginBottom="16dp"
        android:src="@mipmap/icon"/>


</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <group android:checkableBehavior="single">
        <item
            android:id="@+id/nav_home"
            android:icon="@drawable/ic_dashboard"
            android:title="Home"/>
        <item
            android:id="@+id/nav_messages"
            android:icon="@drawable/ic_event"
            android:title="Messages"/>
        <item
            android:id="@+id/nav_friends"
            android:icon="@drawable/ic_headset"
            android:title="Friends"/>
        <item
            android:id="@+id/nav_discussion"
            android:icon="@drawable/ic_forum"
            android:title="Discussion"/>
    </group>

    <item android:title="Sub items">
        <menu>
            <item
                android:icon="@drawable/ic_dashboard"
                android:title="Sub item 1"/>
            <item
                android:icon="@drawable/ic_forum"
                android:title="Sub item 2"/>
        </menu>
    </item>

</menu>

别放错文件夹哈~

布局文件写完了,基本就好了,是不是很爽~看似复杂的效果,写写布局文件就ok。

ps:默认的颜色很多是从当前的主题中提取的,比如icon的stateColor,当然你也可以通过以下属性修改部分样式:

app:itemIconTint=""
app:itemBackground=""
app:itemTextColor=""

(二)Activity

最后是Activity:

package com.imooc.testandroid;

import android.os.Bundle;
import android.support.design.widget.NavigationView;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;


public class NavigationViewActivity extends ActionBarActivity
{

    private DrawerLayout mDrawerLayout;
    private NavigationView mNavigationView;

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

        mDrawerLayout = (DrawerLayout) findViewById(R.id.id_drawer_layout);
        mNavigationView = (NavigationView) findViewById(R.id.id_nv_menu);

        Toolbar toolbar = (Toolbar) findViewById(R.id.id_toolbar);
        setSupportActionBar(toolbar);

        final ActionBar ab = getSupportActionBar();
        ab.setHomeAsUpIndicator(R.drawable.ic_menu);
        ab.setDisplayHomeAsUpEnabled(true);

        setupDrawerContent(mNavigationView);


    }

    private void setupDrawerContent(NavigationView navigationView)
    {
        navigationView.setNavigationItemSelectedListener(

                new NavigationView.OnNavigationItemSelectedListener()
                {

                    @Override
                    public boolean onNavigationItemSelected(MenuItem menuItem)
                    {
                        menuItem.setChecked(true);
                        mDrawerLayout.closeDrawers();
                        return true;
                    }
                });
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_navigation_view, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
        if(item.getItemId() == android.R.id.home)
        {
            mDrawerLayout.openDrawer(GravityCompat.START);
            return true ;
        }
        return super.onOptionsItemSelected(item);
    }

}

我们在Activity里面可以通过navigationView去navigationView.setNavigationItemSelectedListener,当selected的时候,menuItem去setChecked(true)。

别忘了设置theme~

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
    </style>

    <style name="Theme.DesignDemo" parent="Base.Theme.DesignDemo">
    </style>

    <style name="Base.Theme.DesignDemo" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="colorPrimary">#673AB7</item>
        <item name="colorPrimaryDark">#512DA8</item>
        <item name="colorAccent">#FF4081</item>
        <item name="android:windowBackground">@color/window_background</item>
    </style>

</resources>


<color name="window_background">#FFF5F5F5</color>

 <activity
    android:name=".NavigationViewActivity"
    android:label="@string/title_activity_navigation_view"
    android:theme="@style/Theme.DesignDemo">
</activity>

ok,到此就搞定了~~

不过存在一个问题,此时你如果点击Sub items里面的Sub item,如果你期望当前选中应该是Sub item,你会发现不起作用。那怎么办呢?

(三)Sub Item支持Cheable

这里可以修改menu的配置文件:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <group>
        <item
            android:id="@+id/nav_home"
            android:checkable="true"
            android:icon="@drawable/ic_dashboard"
            android:title="Home"/>
        <item
            android:id="@+id/nav_messages"
            android:checkable="true"
            android:icon="@drawable/ic_event"
            android:title="Messages"/>
        <item
            android:id="@+id/nav_friends"
            android:checkable="true"
            android:icon="@drawable/ic_headset"
            android:title="Friends"/>
        <item
            android:id="@+id/nav_discussion"
            android:checkable="true"
            android:icon="@drawable/ic_forum"
            android:title="Discussion"/>
    </group>

    <item android:title="Sub items">
        <menu>
            <item
                android:checkable="true"
                android:icon="@drawable/ic_dashboard"
                android:title="Sub item 1"/>
            <item
                android:checkable="true"
                android:icon="@drawable/ic_forum"
                android:title="Sub item 2"/>
        </menu>
    </item>

</menu>

android:checkableBehavior="single"去掉,然后给每个item项添加android:checkable="true"

然后在代码中:

navigationView.setNavigationItemSelectedListener(

                new NavigationView.OnNavigationItemSelectedListener()
                {

                    private MenuItem mPreMenuItem;

                    @Override
                    public boolean onNavigationItemSelected(MenuItem menuItem)
                    {
                        if (mPreMenuItem != null) mPreMenuItem.setChecked(false);
                        menuItem.setChecked(true);
                        mDrawerLayout.closeDrawers();
                        mPreMenuItem = menuItem;
                        return true;
                    }
                });

存一下preMenuItem,手动切换。

效果:

技术分享

ok,哈~其实这个还是参考链接2里面的一个评论说的~~

到此用法就介绍完了~有木有一点小激动~ 但是还有个问题,对于app,就像ActionBar最初的出现,一开始大家都欢欣鼓舞,后来发现app中多数情况下需要去定制,尼玛,是不是忽然觉得ActionBar太死板了,恶心死了(当然了现在有了ToolBar灵活度上好多了,关于Toolbar可以参考Android 5.x Theme 与 ToolBar 实战~对于上述NavigationView可能也会存在定制上的问题,比如我希望选中的Item左边有个高亮的竖线之类的效果。那么,针对于各种需求,想要能解决各种问题,最好的方式就是说对于NavigationView的效果自己可以实现。最好,我们就来看看NavigationView自己实现有多难?

三、自己实现NavigationView效果

其实NavigationView的实现非常简单,一个ListView就可以了,甚至都不需要去自定义,简单写一个Adapter就行了~~

技术分享

首先观察该图,有没有发现神奇之处,恩,你肯定发现不了,因为我们做的太像了。

其实这个图就是我通过ListView写的一个~是不是和原版很像(~哈~参考了源码的实现,当然像。)

接下来分析,如果说是ListView,那么Item的type肯定不止一种,那我们数一数种类:

  • 带图标和文本的
  • 仅仅是文本的 Sub Items
  • 分割线

你会说还有顶部那个,顶部是headview呀~~

这么分析完成,是不是瞬间觉得没有难度了~

(一)首先布局文件

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
    android:id="@+id/id_drawer_layout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    >

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.Toolbar
            android:id="@+id/id_toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

        <TextView
            android:id="@+id/id_tv_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="HelloWorld"
            android:textSize="30sp"/>
    </RelativeLayout>

    <ListView
        android:id="@+id/id_lv_left_menu"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:paddingTop="0dp"
        android:background="#ffffffff"
        android:clipToPadding="false"
        android:divider="@null"
        android:listSelector="?attr/selectableItemBackground"
        />

</android.support.v4.widget.DrawerLayout>

布局文件上:和上文对比,我们仅仅把NavigationView换成了ListView.

下面注意了,一大波代码来袭.

(二) Activity

public class NavListViewActivity extends ActionBarActivity
{
    private ListView mLvLeftMenu;
    private DrawerLayout mDrawerLayout;


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

        mDrawerLayout = (DrawerLayout) findViewById(R.id.id_drawer_layout);
        mLvLeftMenu = (ListView) findViewById(R.id.id_lv_left_menu);

        Toolbar toolbar = (Toolbar) findViewById(R.id.id_toolbar);
        setSupportActionBar(toolbar);

        final ActionBar ab = getSupportActionBar();
        ab.setHomeAsUpIndicator(R.drawable.ic_menu);
        ab.setDisplayHomeAsUpEnabled(true);

        setUpDrawer();
    }

    private void setUpDrawer()
    {
        LayoutInflater inflater = LayoutInflater.from(this);
        mLvLeftMenu.addHeaderView(inflater.inflate(R.layout.header_just_username, mLvLeftMenu, false));
        mLvLeftMenu.setAdapter(new MenuItemAdapter(this));
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_nav_list_view, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == android.R.id.home)
        {
            mDrawerLayout.openDrawer(GravityCompat.START);
            return true;
        }

        return super.onOptionsItemSelected(item);
    }



}

直接看onCreate中的setUpDrawer(),可以看到我们首先去addHeadView,然后去setAdapter。

那么核心代码就是我们的Adapter了~~

(三)MenuItemAdapter

public class LvMenuItem
    {
        public LvMenuItem(int icon, String name)
        {
            this.icon = icon;
            this.name = name;

            if (icon == NO_ICON && TextUtils.isEmpty(name))
            {
                type = TYPE_SEPARATOR;
            } else if (icon == NO_ICON)
            {
                type = TYPE_NO_ICON;
            } else
            {
                type = TYPE_NORMAL;
            }

            if (type != TYPE_SEPARATOR && TextUtils.isEmpty(name))
            {
                throw new IllegalArgumentException("you need set a name for a non-SEPARATOR item");
            }

            L.e(type + "");


        }

        public LvMenuItem(String name)
        {
            this(NO_ICON, name);
        }

        public LvMenuItem()
        {
            this(null);
        }

        private static final int NO_ICON = 0;
        public static final int TYPE_NORMAL = 0;
        public static final int TYPE_NO_ICON = 1;
        public static final int TYPE_SEPARATOR = 2;

        int type;
        String name;
        int icon;

    }

    public class MenuItemAdapter extends BaseAdapter
    {
        private final int mIconSize;
        private LayoutInflater mInflater;
        private Context mContext;

        public MenuItemAdapter(Context context)
        {
            mInflater = LayoutInflater.from(context);
            mContext = context;

            mIconSize = context.getResources().getDimensionPixelSize(R.dimen.drawer_icon_size);//24dp
        }

        private List<LvMenuItem> mItems = new ArrayList<LvMenuItem>(
                Arrays.asList(
                        new LvMenuItem(R.drawable.ic_dashboard, "Home"),
                        new LvMenuItem(R.drawable.ic_event, "Messages"),
                        new LvMenuItem(R.drawable.ic_headset, "Friends"),
                        new LvMenuItem(R.drawable.ic_forum, "Discussion"),
                        new LvMenuItem(),
                        new LvMenuItem("Sub Items"),
                        new LvMenuItem(R.drawable.ic_dashboard, "Sub Item 1"),
                        new LvMenuItem(R.drawable.ic_forum, "Sub Item 2")
                ));


        @Override
        public int getCount()
        {
            return mItems.size();
        }


        @Override
        public Object getItem(int position)
        {
            return mItems.get(position);
        }


        @Override
        public long getItemId(int position)
        {
            return position;
        }

        @Override
        public int getViewTypeCount()
        {
            return 3;
        }

        @Override
        public int getItemViewType(int position)
        {
            return mItems.get(position).type;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent)
        {
            LvMenuItem item = mItems.get(position);
            switch (item.type)
            {
                case LvMenuItem.TYPE_NORMAL:
                    if (convertView == null)
                    {
                        convertView = mInflater.inflate(R.layout.design_drawer_item, parent,
                                false);
                    }
                    TextView itemView = (TextView) convertView;
                    itemView.setText(item.name);
                    Drawable icon = mContext.getResources().getDrawable(item.icon);
                    setIconColor(icon);
                    if (icon != null)
                    {
                        icon.setBounds(0, 0, mIconSize, mIconSize);
                        TextViewCompat.setCompoundDrawablesRelative(itemView, icon, null, null, null);
                    }

                    break;
                case LvMenuItem.TYPE_NO_ICON:
                    if (convertView == null)
                    {
                        convertView = mInflater.inflate(R.layout.design_drawer_item_subheader,
                                parent, false);
                    }
                    TextView subHeader = (TextView) convertView;
                    subHeader.setText(item.name);
                    break;
                case LvMenuItem.TYPE_SEPARATOR:
                    if (convertView == null)
                    {
                        convertView = mInflater.inflate(R.layout.design_drawer_item_separator,
                                parent, false);
                    }
                    break;
            }

            return convertView;
        }

        public void setIconColor(Drawable icon)
        {
            int textColorSecondary = android.R.attr.textColorSecondary;
            TypedValue value = new TypedValue();
            if (!mContext.getTheme().resolveAttribute(textColorSecondary, value, true))
            {
                return;
            }
            int baseColor = mContext.getResources().getColor(value.resourceId);
            icon.setColorFilter(baseColor, PorterDuff.Mode.MULTIPLY);
        }
    }

首先我们的每个Item对应于一个LvMenuItem,包含icon、name、type,可以看到我们的type分为3类。

那么adapter中代码就很简单了,多Item布局的写法。

这样就完成了,我们自己去书写NavigationView,是不是很简单~~如果你的app需要类似效果,但是又与NavigationView的效果并非一模一样,可以考虑按照上面的思路自己写一个。

最后贴一下用到的Item的布局文件:

  • design_drawer_item_subheader.xml
<?xml version="1.0" encoding="utf-8"?>
<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="?attr/textAppearanceListItem"
          android:textColor="?android:textColorSecondary"/>
  • design_drawer_item_separator.xml

<?xml version="1.0" encoding="utf-8"?>
<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>
  • design_drawer_item,xml:
<?xml version="1.0" encoding="utf-8"?>
<TextView
        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:drawablePadding="32dp"
        android:gravity="center_vertical|start"
        android:maxLines="1"
        android:textAppearance="?attr/textAppearanceListItem"
        android:textColor="?android:attr/textColorPrimary"/>

ok,其实上述ListView的写法也正是NavigationView的源码实现,item的布局文件直接从源码中拖出来的,还是爽爽哒~~

~~ hava a nice day ~~

参考链接

ok~

源码点击下载

微信公众号:hongyangAndroid
(欢迎关注,第一时间推送博文信息)
技术分享

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。