【Android】View 的滑动

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 ?: 0lastY = 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 = 0private var lastY = 0override fun onTouchEvent(event: MotionEvent?): Boolean {// 获取手指触摸点的横坐标和纵坐标val x = event?.x?.toInt()val y = event?.y?.toInt()when (event?.action) {MotionEvent.ACTION_DOWN -> {lastX = x ?: 0lastY = 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.CustomViewandroid: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.LayoutParamslayoutParams.leftMargin = left + offsetXlayoutParams.topMargin = top + offsetYsetLayoutParams(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.MarginLayoutParamslayoutParams.leftMargin = left + offsetXlayoutParams.topMargin = top + offsetYsetLayoutParams(layoutParams)}}...
}

四、Animation

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

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"><translateandroid: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"><translateandroid: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? = nullinit {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 = scrollXval delta = destX - scrollXscroller?.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.mzph.cn/news/710635.shtml

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

相关文章

【AI+应用】怎么快速制作一个类chatGPT套壳网站

最近有人问我&#xff0c; 看了我之前写的一篇文章 [人工智能] AI浪潮下Sora对于普通人的机会 &#xff0c; 怎么做一个类chatGPT的套壳网站&#xff0c;是从0开始做么。 对于普通人来说&#xff0c;万事不懂先AI&#xff0c; AI找不到答案搜索google或百度。对于程序员来说…

C# 获取类型 Type.GetType()

背景 C#是强类型语言&#xff0c;任何对象都有Type&#xff0c;有时候需要使用Type来进行反射、序列化、筛选等&#xff0c;获取Type有Type.GetType, typeof()&#xff0c;object.GetType() 等方法&#xff0c;本文重点介绍Type.GetType()。 系统类型/本程序集内的类型 对于系…

有哪些视频媒体?邀请视频媒体报道活动的好处

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 视频媒体在当今的媒体生态中占据了重要的地位。以下是一些主要的视频媒体类型&#xff1a; 电视台&#xff1a;如中央电视台、各省级卫视台、地方电视台等&#xff0c;他们拥有专业的视…

探索Redis 6.0的新特性

Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的内存中数据结构存储系统&#xff0c;通常被用作缓存、消息队列和实时数据处理等场景。它的简单性、高性能以及丰富的数据结构支持使其成为了众多开发者和企业的首选。在Redis 6.0版本中&#xff0c;引入了一…

【深蓝学院】移动机器人运动规划--第7章 集群机器人运动规划--笔记

文章目录 0. Contents1. Multi-Agent Path Finding (MAPF)1.1 HCA*1.2 Single-Agent A*1.3 ID1.4 M*1.5 Conflict-Based Search(CBS)1.6 ECBS1.6.1 heuristics1.6.2 Focal Search 2. Velocity Obstacle (VO&#xff0c;速度障碍物)2.1 VO2.2. RVO2.3 ORCA 3. Flocking model&am…

ChatGPT4.0 的优势、升级 4.0 为什么这么难以及如何进行升级?

前言 “ChatGPT4.0一个月多少人民币&#xff1f;” ”chatgpt4账号“ ”chatgpt4 价格“ “chatgpt4多少钱” 最近发现很多小伙伴很想知道关于ChatGPT4.0的事情&#xff0c;于是写了这篇帖子&#xff0c;帮大家分析一下。 一、ChatGPT4.0 的优势 &#xff08;PS&#xff1a;…

LINUX基础培训二十七之shell标准输入、输出、错误

一、Shell 输入/输出重定向 大多数 UNIX 系统命令从你的终端接受输入并将所产生的输出发送回​​到您的终端。一个命令通常从一个叫标准输入的地方读取输入&#xff0c;默认情况下&#xff0c;这恰好是你的终端。同样&#xff0c;一个命令通常将其输出写入到标准输出&#xff…

【树莓派系统配置+python3.8+环境配置踩坑点汇总】raspberrypi

最近又开始搞树莓派的深度学习模型。很多windows端的环境需要在树莓派上重新部署&#xff0c;中间出现了非常多的问题。主要以各种库的下载安装为主要。 首先&#xff0c;第一个问题&#xff1a; 树莓派系统烧录之后&#xff0c;默认apt一般需要升级看&#xff0c;而默认下载…

JVM性能优化

运行时优化 方法内联 方法内联&#xff0c;是指 JVM在运行时将调用次数达到一定阈值的方法调用替换为方法体本身 &#xff0c;从而消除调用成本&#xff0c;并为接下来进一步的代码性能优化提供基础&#xff0c;是JVM的一个重要优化手段之一。 注&#xff1a; C的inline属于编…

babylonsjs入门-基础模版

基于babylonjs封装的一些功能和插件 &#xff0c;希望有更多的小伙伴一起玩babylonjs&#xff1b; 欢迎加群&#xff08;点击群号传送&#xff09;&#xff1a;464146715 官方文档 中文文档 案例传送门 ​ 懒得打字&#xff0c;你们直接去copy组件吧&#xff0c;主要看这2…

旧版android模拟器,37岁程序员被裁

前言 从18年毕业至今&#xff0c;就职过两家公司&#xff0c;大大小小项目做了几个&#xff0c;非常感谢我的两位老大&#xff0c;在我的android成长路上给予我很多指导&#xff0c;亦师亦友的关系。 从年前至今参加面试了很多公司&#xff0c;也收到了几家巨头的offer&#…

transformer--编码器2(前馈全连接层、规范化层、子层链接结构、编码器层、编码器)

前馈全连接层 什么是前馈全连接层: 在Transformer中前馈全连接层就是具有两层线性层的全连接网络 前馈全连接层的作用: 考虑注意力机制可能对复杂过程的拟合程度不够,通过增加两层网络来增强模型的能力 code # 前馈全连接层 class PositionwiseFeedForward(nn.Module):de…

绝地求生:发现吃鸡号被盗,怎么操作才是最正确的

首先闲游盒先了解一下盗号者的盗号流程 一般盗号的流程是先把你steam上的皮肤饰品出售&#xff0c;然后把余额转走&#xff0c;再把steam账号作为黑号进行出售。 所以当闲游盒发现号被盗的时候也分为两种情况&#xff1a;一种是他正在出售商品的时候&#xff0c;你收到邮箱提示…

Linux命令行与shell脚本编程大全-3-4

第三部分高级shell脚本编程 第17章创建函数 17.1 脚本函数基础 17.1.1 创建函数 在bash shell 脚本中创建函数的语法有两种。第一种语法是使用关键字function&#xff0c;随后跟 上分配给该代码块的函数名&#xff1a; function name { commands } 17.1.2 使用函数 要在脚…

Multi-Head Attention详解

文中大部分内容以及图片来自&#xff1a;https://medium.com/hunter-j-phillips/multi-head-attention-7924371d477a 当使用 multi-head attention 时&#xff0c;通常d_key d_value &#xff08;d_model / n_heads&#xff09;&#xff0c;其中n_heads是头的数量。研究人员称…

01-Vue2 介绍与指令的使用

1. Vue核心 1.1. Vue简介 1.1.1. 官网 中文官网Vue.js - 渐进式 JavaScript 框架 | Vue.js (vuejs.org)https://cn.vuejs.org/ 英文官网Vue.js - The Progressive JavaScript Framework | Vue.js (vuejs.org)https://vuejs.org/ 1.1.2. 介绍与描述 VUE是构建于用户界面的渐进…

靶机渗透之sar

Name: Sar: 1Date release: 15 Feb 2020Author: LoveSeries: Sar Download: https://drive.google.com/open?id1AFAmM21AwiAEiVFUA0cSr_GeAYaxd3lQ 对于vulnhub中的靶机&#xff0c;我们都需先下载镜像&#xff0c;然后导入VM&#xff0c;并将网络连接改为NAT模式。首先我们…

UDP数据报套接字编程入门

目录 1.TCP和UDP的特点及区别 1.1TCP的特点 1.2UDP的特点 1.3区别 2.UDP Socket的api的介绍 2.1DatagramSocket API 2.2DatagramPacket API 3.回显客户端与服务器 3.1回显服务器 3.1.1UdpEchoServer类的创建 3.1.2服务器的运行方法start() 3.1.3main部分 3.1.4.完整…

C# CAD PaletteSet.Style各种外观和行为样式

ps.Style 是 Autodesk.AutoCAD.Windows.PaletteSet 类的一个属性&#xff0c;用于定义调色板集&#xff08;PaletteSet&#xff09;的各种外观和行为样式。它可以是 PaletteSetStyles 枚举类型的组合值 PaletteSetStyles 枚举中包含以下一些选项&#xff1a; NameEditable&am…

统计子矩阵

一、题目描述 P8783 [蓝桥杯 2022 省 B] 统计子矩阵 二、算法简析 2.1 二维前缀和 我们知道&#xff0c;只要确定了矩阵的左上顶点和右下顶点&#xff0c;一个矩阵就被固定了。因此&#xff0c;我们可以遍历这两个顶点&#xff0c;达到遍历所有子矩阵的目的&#xff0c;复杂…