关于SwipeRefreshLayout和Glide的源码学习

参考:
SwipeRefreshLayout
Glide源码分析参考文章

SwipeRefreshLayout

SwipeRefreshLayout 是 Android 官方提供的一个实现下拉刷新功能的控件。它主要用于包裹在 RecyclerViewListViewScrollView 等可滚动视图的外层,用户通过下拉动作触发刷新操作。

1. 类的继承结构

SwipeRefreshLayout 继承自 ViewGroup,并实现了 NestedScrollingParent2NestedScrollingChild2 接口,以支持嵌套滚动。

class SwipeRefreshLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : ViewGroup(context, attrs), NestedScrollingParent2, NestedScrollingChild2 {
    // 类的实现
}

2. 核心成员变量

  • mTarget: 包裹的子视图,通常是 RecyclerViewListView 等可滚动视图。
  • 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 的拖动逻辑主要通过 onInterceptTouchEventonTouchEvent 方法来实现。

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 实现了 NestedScrollingParent2NestedScrollingChild2 接口,这使得它可以与嵌套滚动视图(如 RecyclerViewNestedScrollView)协作处理复杂的滚动场景。

嵌套滚动父视图支持

作为 NestedScrollingParent2SwipeRefreshLayout 能拦截和处理来自子视图的滚动事件。

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]
    }
}
嵌套滚动子视图支持

作为 NestedScrollingChild2SwipeRefreshLayout 可以将滚动事件传递给父视图,并根据父视图的反馈调整滚动行为。

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
在这里插入图片描述

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值