Android scroller学习总结

2023-10-31

一、关键在于View的scrollTo scrollBy方法

scrollTo scrollBy  滑动的是view的内容,而不是view本身,这也是和动画的区别  
怎么滑动内容呢?通过滑动view的画布,然后重绘
mScrollX mScrollY分别代表画布相对初始位置滑动的距离(坐标系为视图经典坐标系,向下是y正,向右是x正),滑动时就相当于放胶片电影,画布是原始屏幕,可以进行投影,内容不动,画布往右mScrollX为正,往下mScrollY为正;当然也可以以view内容作为滑动的参考对象,画布不动,内容往左、上滑为正(因为scrollTo和scrollBy的参数正负和以view内容做参考对象一致,所以以view内容为参考对象比较容易记忆)

scrollTo(scrollX, scrollY):
scrollX、scrollY相对于初始位置,为正代表手指左滑、上滑,为负代表手指右滑、下滑

scrollBy里面调用的是scrollTo(mScrollX + x, mScrollY + y),注意mScrollX、mScrollY是会随着滑动不断变化的

Scroller滑动辅助类,用以实现滑动惯性效果和回弹效果,最终还是依靠scrollTo、scrollBy来完成的:

因此Scroller类的基本使用流程可以总结如下:
(1)首先通过Scroller类的startScroll()开始一个滑动动画控制,里面进行了一些轨迹参数的设置和计算,开始位置,滑动距离;
(2)在调用 startScroll()的后面调用invalidate();引起视图的重绘操作,从而触发ViewGroup中的computeScroll()被调用;
(3)在computeScroll()方法中,先调用Scroller类中的computeScrollOffset()方法,里面根据当前消耗时间进行轨迹坐标的计算,然后取得计算出的当前滑动的偏移坐标,调用View的scrollTo()方法进行滑动控制,最后也需要调用invalidate();进行重绘

此外,scroller还有一个fling方法,能够实现惯性滑动,具体用法参考下方代码:

package com.example.meettingactivity

import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
import android.view.ViewGroup
import android.widget.Scroller

class CustomViewGroup : ViewGroup {

    var mView: View? = null
    var mX = 0f //每次滑动的横坐标位置
    var homePos = 0f //初始位置
    var lastPos = 0f //上次滑动的位置
    var dx = 0f //每次滑动后距离初始位置的距离
    private val scroller: Scroller
    private var velocityTracker: VelocityTracker? = null

    constructor(context: Context) : this(context, null)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context, attrs: AttributeSet?, defAttr: Int) : super(
        context,
        attrs,
        defAttr
    ) {
        scroller = Scroller(context)
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        Log.i("wjc", "mes childCount=$childCount")
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        measureChildren(widthMeasureSpec, heightMeasureSpec)
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        for (i in 0 until childCount) {
            mView = getChildAt(i)
            mView?.let {
                it.layout(i * it.measuredWidth, t, (i + 1) * it.measuredWidth, b)
            }
        }
    }

    /**
     * 一、使用scrollTo
     */
//    override fun onTouchEvent(event: MotionEvent?): Boolean {
//        event?.let { event ->
//            mX = event.x
//            when (event.action) {
//                MotionEvent.ACTION_DOWN -> {
//                    //1.最终dx是起点-终点的值加上scrollX,所以这里直接加在起点处
//                    homePos = event.x + scrollX
//                }
//
//                MotionEvent.ACTION_MOVE -> {
//                    dx = homePos - mX
//                    scrollTo(dx.toInt(), scrollY)
//                }
//
//                else -> {}
//            }
//        }
//        //2.消费事件,不然无法获取滚动事件
//        return true
//    }

    /**
     * 二、使用scrollBy
     */
//    override fun onTouchEvent(event: MotionEvent?): Boolean {
//        event?.let { event ->
//            mX = event.x
//            when (event.action) {
//                MotionEvent.ACTION_DOWN -> {
//                    lastPos = event.x
//                }
//
//                MotionEvent.ACTION_MOVE -> {
//                    //1.scrollBy里面调用的也是scrollTo(mScrollX + x, mScrollY + y),所以这里dx等于两次事件滑动的距离
//                    dx = lastPos - mX
//                    scrollBy(dx.toInt(), scrollY)
//                    lastPos = mX
//                }
//
//                else -> {}
//            }
//        }
//        //2.消费事件,不然无法获取滚动事件
//        return true
//    }

    /**
     * 三、使用Scroller
     */
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        event?.let { event ->
            mX = event.x
            if (velocityTracker == null) {
                velocityTracker = VelocityTracker.obtain()
            }
            velocityTracker!!.addMovement(event)
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    lastPos = event.x
                    if (!scroller.isFinished) {
                        scroller.abortAnimation()
                    }
                }

                MotionEvent.ACTION_MOVE -> {
                    dx = lastPos - mX
                    scroller.startScroll(scroller.finalX, scroller.finalY, dx.toInt(), 0)
                    invalidate()
                    lastPos = mX
                }

                MotionEvent.ACTION_UP -> {
                    velocityTracker!!.computeCurrentVelocity(1000)
                    val initialVelocity = velocityTracker!!.xVelocity.toInt()
                    Log.i("wjc", "initialVelocity=$initialVelocity")
                    scroller.fling(
                        scroller.finalX,
                        scroller.finalY,
                        -initialVelocity,
                        0,
                        0,
                        Int.MAX_VALUE,
                        0,
                        0
                    )
                    velocityTracker!!.recycle()
                    velocityTracker = null
                }

                else -> {}
            }
        }
        //2.消费事件,不然无法获取滚动事件
        return true
    }

    /**
     * 该方法会在view.draw中调用,在这里继续使用scrollTo进行滑动
     */
    override fun computeScroll() {
        super.computeScroll()
        if (scroller.computeScrollOffset()) {
            scrollTo(scroller.currX, scroller.currY)
            invalidate()
        }
    }

}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android scroller学习总结 的相关文章

随机推荐