参考:
SwipeRefreshLayout
Glide源码分析参考文章
SwipeRefreshLayout
SwipeRefreshLayout
是 Android 官方提供的一个实现下拉刷新功能的控件。它主要用于包裹在 RecyclerView
、ListView
、ScrollView
等可滚动视图的外层,用户通过下拉动作触发刷新操作。
1. 类的继承结构
SwipeRefreshLayout
继承自 ViewGroup
,并实现了 NestedScrollingParent2
和 NestedScrollingChild2
接口,以支持嵌套滚动。
class SwipeRefreshLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : ViewGroup(context, attrs), NestedScrollingParent2, NestedScrollingChild2 {
// 类的实现
}
2. 核心成员变量
mTarget
: 包裹的子视图,通常是RecyclerView
、ListView
等可滚动视图。mCircleView
: 刷新时显示的进度指示器,即转圈动画视图。mCircleDiameter
: 指示器的直径。mTotalDragDistance
: 触发刷新操作的拖动距离。mRefreshing
: 表示当前是否正在刷新。mListener
: 刷新操作的监听器,当用户下拉到一定距离并松手时触发该监听器。
3. 初始化
SwipeRefreshLayout
的初始化主要在其构造方法中完成,关键步骤如下:
- 创建进度指示器
mCircleView
:创建一个CircleImageView
,这个视图即是显示加载动画的组件。 - 设置颜色:通过
setColorSchemeColors
方法设置加载动画的颜色。 - 将进度指示器添加到布局中:将
mCircleView
添加到SwipeRefreshLayout
的视图层次中。
init {
createProgressView()
isChildrenDrawingOrderEnabled = true
mCircleDiameter = resources.getDimensionPixelSize(R.dimen.circle_width)
// 其他初始化代码...
}
4. 拖动逻辑
SwipeRefreshLayout
的拖动逻辑主要通过 onInterceptTouchEvent
和 onTouchEvent
方法来实现。
onInterceptTouchEvent
方法
- 用于拦截触摸事件,决定是否将事件传递给子视图,或者由自己处理。
- 当检测到用户在顶部向下滑动时,
SwipeRefreshLayout
会拦截事件,并开始处理拖动逻辑。
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
ensureTarget()
val action = ev.actionMasked
if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
mReturningToStart = false
}
if (!isEnabled || canChildScrollUp() || mRefreshing || mNestedScrollInProgress) {
return false
}
when (action) {
MotionEvent.ACTION_DOWN -> {
setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView!!.top)
mActivePointerId = ev.getPointerId(0)
mIsBeingDragged = false
}
MotionEvent.ACTION_MOVE -> {
val pointerIndex = ev.findPointerIndex(mActivePointerId)
if (pointerIndex < 0) return false
val y = ev.getY(pointerIndex)
startDragging(y)
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
mIsBeingDragged = false
mActivePointerId = INVALID_POINTER
}
}
return mIsBeingDragged
}
onTouchEvent
方法
- 处理实际的触摸拖动操作,包括计算拖动的距离,更新进度指示器的位置等。
- 当拖动超过
mTotalDragDistance
并且用户松手时,触发刷新操作。
override fun onTouchEvent(ev: MotionEvent): Boolean {
val action = ev.actionMasked
val pointerIndex = ev.findPointerIndex(mActivePointerId)
if (pointerIndex < 0) {
return false
}
when (action) {
MotionEvent.ACTION_MOVE -> {
val y = ev.getY(pointerIndex)
val overscrollTop = (y - mInitialMotionY) * DRAG_RATE
if (mIsBeingDragged) {
if (overscrollTop > mTotalDragDistance) {
setRefreshing(true, true)
} else {
setTargetOffsetTopAndBottom(
(mOriginalOffsetTop + overscrollTop - mCurrentTargetOffsetTop).toInt()
)
}
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
if (mIsBeingDragged) {
finishSpinner(overscrollTop)
mIsBeingDragged = false
return false
}
}
}
return true
}
5. 触发刷新
当用户拖动到一定距离并松手时,调用 setRefreshing(true)
方法触发刷新操作。
fun setRefreshing(refreshing: Boolean) {
if (refreshing && mRefreshing != refreshing) {
mRefreshing = refreshing
val endTarget = if (!mUsingCustomStart) {
mOriginalOffsetTop + mTotalDragDistance
} else {
mTotalDragDistance
}
setTargetOffsetTopAndBottom(endTarget - mCurrentTargetOffsetTop)
mNotify = false
startScaleUpAnimation(mRefreshListener)
} else {
setRefreshing(refreshing, false)
}
}
6. 动画处理
SwipeRefreshLayout
使用了多个动画来增强用户体验,尤其是在处理下拉刷新操作时。这些动画包括视图位置的移动、缩放效果以及透明度的渐变。
位置移动动画
当用户下拉时,加载视图会随手势向下移动,松手后视图会自动回弹到刷新位置或初始位置。
private fun animateOffsetToCorrectPosition(from: Int, listener: Animation.AnimationListener?) {
mFrom = from
mAnimateToCorrectPosition.reset()
mAnimateToCorrectPosition.duration = ANIMATE_TO_TRIGGER_DURATION
mAnimateToCorrectPosition.interpolator = mDecelerateInterpolator
listener?.let {
mCircleView?.setAnimationListener(it)
}
mCircleView?.clearAnimation()
mCircleView?.startAnimation(mAnimateToCorrectPosition)
}
缩放效果
SwipeRefreshLayout
还会在下拉时缩放加载视图,使其看起来更具动态感。
kotlin复制代码private fun startScaleDownAnimation(listener: Animation.AnimationListener?) {
mScaleDownAnimation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation) {
setAnimationProgress(1 - interpolatedTime)
}
}.apply {
duration = SCALE_DOWN_DURATION
}
mCircleView?.setAnimationListener(listener)
mCircleView?.clearAnimation()
mCircleView?.startAnimation(mScaleDownAnimation)
}
透明度变化
SwipeRefreshLayout
通过透明度渐变动画使加载视图的出现和消失更自然。
kotlin复制代码private fun startAlphaAnimation(startingAlpha: Int, endingAlpha: Int) {
mAlphaStartAnimation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation) {
mProgress.alpha = (startingAlpha + (endingAlpha - startingAlpha) * interpolatedTime).toInt()
}
}.apply {
duration = ALPHA_ANIMATION_DURATION
}
mCircleView?.clearAnimation()
mCircleView?.startAnimation(mAlphaStartAnimation)
}
7. 嵌套滚动支持
SwipeRefreshLayout
实现了 NestedScrollingParent2
和 NestedScrollingChild2
接口,这使得它可以与嵌套滚动视图(如 RecyclerView
、NestedScrollView
)协作处理复杂的滚动场景。
嵌套滚动父视图支持
作为 NestedScrollingParent2
,SwipeRefreshLayout
能拦截和处理来自子视图的滚动事件。
kotlin复制代码override fun onNestedScrollAccepted(child: View, target: View, axes: Int, type: Int) {
mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes, type)
startNestedScroll(axes and ViewCompat.SCROLL_AXIS_VERTICAL, type)
mTotalUnconsumed = 0
mNestedScrollInProgress = true
}
override fun onNestedPreScroll(
target: View, dx: Int, dy: Int, consumed: IntArray, type: Int
) {
if (dy > 0 && mTotalUnconsumed > 0) {
val deltaY = if (dy > mTotalUnconsumed) mTotalUnconsumed else dy
mTotalUnconsumed -= deltaY
consumed[1] = deltaY
moveSpinner(mTotalUnconsumed)
}
val parentConsumed = mParentScrollConsumed
if (dispatchNestedPreScroll(dx, dy - consumed[1], parentConsumed, null, type)) {
consumed[1] += parentConsumed[1]
}
}
嵌套滚动子视图支持
作为 NestedScrollingChild2
,SwipeRefreshLayout
可以将滚动事件传递给父视图,并根据父视图的反馈调整滚动行为。
override fun startNestedScroll(axes: Int, type: Int): Boolean {
return mNestedScrollingChildHelper.startNestedScroll(axes, type)
}
override fun stopNestedScroll(type: Int) {
mNestedScrollingChildHelper.stopNestedScroll(type)
}
override fun hasNestedScrollingParent(type: Int): Boolean {
return mNestedScrollingChildHelper.hasNestedScrollingParent(type)
}
Glide
参考:https://juejin.cn/post/6994669144490639368