安卓Android应用开发那些事Android系统方面那些事

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

2019-10-28  本文已影响0人  NoBugException

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构造方法上来看,得到两个重要的知识:

(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构造方法的工作有:

【setEdgeTrackingEnabled()方法】

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

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

【setMinVelocity()方法】

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

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

【setDragger()方法】

    mRightCallback.setDragger(mRightDragger);

将callback和ViewDragHelper绑定。

总结:

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

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

[本章完...]

上一篇下一篇

猜你喜欢

热点阅读