前言
在学习了view 的点击事件的效应后,我们自己来写一个viewPage ,功能比较简单,只有两个页面,我们可以左右滑动来翻页
效果展示
一、思路是什么?
1.一页一个view占满屏幕,所以把自己的所有空间给childView去测量
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
measureChildren(widthMeasureSpec, heightMeasureSpec)
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
2.摆放时一页摆放一个子view,可以看出left,right是进行累加的
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
var childLeft = 0
val childTop = 0
var childRight = width
val childBottom = height
for (child in children) {
child.layout(childLeft, childTop, childRight, childBottom)
childLeft += width
childRight += width
}
}
3.viewGroup要抢占子view的touch序列
①:原因: 假设黑框是ScrollView ,不管是点击子view如绿点所示然后滑动,还是点击子view之外的地方如蓝点所示后滑动,都要起到相同的滑动效果。所以要求我们在手指点到子view上进行滑动的这个过程让viewgroup 拿到,所以要在viewgroup中重写onInterceptTouchEvent 方法 ②:代码
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
var result = false
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
velocityTracker.clear()
}
velocityTracker.addMovement(ev)
when (ev.actionMasked) {
MotionEvent.ACTION_DOWN -> {
downX = ev.x
downY = ev.y
downScrollX = scrollX.toFloat()
scrolling = false
}
MotionEvent.ACTION_MOVE -> {
if (!scrolling) {
val dx = downX - ev.x
if (abs(dx) > pagingSlop) {
result = true
scrolling = true
parent.requestDisallowInterceptTouchEvent(true)
}
}
}
}
return result
}
③:解释 画红框的这两行代码表示如果手指点击上去就代表着一个全新的点击touch 序列,所以这个用于手指松开时的actionMasked 就要重置 画框的这个判断条件,还可以解决其他特殊的情况。如果子view是一个可以上下滑动的viewgroup ,我们上下滑动子view 的时候可以把这个值置为true,这样就可以解决手指突然向左右歪也不会触发viewgroup 的左右滑动的效果,也就是触发了子view的滑动的时候不让viewgroup 抢夺子view 的touch序列
4.viewGroup自己的onTouchEvent
①:同样如果是ACTION_DOWN 就重置velocityTracker
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
velocityTracker.clear()
}
velocityTracker.addMovement(event)
②:手指滑动时滑动页面
MotionEvent.ACTION_MOVE -> {
val dx =
(downX - event.x + downScrollX).toInt().coerceAtLeast(0).coerceAtMost(width)
scrollTo(dx, 0)
}
③:最核心的部分,有手指抬起时
MotionEvent.ACTION_UP -> {
velocityTracker.computeCurrentVelocity(1000, maxVelocity.toFloat())
val xVelocity = velocityTracker.xVelocity
val scrollX = scrollX
val targetPage = if (abs(xVelocity) < minVelocity) {
if (scrollX > width / 2) 1 else 0
} else {
if (xVelocity < 0) 1 else 0
}
val scrollDistance = if (targetPage == 0) -scrollX else width - scrollX
overScroller.startScroll(getScrollX(), 0, scrollDistance, 0)
postInvalidateOnAnimation()
}
图解一些下图中这几行代码 如下图所示 如果滑动的距离scrollX小于页面1的一半宽,那么松开指头后就在屏幕上显示页面一 如果滑动的距离超过了页面1的一半宽度,那么就在屏幕上显示页面2
对应在手机效果上就是下面这样
完整代码
package com.hencoder.viewgroup.view
import android.content.Context
import android.util.AttributeSet
import android.view.*
import android.widget.OverScroller
import androidx.core.view.children
import kotlin.math.abs
class TwoPager2(context: Context, attrs: AttributeSet) : ViewGroup(context, attrs) {
private var downX = 0f
private var downY = 0f
private var downScrollX = 0f
private var scrolling = false
private val overScroller: OverScroller = OverScroller(context)
private val viewConfiguration: ViewConfiguration = ViewConfiguration.get(context)
private val velocityTracker = VelocityTracker.obtain()
private var minVelocity = viewConfiguration.scaledMinimumFlingVelocity
private var maxVelocity = viewConfiguration.scaledMaximumFlingVelocity
private var pagingSlop = viewConfiguration.scaledPagingTouchSlop
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
measureChildren(widthMeasureSpec, heightMeasureSpec)
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
var childLeft = 0
val childTop = 0
var childRight = width
val childBottom = height
for (child in children) {
child.layout(childLeft, childTop, childRight, childBottom)
childLeft += width
childRight += width
}
}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
var result = false
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
velocityTracker.clear()
}
velocityTracker.addMovement(ev)
when (ev.actionMasked) {
MotionEvent.ACTION_DOWN -> {
downX = ev.x
downY = ev.y
downScrollX = scrollX.toFloat()
scrolling = false
}
MotionEvent.ACTION_MOVE -> {
if (!scrolling) {
val dx = downX - ev.x
if (abs(dx) > pagingSlop) {
result = true
scrolling = true
parent.requestDisallowInterceptTouchEvent(true)
}
}
}
}
return result
}
override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
velocityTracker.clear()
}
velocityTracker.addMovement(event)
when (event.actionMasked) {
MotionEvent.ACTION_MOVE -> {
val dx =
(downX - event.x + downScrollX).toInt().coerceAtLeast(0).coerceAtMost(width)
scrollTo(dx, 0)
}
MotionEvent.ACTION_UP -> {
velocityTracker.computeCurrentVelocity(1000, maxVelocity.toFloat())
val xVelocity = velocityTracker.xVelocity
val scrollX = scrollX
val targetPage = if (abs(xVelocity) < minVelocity) {
if (scrollX > width / 2) 1 else 0
} else {
if (xVelocity < 0) 1 else 0
}
val scrollDistance = if (targetPage == 0) -scrollX else width - scrollX
overScroller.startScroll(getScrollX(), 0, scrollDistance, 0)
postInvalidateOnAnimation()
}
}
return true
}
override fun computeScroll() {
if (overScroller.computeScrollOffset()) {
scrollTo(overScroller.currX, overScroller.currY)
postInvalidateOnAnimation()
}
}
}
|