侧滑效果[第二篇]: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.mTouchSlop
,mTouchSlop
变量是系统滑动距离的最小值,大于该值可以认为正式进入滑动状态。
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分析,大家可以看下我写的这篇文章。
[本章完...]