InstaMaterial 概念设计(第七部分) - 抽屉导航(Navigation Drawer)

英文原文:http://frogermcs.github.io/InstaMaterial-concept-part-7-navigation-drawer/

转载译文须注明出处。前面的几部分也都翻译出来了,文章底部相关文章中可以找到,也可以在本站直接搜索InstaMaterial 。

介绍

抽屉导航(Navigation Drawer) 是安卓中非常有名的一种设计模式(这不是java中的设计模式哈)。它是一个从屏幕左侧边缘滑出的一个显示app主菜单的面板。有时候它是从右侧滑入的,不过,除非你有充分的理由,否则别那么做,那是一种愚蠢的设计。

Navigation Drawer这种设计的相关文档非常丰富(不管是设计方面的还是编程方面的)。如果你想深入了解设计准则只需查看Navigation Drawer 设计准则,我们今天也不会谈论太多关于如何实现Drawer的事情,相反,我推荐你直接阅读官方文档 创建一个Navigation Drawer

在本篇文章中,不是拷贝官方文档,而是准备一个DrawerLayoutInstaller - 一个帮助我们配置并将DrawerLayout注入到Activity中的工具,可以省去每次都要弄繁琐的xml文件。

实现Navigation Drawer

======================

准备

Before we start, let’s make some preparation by adding resources, and less meaning boilerplate.

在我们开始之前,让我们先做一些资源文件上的准备

首先添加app的menu中需要用到的所有icon:

New resources

每一个都长得像概念视频中的样子,但其实他们都是从谷歌的Material 设计图标 中获取的。

我们的菜单是基于ListView实现的,因此我们需要为list的item准备好布局。下面是三种不同的布局:

  • 带有用户头像与昵称的头部
    Menu header

  • 菜单按钮
    Menu item

  • 分割线(用在菜单按钮之间)

<?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="#dddddd" />
</FrameLayout>

这里是 带有所有新源码的一个完整提交

GlobalMenuView

现在我们来为全局菜单准备布局。总的来说他就是个简单的ListView,我们本可以把它放在xml中,但是考虑到我们希望将这个view注入(嵌入)到更多的Activity中,还是用纯代码更好些。

这个View的需求很简单 - 只要显示菜单项与用户的简单介绍就可以了。因此实现并不是太复杂:

GlobalMenuView.java

public class GlobalMenuView extends ListView implements View.OnClickListener {
 
    private OnHeaderClickListener onHeaderClickListener;
    private GlobalMenuAdapter globalMenuAdapter;
    private ImageView ivUserProfilePhoto;
    private int avatarSize;
    private String profilePhoto;
 
    public GlobalMenuView(Context context) {
        super(context);
        init();
    }
 
    private void init() {
        setChoiceMode(CHOICE_MODE_SINGLE);
        setDivider(getResources().getDrawable(android.R.color.transparent));
        setDividerHeight(0);
        setBackgroundColor(Color.WHITE);
 
        setupHeader();
        setupAdapter();
    }
 
    private void setupAdapter() {
        globalMenuAdapter = new GlobalMenuAdapter(getContext());
        setAdapter(globalMenuAdapter);
    }
 
    private void setupHeader() {
        this.avatarSize = getResources().getDimensionPixelSize(R.dimen.global_menu_avatar_size);
        this.profilePhoto = getResources().getString(R.string.user_profile_photo);
 
        setHeaderDividersEnabled(true);
        View vHeader = LayoutInflater.from(getContext()).inflate(R.layout.view_global_menu_header, null);
        ivUserProfilePhoto = (ImageView) vHeader.findViewById(R.id.ivUserProfilePhoto);
        Picasso.with(getContext())
                .load(profilePhoto)
                .placeholder(R.drawable.img_circle_placeholder)
                .resize(avatarSize, avatarSize)
                .centerCrop()
                .transform(new CircleTransformation())
                .into(ivUserProfilePhoto);
        addHeaderView(vHeader);
        vHeader.setOnClickListener(this);
    }
 
    @Override
    public void onClick(View v) {
        if (onHeaderClickListener != null) {
            onHeaderClickListener.onGlobalMenuHeaderClick(v);
        }
    }
 
    public interface OnHeaderClickListener {
        public void onGlobalMenuHeaderClick(View v);
    }
 
    public void setOnHeaderClickListener(OnHeaderClickListener onHeaderClickListener) {
        this.onHeaderClickListener = onHeaderClickListener;
    }
}

正如你看到的,用户介绍的view作为ListView的头部被使用。这就是为什么需要添加一个自定义的监听者来处理onClick。其余的都很简单。

GlobalMenuAdapter更简单,因此就不粘贴代码在这里了。不过,你还是可以点这里:commit with GlobalMenuView implementation

Navigation Drawer

是建立Navigation Drawer的时候了,首先准备DrawerLayout的根View布局:

drawer_root.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/drawerLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
 
    <FrameLayout
        android:id="@+id/vContentFrame"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
 
    <FrameLayout
        android:id="@+id/vLeftDrawer"
        android:layout_width="280dp"
        android:layout_height="match_parent"
        android:layout_gravity="start" />
</android.support.v4.widget.DrawerLayout>

这个基本的布局包含了自定义的根view(在vContentFrame 元素中)以及左边的drawer(vLeftDrawer 元素)。

DrawerLayoutInstaller

现在该来准备将DrawerLayout注入到Activit布局树中的工具了。未来很可能要对DrawerLayoutInstaller做一些改进,但是目前的需求很简单:

1.在不接触xml文件的情况下将DrawerLayout注入到已有的Activity中。

2.可以自定义DrawerLayout的xml根view以及左item的view。

3.一些自定义(左item的宽度,自定义打开/关闭drawer的toggle)

The first point is done with three simple steps:

第一点通过三个简单的步骤完成:

  1. 得到Activity的根View(使用activity.findViewById(android.R.id.content)获得)同时将它的唯一子view拿掉

  2. 将抽屉布局作为孩子放入到根View中

  3. 将从根View中拿掉的子View放到抽屉的content view中

实现:

DrawerLayoutInstaller_drawerinject.java

private void addDrawerToActivity(DrawerLayout drawerLayout) {
    ViewGroup rootView = (ViewGroup) activity.findViewById(android.R.id.content);
    ViewGroup drawerContentRoot = (ViewGroup) drawerLayout.getChildAt(0);
    View contentView = rootView.getChildAt(0);
 
    rootView.removeView(contentView);
 
    drawerContentRoot.addView(contentView, new ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT
    ));
 
    rootView.addView(drawerLayout, new ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT
    ));
}

其余的代码你可以在这里找到DrawerLayoutInstaller 的实现

我们终于可以将所有这些东西放在一起了,首先在BaseActivity中我们需要构建我们的GlobalMenuView ,然后将它放在DrawerLayout中。然后将所有东西注入到Activity布局中:

BaseActivity_setupDrawer.java

private void setupDrawer() {
    GlobalMenuView menuView = new GlobalMenuView(this);
    menuView.setOnHeaderClickListener(this);
 
    drawerLayout = DrawerLayoutInstaller.from(this)
            .drawerRoot(R.layout.drawer_root)
            .drawerLeftView(menuView)
            .drawerLeftWidth(Utils.dpToPx(300))
            .withNavigationIconToggler(getToolbar())
            .build();
}

最后一件事 - 实现打开ProfileAcitivty的onGlobalMenuHeaderClick()方法:

BaseActivity_globalMenuHeaderClick.java

@Override
public void onGlobalMenuHeaderClick(final View v) {
    drawerLayout.closeDrawer(Gravity.START);
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            int\[\] startingLocation = new int\[2\];
            v.getLocationOnScreen(startingLocation);
            startingLocation\[0\] += v.getWidth() / 2;
            UserProfileActivity.startUserProfileFromLocation(startingLocation, BaseActivity.this);
            overridePendingTransition(0, 0);
        }
    }, 200);
}

 这里handler将打开动作延迟到DrawerLayout关闭之后。下面是最终的效果:

Navigation Drawer

这就是今天的全部内容了,简单吧,谢谢阅读!

源码

项目的完整代码: repository

作者: Miroslaw Stanek

来自:InstaMaterial概念设计