图解系列之SwipeRefreshLayout源码分析(一)
- 更多分享:www.catbro.cn
-
SwipeRefreshLayout是google官方提供的下拉刷新控制,其已非侵入式的方式运转,深受广大开发者的喜爱。
-
SwipeRefreshLayout的实现方式为二合一的方式,为什么是二合一呢?因为从兼容性方面考虑,google通过传统的方式即重写onTouchEvent方法来实现滑动的拦截处理操作,但CoordinatorLayout又是开发中常用的利器,而配合其使用需实现内部嵌套接口,所以SwipeRefreshLayout还实现了NestedScrollingChild和NestedScrollingParent接口
-
同时,SwipeRefreshLayout还重写了onMesure和onLayout来做自己的测量和布局
-
下面我们将通过源码结合图解梳理的方式去学习SwipeRefreshLayout的设计思想,将重点从以下几点进行分析
- 构造方法;
- onMeasure();
- onLayout();
- onIntercepterTouchEvent();(重点)
- onTouchEvent();(重点)
- 嵌套滑动机制(重点)
-
该知识点将实行三步走的战略,分点分类将其消灭,共分三篇进行梳理
-
第一篇为测量布局篇
-
第二篇为传统实现篇
-
第三篇为嵌套滚动实现篇
构造函数
-
构造函数源码 public SwipeRefreshLayout(Context context, AttributeSet attrs) { super(context, attrs);
//触发移动事件的最小距离,自定义View处理touch事件的时候, //有的时候需要判断用户是否真的存在movie,系统提供了这样的方法。 //表示滑动的时候,手的移动要大于这个返回的距离值才开始移动控件。 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); //动画的执行时长 mMediumAnimationDuration = getResources().getInteger( android.R.integer.config_mediumAnimTime); //一般如果自定义view不需要在onDraw绘制自己的东西时,可以不理会 setWillNotDraw(false); //获取动画的插值器 mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR); //获取设备显示器的指标数据,后面计算需要 final DisplayMetrics metrics = getResources().getDisplayMetrics(); //刷新圆的直径,默认是40 mCircleDiameter = (int) (CIRCLE_DIAMETER * metrics.density); //创建一个圆形进度View,下拉时看到的那个就是了 createProgressView(); //设置其child的绘制是可以排序的,因为SwipeRefreshLayout总是将下拉的圆形最后再绘制, //这样就能一直保证绘制其在最上层可见了 ViewCompat.setChildrenDrawingOrderEnabled(this, true); // the absolute offset has to take into account that the circle starts at an offset //下拉触发刷新的偏离距离,默认是64 也就是下拉一个半圆多一点点的距离松开手就可以触发下拉刷新了 mSpinnerOffsetEnd = (int) (DEFAULT_CIRCLE_TARGET * metrics.density); //总共需要拖拽的距离等于触发的距离 mTotalDragDistance = mSpinnerOffsetEnd; //嵌套滑动机制的辅助类 mNestedScrollingParentHelper = new NestedScrollingParentHelper(this); mNestedScrollingChildHelper = new NestedScrollingChildHelper(this); //开启嵌套滑动 setNestedScrollingEnabled(true); //最开始的偏离值 当前的偏离值 mOriginalOffsetTop = mCurrentTargetOffsetTop = -mCircleDiameter; moveToStart(1.0f); final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS); setEnabled(a.getBoolean(0, true)); a.recycle(); }
-
重要字段:
-
mTotalDragDistance
-
mOriginalOffsetTop :刷新view最初的top值
-
mCurrentTargetOffsetTop :刷新view实时的top值
-
mSpinnerOffsetEnd :刷新view触发刷新的阈值
-
onMeasure
-
onMeasure源码
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //mTarget 即为下拉的圆形view if (mTarget == null) { ensureTarget(); } if (mTarget == null) { return; } //内容View,如SwipeRefreshLayout包裹着的RecyclerView //SwipeRefreshLayout 减去自己padding,剩下的都给mTarget,使其撑满 mTarget.measure(MeasureSpec.makeMeasureSpec( getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec( getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY)); //刷新控件,大小直接根据给定的硬编码 mCircleView.measure(MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY)); //刷新控件在SwipeRefreshLayuot‘s childs 中的索引值,后面draw时会用到 mCircleViewIndex = -1; // Get the index of the circleview. for (int index = 0; index < getChildCount(); index++) { if (getChildAt(index) == mCircleView) { mCircleViewIndex = index; break; } } }
-
重要字段
- mCircleViewIndex
- mTarget
- mCircleView
onLayout
-
onLayout源码
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { //获取测量到的宽度和高度 final int width = getMeasuredWidth(); final int height = getMeasuredHeight(); //没有child 直接return if (getChildCount() == 0) { return; } if (mTarget == null) { ensureTarget(); } //没有mTarget 直接 return if (mTarget == null) { return; } final View child = mTarget; final int childLeft = getPaddingLeft(); final int childTop = getPaddingTop(); final int childWidth = width - getPaddingLeft() - getPaddingRight(); final int childHeight = height - getPaddingTop() - getPaddingBottom(); //mTarget撑满SwipeRefreshLayout child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); int circleWidth = mCircleView.getMeasuredWidth(); int circleHeight = mCircleView.getMeasuredHeight(); //一直水平居中 mCircleView.layout((width / 2 - circleWidth / 2), mCurrentTargetOffsetTop, (width / 2 + circleWidth / 2), mCurrentTargetOffsetTop + circleHeight); }
绘制
-
通过重写getChildDrawingOrder去控制viewgroup绘制child的顺序,viewgroup默认从上至下
protected int getChildDrawingOrder(int childCount, int i) { if (mCircleViewIndex < 0) { return i; } else if (i == childCount - 1) { // Draw the selected child last //绘制到最后一个时,返回 return mCircleViewIndex; } else if (i >= mCircleViewIndex) { // Move the children after the selected child earlier one return i + 1; } else { // Keep the children before the selected child the same return i; } }
总结
- 通过上面我们了解了SwipeRefreshLayout的测量布局和绘制部分都做了什么
- 也了解到如何让下拉刷新view一直保持在最顶层让用户可见的一些策略
- 下篇将梳理传统touch事件实现方式的部分代码