IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 手写简易的viewPage--Android---自定义控件 -> 正文阅读

[移动开发]手写简易的viewPage--Android---自定义控件


前言

在学习了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()
                //如果刚刚滑动完,现在点击就以为新的时间序列,那么这个值就要置为0,意思还没有在滑动
                scrolling = false
            }
            MotionEvent.ACTION_MOVE -> {
                //如果到目前为止手指还没有触发左右滑动
                if (!scrolling) {
                    //计算左右滑动的位移
                    val dx = downX - ev.x
                    //左右滑动的距离大于触发页面滑动的阈值才触发左右滑动
                    if (abs(dx) > pagingSlop) {
                        //拦截这次的点击的时间序列
                        result = true
                        scrolling = true
                        //通知父view不要抢夺这次的点击序列
                        parent.requestDisallowInterceptTouchEvent(true)
                    }
                }
            }
        }
        //如果返回true,则子view之后的点击事件都由viewGroup拿到了,子view就不会再响应呢
        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 -> {
    //计算这时手指松开时触点的速度,1000是单位
    velocityTracker.computeCurrentVelocity(1000, maxVelocity.toFloat())
    //拿到x方向上的惯性速度
    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()
                //如果刚刚滑动完,现在点击就以为新的时间序列,那么这个值就要置为0,意思还没有在滑动
                scrolling = false
            }
            MotionEvent.ACTION_MOVE -> {
                //如果到目前为止手指还没有触发左右滑动
                if (!scrolling) {
                    //计算左右滑动的位移
                    val dx = downX - ev.x
                    //左右滑动的距离大于触发页面滑动的阈值才触发左右滑动
                    if (abs(dx) > pagingSlop) {
                        //拦截这次的点击的时间序列
                        result = true
                        scrolling = true
                        //通知父view不要抢夺这次的点击序列
                        parent.requestDisallowInterceptTouchEvent(true)
                    }
                }
            }
        }
        //如果返回true,则子view之后的点击事件都由viewGroup拿到了,子view就不会再响应呢
        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 -> {
                //计算这时手指松开时触点的速度,1000是单位
                velocityTracker.computeCurrentVelocity(1000, maxVelocity.toFloat())
                //拿到x方向上的惯性速度
                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()
        }
    }
}
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-10-07 13:57:32  更:2021-10-07 13:57:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 0:28:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码