抽屉菜单DrawerLayout分析

     
侧拉菜单作为大规模的领航交互控件,最开首在一向不未有android官方控件时,很多时候都以行使开源的SlidingMenu,一直没机会分析侧拉菜单的兑现机理,本文将分析android.support.v4.widget.DrawerLayout的利用及达成。

后续分析DrawerLayout的手势分发部分

图片 1

聊起手势分发,那本身正是个好话题,DrawerLayout作为后续自ViewGroup得布局他能够阻挡手势也能够分发给子view,也等于在onInterceptTouch伊夫nt中做的操作,不过她的底下还有2个onTouch伊夫nt方法,先看哪个吧?追溯代码大家能够知道ViewGroup继承自View,而onTouch伊夫nt是View的法子

    官方介绍

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
the android:layout_gravity attribute 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 of match_parent. Add drawers as child
views after the main content view and set
the layout_gravity appropriately. Drawers commonly
use match_parent for height with a fixed width.

DrawerLayout.DrawerListener can 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
the STATE_IDLE state. DrawerLayout.SimpleDrawerListener offers
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

    style=”color: #8f9f36;”>xmlns:android=”http://schemas.android.com/apk/res/android

    style=”color: #8f9f36;”>xmlns:tools=”http://schemas.android.com/tools

    style=”color: #8f9f36;”>android:id=”@+id/drawer_layout”

    android:layout_width= style=”color: #c8352b;”>”match_parent”

    android:layout_height= style=”color: #c8352b;”>”match_parent”

    style=”color: #8f9f36;”>tools:context=”com.aven.myapplication2.app.MainActivity” style=”color: #008f00;”>>

 

    <FrameLayout

        style=”color: #8f9f36;”>android:id=”@+id/container”

       
android:layout_width= style=”color: #c8352b;”>”match_parent”

       
android:layout_height= style=”color: #c8352b;”>”match_parent” style=”color: #008f00;”>/>

 

    style=”color: #008f00;”><fragment style=”color: #8f9f36;”>android:id=”@+id/navigation_drawer”

        style=”color: #8f9f36;”>android:layout_width=”@dimen/navigation_drawer_width”

       
android:layout_height= style=”color: #c8352b;”>”match_parent”

       
android:layout_gravity= style=”color: #c8352b;”>”start”

        style=”color: #8f9f36;”>android:name=”com.aven.myapplication2.app.NavigationDrawerFragment”

        style=”color: #8f9f36;”>tools:layout=”@layout/fragment_navigation_drawer” style=”color: #008f00;”>/>

 

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

 

 

从而DrawerLayout的使用非凡不难,和广大容器类布局一样,它本人也继承自ViewGroup,只是在在那之中贯彻中会私下认可将首先个子节点作为内容区,第四个作为抽屉菜单,所以写布局的事后必须铭记,万幸到现在的IDE已经十一分智能,通过带领来成立Drawerlayout时,会自动生成Activity和xml
layout布局,比如选择AndroidStudio就11分有利于。

 

图片 2

源码分析

作者们照旧先花点时间把贰者的涉嫌先承认再持续。

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, TOUCH_SLOP_SENSITIVITY, 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。

 

onInterceptTouchEvent和onTouchEvent—鸡和蛋?

固定到ViewGroup,能够发现onInterceptTouch伊夫nt分定义如下,从它前边壹段相当短的证明就能够看出其主要和复杂性,暗中认可的回到是false

/\**

* * Implement this method to intercept all touch screen motion
events.  This*

* * allows you to watch events as they are dispatched to your
children, and*

* * take ownership of the current gesture at any point.*

* **

* * <p>Using this function takes some care, as it has a fairly
complicated*

* * interaction with {@link View#onTouchEvent(MotionEvent)*

* * View.onTouchEvent(MotionEvent)}, and using it requires
implementing*

* * that method as well as this one in the correct way.  Events will
be*

* * received in the following order:*

* **

* * <ol>*

* * <li> You will receive the down event here.*

* * <li> The down event will be handled either by a child of
this view*

* * group, or given to your own onTouchEvent() method to handle; this
means*

* * you should implement onTouchEvent() to return true, so you will*

* * continue to see the rest of the gesture (instead of looking for*

* * a parent view to handle it).  Also, by returning true from*

* * onTouchEvent(), you will not receive any following*

* * events in onInterceptTouchEvent() and all touch processing must*

* * happen in onTouchEvent() like normal.*

* * <li> For as long as you return false from this function,
each following*

* * event (up to and including the final up) will be delivered first
here*

* * and then to the target’s onTouchEvent().*

* * <li> If you return true from here, you will not receive any*

* * following events: the target view will receive the same event but*

* * with the action {@link MotionEvent#ACTION_CANCEL}, and all
further*

* * events will be delivered to your onTouchEvent() method and no
longer*

* * appear here.*

* * </ol>*

* **

* * @param ev The motion event being dispatched down the hierarchy.*

* * @return Return true to steal motion events from the children and
have*

* * them dispatched to this ViewGroup through onTouchEvent().*

* * The current target will receive an ACTION_CANCEL event, and no
further*

* * messages will be delivered here.*

* */*

public boolean onInterceptTouchEvent(MotionEvent ev) {

    return false;

}

 

前两段告诉大家,复写onInterceptTouch伊芙nt方法,能够完毕监听全体的动作事件Motion伊芙nt,在向子view传递事件前做大家须要的操作,当然那指的是和这么些viewgroup相关的事件;同时大家须要慎重处理该函数,因为她和onTouch伊夫nt关系十一分紧凑,下边是事件接受的逐一:

第贰接受的的事按下事件,down事件,他能够被view处理也足以在自小编的onTouch伊夫nt里处理,所以达成onTouch伊芙nt并且再次回到true,那样onTouch伊夫nt继续才能接到down之后的任何事件,同时onInterceptTouch伊芙nt不会在接到后续事件,因为已经更换成onTouch伊夫nt处理了。

那正是说什么样时候onInterceptTouch伊夫nt会把后续事件转换来她的onTouch伊夫nt呢?那取决于onInterceptTouch伊芙nt的再次回到值,假诺回到false,全体事件都会先分发到此地,然后再到指标view的onTouch伊芙nt;相反固然回去true,那么onInterceptTouch伊芙nt将不再接收后续事件,并且目的view会收到cancel事件,接着自个儿的onTouch伊夫nt几首后续的轩然大波。

那其实从名字来看是相比较好理解的onInterceptTouch伊芙nt表示在截取触摸事件的被调用的法子,既然是截取就能够直接吧事件截下来后不复今后传递,那是正是上边的第一种情景,重临true,即大家丹舟共济消耗了触摸事件,子view将从未机会获取晋升。

图片 3

差不离意思就是假如期望本身消耗掉改事件就能够间接回到true,那或多或少和onTouch伊夫nt的回到类似目标。

天涯论坛有篇小说对这个事件分发做了很好的分析:http://www.cnblogs.com/sunzn/archive/2013/05/10/3064129.html

详尽的阐发了了dispatchTouch伊夫nt,onInterceptTouch伊夫nt以及onTouch伊芙nt之间的涉及

最近大家回过头来看DrawerLayout里的分发是怎么着写的:

重写了后头五个章程,先看onInterceptTouch伊芙nt:

@Override

public style=”color: #c01e51;”>boolean onInterceptTouchEvent style=”color: #797979;”>(MotionEvent ev style=”color: #797979;”>) style=”color: #797979;”>{

    final style=”color: #c01e51;”>int action style=”color: #797979;”>= MotionEventCompat style=”color: #797979;”>. style=”color: #8f9f36;”>getActionMasked style=”color: #797979;”>(ev style=”color: #797979;”>);

 

    // “|” used deliberately
here; both methods should be invoked.

    final style=”color: #c01e51;”>boolean interceptForDrag style=”color: #797979;”>= mLeftDragger style=”color: #797979;”>. style=”color: #8f9f36;”>shouldInterceptTouchEvent style=”color: #797979;”>(ev style=”color: #797979;”>) style=”color: #797979;”>|

            mRightDragger. style=”color: #8f9f36;”>shouldInterceptTouchEvent style=”color: #797979;”>(ev style=”color: #797979;”>);

 

    boolean interceptForTap style=”color: #797979;”>= style=”color: #008f00;”>false style=”color: #797979;”>;

 

    switch style=”color: #797979;”>(action style=”color: #797979;”>) style=”color: #797979;”>{

        case MotionEvent style=”color: #797979;”>. style=”color: #8f9f36;”>ACTION_DOWN style=”color: #797979;”>: style=”color: #797979;”>{

            final style=”color: #c01e51;”>float x style=”color: #797979;”>= ev style=”color: #797979;”>. style=”color: #8f9f36;”>getX style=”color: #797979;”>();

            final style=”color: #c01e51;”>float y style=”color: #797979;”>= ev style=”color: #797979;”>. style=”color: #8f9f36;”>getY style=”color: #797979;”>();

            mInitialMotionX =
x;

            mInitialMotionY =
y;

            if style=”color: #797979;”>(mScrimOpacity style=”color: #797979;”>> style=”color: #797979;”>0 style=”color: #797979;”>&&

                    isContentView style=”color: #797979;”>(mLeftDragger style=”color: #797979;”>. style=”color: #8f9f36;”>findTopChildUnder style=”color: #797979;”>(( style=”color: #c01e51;”>int style=”color: #797979;”>) x style=”color: #797979;”>, style=”color: #797979;”>( style=”color: #c01e51;”>int style=”color: #797979;”>) y style=”color: #797979;”>))) style=”color: #797979;”>{

                interceptForTap =
true style=”color: #797979;”>;

            }

            mDisallowInterceptRequested style=”color: #797979;”>= style=”color: #008f00;”>false style=”color: #797979;”>;

            mChildrenCanceledTouch style=”color: #797979;”>= style=”color: #008f00;”>false style=”color: #797979;”>;

            break style=”color: #797979;”>;

        }

 

        case MotionEvent style=”color: #797979;”>. style=”color: #8f9f36;”>ACTION_MOVE style=”color: #797979;”>: style=”color: #797979;”>{

            // If we cross the
touch slop, don’t perform the delayed peek for an edge touch.

            if style=”color: #797979;”>(mLeftDragger style=”color: #797979;”>. style=”color: #8f9f36;”>checkTouchSlop style=”color: #797979;”>(ViewDragHelper style=”color: #797979;”>. style=”color: #8f9f36;”>DIRECTION_ALL style=”color: #797979;”>)) style=”color: #797979;”>{

                mLeftCallback style=”color: #797979;”>. style=”color: #8f9f36;”>removeCallbacks style=”color: #797979;”>();

                mRightCallback style=”color: #797979;”>. style=”color: #8f9f36;”>removeCallbacks style=”color: #797979;”>();

            }

            break style=”color: #797979;”>;

        }

 

        case MotionEvent style=”color: #797979;”>. style=”color: #8f9f36;”>ACTION_CANCEL style=”color: #797979;”>:

        case MotionEvent style=”color: #797979;”>. style=”color: #8f9f36;”>ACTION_UP style=”color: #797979;”>: style=”color: #797979;”>{

            closeDrawers( style=”color: #008f00;”>true style=”color: #797979;”>);

            mDisallowInterceptRequested style=”color: #797979;”>= style=”color: #008f00;”>false style=”color: #797979;”>;

            mChildrenCanceledTouch style=”color: #797979;”>= style=”color: #008f00;”>false style=”color: #797979;”>;

        }

    }

 

    return interceptForDrag
|| interceptForTap style=”color: #797979;”>|| hasPeekingDrawer style=”color: #797979;”>() style=”color: #797979;”>|| mChildrenCanceledTouch style=”color: #797979;”>;

}

 

一.第二从touch
event里面获取当前具体的action动作,Motion伊芙ntCompat.getActionMasked(ev),内部实际上做了一回按位于操作event.getAction() &
ACTION_MASK;

二.反省当前是不是满意截取drag状态,用于决定onInterceptTouch伊芙nt重回值,那里有个注解说是故意用了|或,而不是||或,两者分别在于||只要第1个原则满足就不在执行第2个反省,贰|区别,无论怎么着都会将三个标准化检查一回;

三.接下来是多少个case,依照当前的action做拍卖;

style=”font-family: Courier; font-size: 12px; color: #797979;”> style=”font-size: 12px;”> style=”color: #020202;”>ACTION_DOWN,当按下时 style=”color: #020202; font-family: Courier; font-size: 1二px;”>记录按下点的x,y坐标值,依据条件设置当前是或不是满意tap状态,具体条件有八个,1是 style=”font-family: Courier; font-size: 1二px;”>mScrimOpacity,表示子view中在显示器上占有的最小幅度面(0-一),二时依照坐标点的职责获取改点对应的最上层view对象,假若是预约义的content
view即DrawerLayout里的主内容突显view,约等于同时知足view在显示器上且点击的岗位一贯落在了content
view上。

style=”color: #020202; font-family: Courier;”>ACTION_MOVE,当手按下后伊始在显示屏上运动时,如果垂直和水准上的位移差量达到了drag
helper的阀值则一处左右两边的回调接口

style=”color: #020202; font-family: Courier;”>ACTION_CANCLE和ACTION_UP,手势甘休后,关闭菜单

最终结合多少个状态来十三分来控制onInterceptTouch伊芙nt重回true依然false,

未完待续

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实例的,我们传入了1个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在滑行的值;

vc.getScaled马克西姆umFlingVelocity获取触发view
fling的最大每秒滚动的离开,也是pix为单位;

获得view fling的小不点儿每秒滚动距离,同样pix为单位;

那里有scroll和fling,作者的知情是scroll表示手未有偏离荧屏产生的滑动作效果果,贰fling则是竭力1划,然后view自身开端滚动的法力。

最终实例化三个Scroller,那是专门用来处理滚动的贰个类,那里用的是增加包里的campact类做版本包容。

到此DrawerLayout已经准备好全数财富,接下去正是手势分发时候的各类调用,这一有的留到下一篇文章在做分析

 

Source:

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