【Android】View 的滑动

news2024/10/7 8:20:41

View 的滑动是 Android 实现自定义控件的基础,同时在开发中我们也难免会遇到 View 的滑动处理。其实不管是哪种滑动方式,其基本思想都是类似的:当点击事件传到 View 时,系统记下触摸点的坐标,手指移动时系统记下移动后触摸的坐标并算出偏移量,并通过偏移量来修改View的坐标。

实现 View 滑动有很多种方法,在这里主要讲解6种滑动方法,分别是 layout()、offsetLeftAndRight() 与 offsetTopAndBottom()、LayoutParams、Animation、scollTo() 与 scollBy(),以及 Scroller。

一、layout() 方法

View 进行绘制的时候会调用 onLayout() 方法来设置显示的位置,因此我们同样也可以通过修改 View 的 left、top、right、bottom 这4种属性来控制 View 的坐标。首先我们要自定义一个 View,在 onTouchEvent() 方法中获取触摸点的坐标,代码如下所示:

override fun onTouchEvent(event: MotionEvent?): Boolean {
    // 获取手指触摸点的横坐标和纵坐标
    val x = event?.x?.toInt()
    val y = event?.y?.toInt()

    when (event?.action) {
        MotionEvent.ACTION_DOWN -> {
            lastX = x ?: 0
            lastY = y ?: 0
        }

        ...
    }

    ...
}

接下来我们在 ACTION_MOVE 事件中计算偏移量,再调用 layout() 方法重新放置这个自定义 View 的位置即可。

override fun onTouchEvent(event: MotionEvent?): Boolean {
    ...

    when (event?.action) {
        ...

        MotionEvent.ACTION_MOVE -> {
            // 计算移动的距离
            val offsetX = x ?: (0 - lastX)
            val offsetY = y ?: (0 - lastY)

            // 调用 layout 方法来重新放置它的位置
            layout(left + offsetX, top + offsetY, right + offsetX, bottom + offsetY)
        }
    }

    ...
}

在每次移动时都会调用 layout() 方法对屏幕重新布局,从而达到移动 View 的效果。自定义 View 的全部代码如下所示:

class CustomView(context: Context, attributeSet: AttributeSet) : View(context, attributeSet) {

    private var lastX = 0
    private var lastY = 0

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        // 获取手指触摸点的横坐标和纵坐标
        val x = event?.x?.toInt()
        val y = event?.y?.toInt()

        when (event?.action) {
            MotionEvent.ACTION_DOWN -> {
                lastX = x ?: 0
                lastY = y ?: 0
            }

            MotionEvent.ACTION_MOVE -> {
                // 计算移动的距离
                val offsetX = x ?: (0 - lastX)
                val offsetY = y ?: (0 - lastY)

                // 调用 layout 方法来重新放置它的位置
                layout(left + offsetX, top + offsetY, right + offsetX, bottom + offsetY)
            }
        }

        return true
    }
}

随后,我们在布局中引用自定义 View 就可以了:

<com.tyhoo.android.demo.CustomView
    android:id="@+id/test_view"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:background="@android:color/holo_red_light"
    ... />

运行程序,效果如图1所示:
请添加图片描述

图1

图1中的方块就是我们自定义的 View,它会随着我们手指的滑动改变自己的位置。

二、offsetLeftAndRight() 与 offsetTopAndBottom()

这两种方法和 layout() 方法的效果差不多,其使用方式也差不多。我们将 ACTION_MOVE 中的代码替换成如下代码:

override fun onTouchEvent(event: MotionEvent?): Boolean {
    ...

    when (event?.action) {
        ...

        MotionEvent.ACTION_MOVE -> {
            // 计算移动的距离
            val offsetX = x ?: (0 - lastX)
            val offsetY = y ?: (0 - lastY)

            // 对 left 和 right 进行偏移
            offsetLeftAndRight(offsetX)
            // 对 top 和 bottom 进行偏移
            offsetTopAndBottom(offsetY)
        }
    }

    ...
}

三、LayoutParams

LayoutParams 主要保存了一个 View 的布局参数,因此我们可以通过 LayoutParams 来改变 View 的布局参数从而达到改变 View 位置的效果。同样,我们将 ACTION_MOVE 中的代码替换成如下代码:

override fun onTouchEvent(event: MotionEvent?): Boolean {
    ...

    when (event?.action) {
        ...

        MotionEvent.ACTION_MOVE -> {
            // 计算移动的距离
            val offsetX = x ?: (0 - lastX)
            val offsetY = y ?: (0 - lastY)

            val layoutParams = layoutParams as ConstraintLayout.LayoutParams
            layoutParams.leftMargin = left + offsetX
            layoutParams.topMargin = top + offsetY
            setLayoutParams(layoutParams)
        }
    }

    ...
}

因为父控件是 ConstraintLayout,所以我们用了 ConstraintLayout.LayoutParams。如果父控件是 RelativeLayout,则要使用RelativeLayout.LayoutParams。除了使用布局的 LayoutParams 外,我们还可以用 ViewGroup.MarginLayoutParams 来实现:

override fun onTouchEvent(event: MotionEvent?): Boolean {
    ...

    when (event?.action) {
        ...

        MotionEvent.ACTION_MOVE -> {
            // 计算移动的距离
            val offsetX = x ?: (0 - lastX)
            val offsetY = y ?: (0 - lastY)

            val layoutParams = layoutParams as ViewGroup.MarginLayoutParams
            layoutParams.leftMargin = left + offsetX
            layoutParams.topMargin = top + offsetY
            setLayoutParams(layoutParams)
        }
    }

    ...
}

四、Animation

可以采用 View 动画来移动,在 res 目录新建 anim 文件夹并创建 translate.xml:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="1000"
        android:fromXDelta="0"
        android:toXDelta="300" />
</set>

接下来在 Kotlin 代码中调用就好了,代码如下所示:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val testView = findViewById<CustomView>(R.id.test_view)
        testView.animation = AnimationUtils.loadAnimation(this, R.anim.translate)
    }
}

运行程序,效果如图2所示:
请添加图片描述

图2

运行程序,我们设置的方块会向右平移300像素,然后又会回到原来的位置。为了解决这个问题,我们需要在 translate.xml 中加上 fillAfter=“true”,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true">
    <translate
        android:duration="1000"
        android:fromXDelta="0"
        android:toXDelta="300" />
</set>

运行程序,效果如图3所示:
请添加图片描述

图3

运行代码后会发现,方块向右平移300像素后就停留在当前位置了。

需要注意的是,View 动画并不能改变 View 的位置参数。如果对一个 View 进行如上的平移动画操作,当 View 平移300像素停留在当前位置时,我们点击这个 View 并不会触发点击事件,但在我们点击这个 View 的原始位置时却触发了点击事件。对于系统来说这个 View 并没有改变原有的位置,所以我们点击其他位置当然不会触发这个 View 的点击事件。

五、scrollTo() 与 scollBy()

scrollTo(x, y) 表示移动到一个具体的坐标点,而 scrollBy(dx, dy) 则表示移动的增量为 dx、dy。其中,scollBy 最终也是要调用 scollTo 的。View 的 scollTo 和 scollBy 的源码如下所示:

public void scrollTo(int x, int y) {
    if (mScrollX != x || mScrollY != y) {
        int oldX = mScrollX;
        int oldY = mScrollY;
        mScrollX = x;
        mScrollY = y;
        invalidateParentCaches();
        onScrollChanged(mScrollX, mScrollY, oldX, oldY);
        if (!awakenScrollBars()) {
            postInvalidateOnAnimation();
        }
    }
}

public void scrollBy(int x, int y) {
    scrollTo(mScrollX + x, mScrollY + y);
}

scollTo、scollBy 移动的是 View 的内容,如果在 ViewGroup 中使用,则是移动其所有的子 View。我们将 ACTION_MOVE 中的代码替换成如下代码:

override fun onTouchEvent(event: MotionEvent?): Boolean {
    ...

    when (event?.action) {
        ...

        MotionEvent.ACTION_MOVE -> {
            // 计算移动的距离
            val offsetX = x ?: (0 - lastX)
            val offsetY = y ?: (0 - lastY)

            (parent as View).scrollBy(-offsetX, -offsetY)
        }
    }

    return true
}

这里若要实现自定义 View 随手指移动的效果,就需要将偏移量设置为负值。为什么要设置为负值呢?这是参考对象不同导致的差异。所以我们用 scrollBy 方法的时候要设置负数才会达到自己想要的效果。

六、Scroller

我们在用 scollTo/scollBy 方法进行滑动时,这个过程是瞬间完成的,所以用户体验不大好。这里我们可以使用 Scroller 来实现有过渡效果的滑动,这个过程不是瞬间完成的,而是在一定的时间间隔内完成的。Scroller 本身是不能实现 View 的滑动的,它需要与 View 的 computeScroll() 方法配合才能实现弹性滑动的效果。在这里我们实现自定义 View 平滑地向右移动。首先我们要初始化 Scroller,代码如下所示:

class CustomView(context: Context, attributeSet: AttributeSet) : View(context, attributeSet) {

    ...
    
    private var scroller: Scroller? = null

    init {
        scroller = Scroller(context)
    }

    ...
}

接下来重写 computeScroll() 方法,系统会在绘制 View 的时候在 draw() 方法中调用该方法。在这个方法中,我们调用父类的 scrollTo() 方法并通过 Scroller 来不断获取当前的滚动值,每滑动一小段距离我们就调用invalidate() 方法不断地进行重绘,重绘就会调用 computeScroll() 方法,这样我们通过不断地移动一个小的距离并连贯起来就实现了平滑移动的效果。

override fun computeScroll() {
    super.computeScroll()
    scroller?.let {
        if (it.computeScrollOffset()) {
            (parent as View).scrollTo(it.currX, it.currY)
            invalidate()
        }
    }
}

我们在自定义 View 中写一个 smoothScrollTo 方法,调用 Scroller 的 startScroll() 方法,在 2000ms 内沿 X 轴平移 delta 像素,代码如下所示:

fun smoothScrollTo(destX: Int, destY: Int) {
    val scrollX = scrollX
    val delta = destX - scrollX
    scroller?.startScroll(scrollX, 0, delta, 0, 2000)
    invalidate()
}

最后我们再调用自定义 View 的 smoothScrollTo() 方法。这里我们设定自定义 View 沿着 X 轴向右平移 400 像素。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val testView = findViewById<CustomView>(R.id.test_view)
        testView.smoothScrollTo(-400, 0)
    }
}

运行程序,效果如图4所示:
请添加图片描述

图4

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1479636.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

全志XR806 FreeRTOS快速开发入门

RTOS 快速开发入门 XR806 是一颗高集成度无线应用MCU&#xff0c;其集成了ARMv8-M 内核、IEEE 802.11b/g/n Wi-Fi 子系统、BLE 5.0子系统、电源管理系统、高级别的安全系统以及丰富的外设接口&#xff0c;具有优秀的射频性能、稳定性、可靠性和超低功耗。 芯之联软件开发平台…

IIS发布PHP网站字体404解决办法

最近在使用 IIS 发布 PHP 网站时&#xff0c;我遇到了一个前端问题&#xff0c;即字体库文件 404 错误。这个问题的根本原因是 IIS 未能正确识别字体文件类型&#xff0c;导致浏览器在加载页面时无法正确获取所需字体资源&#xff0c;进而触发了404错误。这样的问题会导致网站页…

量化投资实战(一)之K线图策略

点赞、关注再看&#xff0c;养成良好习惯 Life is short, U need Python 量化投资实战系列&#xff0c;不断更新中 1. K线图简介 K 线图&#xff08;Candlestick Charts&#xff09;又称为“蜡烛图”、“阴线图”等。股市及期货市场中的 K 线图的画法包含四个数据&#xff0c;…

ETH网络中的区块链

回顾BTC网络的区块链系统 什么是区块链&#xff1f;BTC网络是如何运行的&#xff1f;BTC交易模式 - UXTO ETH网络中的区块链 ETH网络的基石依旧是 区块链。上面 什么是区块链&#xff1f; 的文章依旧适用。 相比BTC网络&#xff0c;ETH网络的账户系统就相对复杂&#xff0c;所…

FPGA之带有进位逻辑的加法运算

module ADDER&#xff08; input [5&#xff1a;0]A&#xff0c; input [5&#xff1a;0]B&#xff0c;output[6&#xff1a;0]Q &#xff09;&#xff1b; assign Q AB&#xff1b; endmodule 综合结果如下图所示&#xff1a; 使用了6个Lut&#xff0c;&#xff0c;6个LUT分布…

【通信基础知识】完整通信系统的流程图及各模块功能详解

2024.2.29 抱歉最近在写毕设大论文&#xff0c;因此没有太多时间更新。然而&#xff0c;在写论文的过程中&#xff0c;发现自己对通信系统的了解还不够全明白&#xff0c;因此差了一些硕博论文总结了一个完整的通信系统流程图。若有不对的地方请多多指正//部分内容有参考ChatGP…

Python+neo4j构建豆瓣电影知识图谱

文章目录 数据来源数据整理导入节点和关系导入使用Subgraph批量导入节点和关系 多标签实体和实体去重 数据来源 http://www.openkg.cn/dataset/douban-movie-kg 该网址拥有丰富的中文知识图谱数据集&#xff0c;OpenKG(Open Knowledge Graph)&#xff0c;可供研究人员使用研究…

2019年下半年教师资格证考试《高中信息技术》题

2.在如图1所示的图文混排Word文档中&#xff0c;文字环绕方式使用了&#xff08; D&#xff09;。 图1 A嵌入型 B上下环绕型 C衬于文字下方 D编辑环绕顶点 6.在Flash中绘制类似“雨”的形状&#xff0c;一般首先使用“椭圆工具”绘制出一个圆形&#xff08;如图4中a图&…

Win11系统实现adb命令向安卓子系统安装APP

Win11系统实现通过adb命令向安卓子系统安装已下载好的apk包。 要实现以上目标&#xff0c;我们需要用到一个Android SDK 的组件Android SDK Platform-Tools &#xff01;这个组件呢其实是被包含在 Android Studio中的&#xff0c;如果你对安卓开发有所了解对此应该不会陌生&…

利用DrissionPage库写一个简单的哔哩哔哩自动评论引流脚本

以前利用selenium写过哔哩哔哩简单的脚本&#xff0c;后来数据丢失&#xff0c;痛苦万分&#xff0c;今天看到这个库了解一下便写了这个脚本。 切记做好数据备份 文章目录 DrissionPage简介&#xff1a;代码部分 DrissionPage和selenium很像&#xff0c;但是更简单&#xff0c;…

展厅设计的理念是什么

1、立足当地文化 升华本地精神 &#xff0c;因地制宜&#xff0c;深入挖掘本土文化特色&#xff0c;撷取其精华&#xff0c;灵活运用、巧妙融入&#xff0c;做到掌控宏观全局。 重点突出&#xff0c;努力打造本土拳头品牌&#xff0c;挖掘其内涵&#xff0c;拓展延伸、着重展示…

cannot import name ‘Iterator‘ from ‘torchtext.data‘

在运行Transformer工程的时候&#xff0c;执行如下语句时出现题目中的报错。 from torchtext.data import Iterator 这个问题解决起来比较曲折&#xff0c;记录下来以备后续查阅。 原来的环境安装的是最新的pytorch&#xff0c;2.x版本&#xff0c;而且该环境在另一个大型Tr…

36102系列微波测量探针

01 36102系列微波测量探针 产品综述&#xff1a; 微波测量探针产品36102系列&#xff0c;无缝隙覆盖DC~110GHz&#xff0c;触点防堆金处理&#xff0c;压痕轻&#xff0c;性能可靠&#xff0c;产品统一采用标准同轴安装接口&#xff0c;触点类型、触点尺寸可选&#xff0c;可…

python64-Python的循环之嵌套循环

如果把一个循环放在另一个循环体内,那么就可以形成嵌套循环。嵌套循环既可以是for-in 循环嵌套while循环,也可以是while循环嵌套do while循环.即各种类型的循环都可以作为外层循环,各种类型的循环也都可以作为内层循环。 当程序遇到嵌套循环时,如果外层循环的循环条件允许…

PSO-CNN-LSTM多输入分类预测|粒子群算法优化的卷积-长短期神经网络分类预测(Matlab)——附代码+数据

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、算法介绍&#xff1a; 四、完整程序数据分享下载&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matlab平台…

韩国量子之梦:将量子计算纳入新增长 4.0战略

内容来源&#xff1a;量子前哨&#xff08;ID&#xff1a;Qforepost&#xff09; 编辑丨王珩 编译/排版丨沛贤 深度好文&#xff1a;1500字丨9分钟阅读 据《朝鲜邮报》报道&#xff0c;韩国将推出由量子计算加速的云服务&#xff0c;并在首尔地区启动城市空中交通的试飞&…

Android 混淆是啥玩意儿?

什么是混淆 Android混淆&#xff0c;是伴随着Android系统的流行而产生的一种Android APP保护技术&#xff0c;用于保护APP不被破解和逆向分析。简单的说&#xff0c;就是将原本正常的项目文件&#xff0c;对其类、方法、字段&#xff0c;重新命名a,b,c…之类的字母&#xff0c…

贝叶斯优化双向门控循环单元BO-BIGRU时序预测的matlab实现【源代码】

贝叶斯优化双向门控循环单元简介&#xff1a; 贝叶斯优化双向门控循环单元&#xff08;BO-BIGRU&#xff09;是一种结合了贝叶斯优化和双向门控循环单元&#xff08;BIGRU&#xff09;的神经网络模型。BIGRU是一种改进的循环神经网络&#xff08;RNN&#xff09;&#xff0c;它…

Java+SpringBoot,打造社区疫情信息新生态

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

新一代科学计算与系统建模仿真平台MWORKS 2024a震撼发布:产品强势进化,更新亮点速览!

2月25日&#xff0c;同元软控成功举办MWORKS 2024产品发布会&#xff0c;会上公布了新版MWORKS的设计理念、关键技术、版本亮点、产品特性以及重大改进。当前&#xff0c;科学计算与系统建模仿真平台MWORKS 2024a已正式上线&#xff0c;开放下载。 MWORKS已成为全球第4个完整的…