一、关键在于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.meettingactivityimport 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.Scrollerclass CustomViewGroup : ViewGroup {var mView: View? = nullvar mX = 0f //每次滑动的横坐标位置var homePos = 0f //初始位置var lastPos = 0f //上次滑动的位置var dx = 0f //每次滑动后距离初始位置的距离private val scroller: Scrollerprivate var velocityTracker: VelocityTracker? = nullconstructor(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.xif (velocityTracker == null) {velocityTracker = VelocityTracker.obtain()}velocityTracker!!.addMovement(event)when (event.action) {MotionEvent.ACTION_DOWN -> {lastPos = event.xif (!scroller.isFinished) {scroller.abortAnimation()}}MotionEvent.ACTION_MOVE -> {dx = lastPos - mXscroller.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()}}}