侧滑效果[第二篇]:DrawerLayout源码分析

DrawerLayout是Google官方提供的侧滑栏控件,它的侧滑效果如果用在其它View上会有优化Android UI的效果,使APP更加友好,此时此刻,我们需要研究源码,分析侧滑效果是如何实现的?

(1)为什么看源码?

看源码的初衷是为了达到一种目的,而不需要每一行都研究。

Android自带的抽屉布局具有侧滑效果,所以本篇文章分析源码的目的在于:研究侧滑效果是怎么实现的?

抱着这样的目的我们开始研究源码。

(2)DrawerLayout初始化分析

初始化分析,即对DrawerLayout构造方法分析。

DrawerLayout布局都在放在xml文件中的,当加载这个布局时,首先执行的是DrawerLayout具有两个参数的构造方法,源码如下:

public MyDrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
    this(context, attrs, 0);
}

以上代码就是DrawerLayout首先要执行的方法,继续跟踪下级构造方法,如下:

public DrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    //用于控制child View获取焦点的能力
    //FOCUS_BEFORE_DESCENDANTS:ViewGroup本身先对焦点进行处理,如果没有处理则分发给child View进行处理
    //FOCUS_AFTER_DESCENDANTS:先分发给Child View进行处理,如果所有的Child View都没有处理,则自己再处理
    //FOCUS_BLOCK_DESCENDANTS:ViewGroup本身进行处理,不管是否处理成功,都不会分发给ChildView进行处理
    setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
    //获取像素密度倍数
    final float density = getResources().getDisplayMetrics().density;
    //最小margin值,默认是64dp,将dp转成pa发关闭抽屉或打开抽屉
    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);
    //设置DrawerLayout边的方向
    mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
    //最小滑动速度,超过这个距离才能触发关闭抽屉或打开抽屉
    mLeftDragger.setMinVelocity(minVel);
    mLeftCallback.setDragger(mLeftDragger);

    //创建右拖拽帮助类,是用于编写自定义视图组的实用程序类。它提供了许多有用的操作和状态跟踪,允许用户在其父视图组中拖动和重新定位视图。
    mRightDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mRightCallback);
    //设置DrawerLayout边的方向
    mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
    //最小滑动速度,超过这个距离才能触发关闭抽屉或打开抽屉
    mRightDragger.setMinVelocity(minVel);
    mRightCallback.setDragger(mRightDragger);

    // So that we can catch the back button:这样我们就可以抓住后退按钮
    setFocusableInTouchMode(true);

    //设置当前视图可访问性的重要性。如果很重要,视图将触发可访问性事件并报告给查询屏幕的可访问性服务。
    ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
    //设置通过组合实现可访问性支持的委托
    ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate());
    //禁止多点触摸传递到子View
    setMotionEventSplittingEnabled(false);

    //ViewCompat.getFitsSystemWindows(this)相当于android:fitsSystemWindows="true"
    if (ViewCompat.getFitsSystemWindows(this)) {
        if (Build.VERSION.SDK_INT >= 21) {
            setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
                @Override
                public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) {
                    final MyDrawerLayout drawerLayout = (MyDrawerLayout) view;
                    drawerLayout.setChildInsets(insets, insets.getSystemWindowInsetTop() > 0);
                    return insets.consumeSystemWindowInsets();
                }
            });
            setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
            final TypedArray a = context.obtainStyledAttributes(THEME_ATTRS);
            try {
                mStatusBarBackground = a.getDrawable(0);
            } finally {
                a.recycle();
            }
        } else {
            mStatusBarBackground = null;
        }
    }
    mDrawerElevation = DRAWER_ELEVATION * density;

    mNonDrawerViews = new ArrayList<View>();
}

我在以上源码中添加了一些注释,感觉没必要加那么多,看源码只需找出核心代码即可。

核心代码如下:

    //左拖拽监听
    mLeftCallback = new ViewDragCallback(Gravity.LEFT);
    //右拖拽监听
    mRightCallback = new ViewDragCallback(Gravity.RIGHT);
    //创建左拖拽帮助类,是用于编写自定义视图组的实用程序类,它提供了许多有用的操作和状态跟踪,允许用户在其父视图组中拖动和重新定位视图。
    mLeftDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mLeftCallback);
    //设置DrawerLayout边的方向
    mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
    //最小滑动速度,超过这个距离才能触发关闭抽屉或打开抽屉
    mLeftDragger.setMinVelocity(minVel);
    mLeftCallback.setDragger(mLeftDragger);

    //创建右拖拽帮助类,是用于编写自定义视图组的实用程序类。它提供了许多有用的操作和状态跟踪,允许用户在其父视图组中拖动和重新定位视图。
    mRightDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mRightCallback);
    //设置DrawerLayout边的方向
    mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
    //最小滑动速度,超过这个距离才能触发关闭抽屉或打开抽屉
    mRightDragger.setMinVelocity(minVel);
    mRightCallback.setDragger(mRightDragger);

代码中定义了抽屉布局的左滑和右滑的监听,并且创建了ViewDragHelper对象来操作抽屉布局的视图。

所以,从DrawerLayout构造方法上来看,得到两个重要的知识:

  • DrawerLayout设置了左滑和右滑的监听,也就是说,不支持上下侧滑;
  • DrawerLayout视图的操作都封装在了一个帮助类里面:ViewDragHelper
(3)ViewDragHelper类分析

ViewDragHelper类总共1500多行代码,每一行都分析的话也不太现实,所以这里只分析关键方法,在DrawerLayout构造方法里用到了以下代码:

//创建右拖拽帮助类,是用于编写自定义视图组的实用程序类。它提供了许多有用的操作和状态跟踪,允许用户在其父视图组中拖动和重新定位视图。
mRightDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mRightCallback);
//设置DrawerLayout边的方向
mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
//最小滑动速度,超过这个距离才能触发关闭抽屉或打开抽屉
mRightDragger.setMinVelocity(minVel);
mRightCallback.setDragger(mRightDragger);

所以,只需要分析create()setEdgeTrackingEnabled()setMinVelocity()setDragger()这些方法即可。

【create()方法】

/**
 * 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(@NonNull ViewGroup forParent, float sensitivity,
                                    @NonNull Callback cb) {
    final ViewDragHelper helper = create(forParent, cb);
    helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
    return helper;
}

sensitivity:辅助对象对检测拖动开始的敏感程度的乘数。较大的值更敏感。

sensitivity变量在create 方法中经过计算之后被赋值到helper.mTouchSlop变量,helper.mTouchSlopmTouchSlop变量是系统滑动距离的最小值,大于该值可以认为正式进入滑动状态。

create方法中还有一个create方法, 该方法返回一个ViewDragHelper对象,源码如下:

/**
 * 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(@NonNull ViewGroup forParent, @NonNull Callback cb) {
    return new ViewDragHelper(forParent.getContext(), forParent, cb);
}

最终执行的到ViewDragHelper类的构造方法

/**
 * 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(@NonNull Context context, @NonNull ViewGroup forParent,
                       @NonNull 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);
    final float density = context.getResources().getDisplayMetrics().density;

    //设置边的大小
    mEdgeSize = (int) (EDGE_SIZE * density + 0.5f);

    //获取系统滑动距离的最小值,大于该值可以认为滑动
    mTouchSlop = vc.getScaledTouchSlop();

    // 获得允许执行fling (抛)的最大速度值
    mMaxVelocity = vc.getScaledMaximumFlingVelocity();

    // 获得允许执行fling (抛)的最小速度值
    mMinVelocity = vc.getScaledMinimumFlingVelocity();

    //创建滚动类
    mScroller = new OverScroller(context, sInterpolator);
}

ViewDragHelper构造方法的工作有:

  • mEdgeSize:获取边的大小,当长按屏幕左边或者右边时,会出现一个边界,这个边界的宽度就是边的大小;

  • mTouchSlop:获取系统滑动距离的最小值,如果滑动距离小于这个值则不会触发抽屉布局的侧滑,只有滑动距离大于这个值时才会触发抽屉侧滑效果;

  • mMaxVelocity:获得允许执行fling (抛)的最大速度值

  • mMinVelocity:获得允许执行fling (抛)的最小速度值

  • mScroller:创建滚动类OverScroller,抽屉布局的滚动其实是由OverScroller实现的;

【setEdgeTrackingEnabled()方法】

    //设置DrawerLayout边的方向
    mLeftDragger.setEdgeTrackingEnabled(MyViewDragHelper.EDGE_LEFT);

这个方法可以设置抽屉布局的方向,抽屉布局只支持左、右两个方向。

【setMinVelocity()方法】

    //最小滑动速度,超过这个距离才能触发关闭抽屉或打开抽屉
    mRightDragger.setMinVelocity(minVel);

设置虽小滑动向量,可以理解为滑动速度。

【setDragger()方法】

    mRightCallback.setDragger(mRightDragger);

将callback和ViewDragHelper绑定。

总结:

源码分析到这里,其实已经差不多了,DrawerLayout抽屉侧滑效果的组成是:

  • DrawerLayout:抽屉布局
  • ViewDragHelper:抽屉被拖拽的帮助类
  • ViewDragCallback:左滑或右滑监听
  • OverScroller:滚动实现

有关OverScroller的知识可以查看这篇博客Android OverScroller分析,大家可以看下我写的这篇文章。

[本章完...]

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,976评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,249评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,449评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,433评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,460评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,132评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,721评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,641评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,180评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,267评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,408评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,076评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,767评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,255评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,386评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,764评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,413评论 2 358

推荐阅读更多精彩内容