UI抽屉菜单DrawerLayout一测拉菜单

这篇文章是转载的,写的不错,自己好好留着看一下……

[UI]抽屉菜单DrawerLayout分析(一)

侧拉菜单作为常见的导航交互控件,最开始在没有没有android官方控件时,很多时候都是使用开源的SlidingMenu,一直没机会分析侧拉菜单的实现机理,本文将分析android.support.v4.widget.DrawerLayout的使用及实现。

官方介绍

DrawerLayout acts as a top-level container for window content that allows for interactive "drawer" views to be pulled out from the edge of the window.

Drawer positioning and layout is controlled using theandroid:layout_gravityattribute on child views corresponding to which side of the view you want the drawer to emerge from: left or right. (Or start/end on platform versions that support layout direction.)

To use a DrawerLayout,position your primary content view as the first child with a width and height ofmatch_parent. Add drawers as child views after the main content view and set thelayout_gravityappropriately. Drawers commonly usematch_parentfor height with a fixed width.

DrawerLayout.DrawerListenercan be used to monitor the state and motion of drawer views. Avoid performing expensive operations such as layout during animation as it can cause stuttering; try to perform expensive operations during theSTATE_IDLEstate.DrawerLayout.SimpleDrawerListeneroffers default/no-op implementations of each callback method.

As per the Android Design guide,any drawers positioned to the left/start should always contain content for navigating around the application,whereas any drawers positioned to the right/end should always contain actions to take on the current content. This preserves the same navigation left,actions right structure present in the Action Bar and elsewhere

DrawerLayout直译的事抽屉布局的意思,作为视窗内的顶层容器,它允许用户通过抽屉式的推拉操作,从而把视图视窗外边缘拉到屏幕内,如右图:

抽屉菜单的摆放和布局通过android:layout_gravity属性来控制,可选值为left、right或start、end。通过xml来布局的话,需要把DrawerLayout作为父容器,组界面布局作为其第一个子节点,抽屉布局则紧随其后作为第二个子节点,这样就做就已经把内容展示区和抽屉菜单区独立开来,只需要分别非两个区域设置内容即可。android提供了一些实用的监听器,重载相关的回调方法可以在菜单的交互过程中书写逻辑业务。下面是一个demo布局:

<android.support.v4.widget.DrawerLayout

xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/drawer_layout"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context="com.aven.myapplication2.app.MainActivity">

<FrameLayout

android:id="@+id/container"

android:layout_width= android:layout_height="match_parent"/>

<fragmentandroid:id="@+id/navigation_drawer"

android:layout_width="@dimen/navigation_drawer_width"

android:layout_gravity="start"

android:name="com.aven.myapplication2.app.NavigationDrawerFragment"

tools:layout="@layout/fragment_navigation_drawer"/>

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

所以DrawerLayout的使用非常简单,和很多容器类布局一样,它本身也继承自ViewGroup,只是在内部实现中会默认将第一个子节点作为内容区,第二个作为抽屉菜单,所以写布局的事后必须牢记,好在现在的IDE已经非常智能,通过引导来创建Drawerlayout时,会自动生成Activity和xml layout布局,比如使用AndroidStudio就非常方便。

源码分析

DrawerLayout实例化相关辅助类

既然DrawerLayout使用是作为顶层布局layout,那先看看他的构造函数:

public DrawerLayout(Context context,AttributeSet attrs,int defStyle) {
    super(context,attrs,defStyle);
    //根据屏幕分辨率密度计算最小的边距
    final float density = getResources().getDisplayMetrics().density;
    mMinDrawerMargin = (int) (MIN_DRAWER_MARGIN * density + 0.5f);
    final float minVel = MIN_FLING_VELOCITY * density;
    //实例化视图滑动的回调接口,包括左右两边
    mLeftCallback = new ViewDragCallback(Gravity.LEFT);
    mRightCallback = new ViewDragCallback(Gravity.RIGHT);
    //创建滑动手势的的辅助类,负责具体的滑动监听实现
    mLeftDragger = ViewDragHelper.create(this,TOUCH_SLOP_SENSITIVITY,mLeftCallback);
    mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
    mLeftDragger.setMinVelocity(minVel);
    mLeftCallback.setDragger(mLeftDragger);
 
    mRightDragger = ViewDragHelper.create(this,mRightCallback);
    mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
    mRightDragger.setMinVelocity(minVel);
    mRightCallback.setDragger(mRightDragger);
 
    // So that we can catch the back button
    setFocusableInTouchMode(true);
 
    ViewCompat.setAccessibilityDelegate(this,new AccessibilityDelegate());
    ViewGroupCompat.setMotionEventSplittingEnabled(this,false);
}

从构造函数中,我们发现有两个关键的类ViewDragCallback,ViewDragHelper,命名上来看前者和滑动的回调相关,后者和view的滑动操作实现有关,所以先看ViewDragHelper。

ViewDragHelper负责实现drag操作

从它的类注释信息中可以看到,这个helper是个辅助类,里面封装了一些便于用户拖动ViewGroup内子view的操作及状态记录方法。

/**

* ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number

* of useful operations and state tracking for allowing a user to drag and reposition

* views within their parent ViewGroup.

*/

现在来看看这个helper到底是怎么封装的滑动操作,从上面的实例化我们知道这个helper通过工厂方法来构造实例,工厂方法有两个如下:

/**
 * Factory method to create a new ViewDragHelper.
 *
 * @param forParent Parent view to monitor
 * @param cb Callback to provide information and receive events
 * @return a new ViewDragHelper instance
 */
public static ViewDragHelper create(ViewGroup forParent,Callback cb) {
    return new ViewDragHelper(forParent.getContext(),forParent,cb);
}
 
/**
 * Factory method to create a new ViewDragHelper.
 *
 * @param forParent Parent view to monitor
 * @param sensitivity Multiplier for how sensitive the helper should be about detecting
 *                    the start of a drag. Larger values are more sensitive. 1.0f is normal.
 * @param cb Callback to provide information and receive events
 * @return a new ViewDragHelper instance
 */
public static ViewDragHelper create(ViewGroup forParent,float sensitivity,Callback cb) {
    final ViewDragHelper helper = create(forParent,cb);
    helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
    return helper;
}


这第二个工厂方法create就是刚才看到的上层调用来创建helper实例的,我们传入了一个viewgroup,也就是说helper将持有我们的DrawerLayout实例引用,第二是一个浮点数,和drag操作的敏感性相关,数值越大表示drag操作更易被监听,最后是一个Callback,即ViewDragCallback实例,它本身继承自ViewDragHelper.Callback,现在来看helper的构造方法:

/**
 * Apps should use ViewDragHelper.create() to get a new instance.
 * This will allow VDH to use internal compatibility implementations for different
 * platform versions.
 *
 * @param context Context to initialize config-dependent params from
 * @param forParent Parent view to monitor
 */
private ViewDragHelper(Context context,ViewGroup forParent,Callback cb) {
    if (forParent == null) {
        throw new IllegalArgumentException("Parent view may not be null");
    }
    if (cb == null) {
        throw new IllegalArgumentException("Callback may not be null");
    }
 
    mParentView = forParent;
    mCallback = cb;
 
    final ViewConfiguration vc = ViewConfiguration.get(context);
    finalfloat density = context.getResources().getDisplayMetrics().density;
    mEdgeSize = (int) (EDGE_SIZE * density + 0.5f);
 
    mTouchSlop = vc.getScaledTouchSlop();
    mMaxVelocity = vc.getScaledMaximumFlingVelocity();
    mMinVelocity = vc.getScaledMinimumFlingVelocity();
    mScroller = ScrollerCompat.create(context,sInterpolator);
}

首先需要检测我们传入的DrawerLayout和回调Callback,不允许为空。接下来从ViewConfiguration中获取一些view的默认配置,

vc.getScaledTouchSlop是获取一个pix为单位的距离,代表view在滑动的值;

.getScaledMaximumFlingVelocity获取触发view fling的最大每秒滚动的距离,也是pix为单位;

获取view fling的最小每秒滚动距离,同样pix为单位;

这里有scroll和fling,我的理解是scroll表示手没有离开屏幕产生的滑动效果,二fling则是用力一划,然后view自己开始滚动的效果。

最后实例化一个Scroller,这是专门用来处理滚动的一个类,这里用的是扩展包里的campact类做版本兼容。

到此DrawerLayout已经准备好所有资源,接下来就是手势分发时候的各种调用,这一部分留到下一篇文章在做分析

Source:

git clone https://github.com/avenwu/DrawerDemo.git

作者: 小文字
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
原文链接:http://www.cnblogs.com/avenwu/archive/2014/04/16/3669367.html
感谢作者 ,下一篇会继续分析——

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


php输出xml格式字符串
J2ME Mobile 3D入门教程系列文章之一
XML轻松学习手册
XML入门的常见问题(一)
XML入门的常见问题(三)
XML轻松学习手册(2)XML概念
xml文件介绍及使用
xml编程(一)-xml语法
XML文件结构和基本语法
第2章 包装类
XML入门的常见问题(二)
Java对象的强、软、弱和虚引用
JS解析XML文件和XML字符串详解
java中枚举的详细使用介绍
了解Xml格式
XML入门的常见问题(四)
深入SQLite多线程的使用总结详解
PlayFramework完整实现一个APP(一)
XML和YAML的使用方法
XML轻松学习总节篇