【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.rhkb.cn/news/265863.html

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

相关文章

docker 容器修改端口和目录映射

一、容器修改端口映射 一般在运行容器时&#xff0c;我们都会通过参数 -p&#xff08;使用大写的-P参数则会随机选择宿主机的一个端口进行映射&#xff09;来指定宿主机和容器端口的映射&#xff0c;例如 docker run -it -d --name [container-name] -p 8088:80 [image-name]…

Flask基础学习3

参考视频&#xff1a;41-【实战】答案列表的渲染_哔哩哔哩_bilibili flask 实现发送短信功能 pip install flask-mail # 安装依赖 我这里用登录的网易邮箱获取的授权码&#xff08;登录QQ邮箱的授权码总是断开收不到邮件&#xff09;&#xff0c; # config # config mail MAI…

云尚办公-0.0.3

5. controller层 import pers.beiluo.yunshangoffice.model.system.SysRole; import pers.beiluo.yunshangoffice.service.SysRoleService;import java.util.List;//RestController&#xff1a;1.该类是控制器&#xff1b;2.方法返回值会被写进响应报文的报文体&#xff0c;而…

Dockerfile(4) - RUN 指令详解

RUN 运行命令 shell 形式 命令在 shell 中运行Linux 上默认为 /bin/sh -cWindows 上 cmd /S /C RUN <command> exec 形式 RUN ["executable", "param1", "param2"] 必须双引号&#xff0c;不能是单引号 两种写法的实际栗子 RUN …

java高级——反射

目录 反射概述反射的使用获取class对象的三种方式反射获取类的构造器1. 获取类中所有的构造器2. 获取单个构造器 反射获取构造器的作用反射获取成员变量反射变量赋值、取值获取类的成员方法反射对象类方法执行 反射简易框架案例案例需求实现步骤代码如下 反射概述 什么是反射 反…

骨传导耳机什么牌子的好?揭秘成功法则与避坑策略

科技进步带来了骨传导耳机的兴起&#xff0c;这种耳机以其独特的优势而受到越来越多消费者的青睐。与传统的入耳式相比&#xff0c;骨传导耳机通过骨头传递声音&#xff0c;避免了对耳道的直接压迫&#xff0c;减少了对听力的潜在伤害。同时它们允许用户在享受音乐的同时&#…

unity使用Registry类将指定内容写入注册表

遇到一个新需求&#xff0c;在exe执行初期把指定内容写入注册表&#xff0c;Playerprefs固然可以写入&#xff0c;但是小白不知道怎么利用Playerprefs写入DWORD类型的数据&#xff0c;因此使用了Registry类 一. 对注册表中键的访问 注册表中共可分为五类 一般在操作时&#…

中电金信精选好文,全篇划重点~

从硅谷银行件看中美金融监管差异 2023年3月&#xff0c;硅谷银行事件引发全球金融市场震荡&#xff0c;该事件除了给美联储从暴力降息到暴力升息的极限操作敲响一记警钟之外&#xff0c;更是暴露出美国金融监管漏洞重重的现状。相较之下&#xff0c;近年来我国不断深化金融监管…

Linux NFC 子系统剖析

1.总览 linux源码中NFC在net/nfc下&#xff0c;文件结构如下图&#xff1a; hci&#xff1a;Host Controller Interface 主要是针对NFC的主机-控制器接口协议 nci&#xff1a;NFC Controller Interface 主要是NFC的控制器接口协议&#xff0c;用于NFCC(NFC Controller)和DH(…

EAP-TLS实验之H3C MSR2600-10-X1配置相关

H3C MSR2600充当802.1x流程中的NAS&#xff08;Network Access System&#xff09;角色&#xff0c;一般负责实际待验证的设备与认证服务器之间沟通的桥梁&#xff08;当然也可以配置成认证服务器角色&#xff09;工作。在挑选购买支持802.1x的路由器或交换机时需要跟厂家明确是…

【python基础学习08课_函数的嵌套、内置函数等】

一、函数 1、函数的注释 1&#xff09;如何写注释 """ 函数的注释 -- 说明这个函数的作用&#xff0c;以及参数的诠释"""三个引号之间可以注释例如&#xff1a;这是一段注释"""这是一段注释""" 2&#xff09;如…

SVN教程-SVN的基本使用

SVN&#xff08;Apache Subversion&#xff09;是一款强大的集中式版本控制系统&#xff0c;它在软件开发项目中扮演着至关重要的角色&#xff0c;用于有效地跟踪、记录和管理代码的演变过程。与分布式系统相比&#xff0c;SVN 的集中式架构使得团队能够更加协同地进行开发&…

计算机网络之应用层

域名由点和标号(label)组成, 点分割的即是标号每个标号不超过63个字符,总计不超过255个字符, 并且不区分大小写顶级域名TLD(Top Level Domain)分为三类, 国家顶级域名nTLD, 通用顶级域名gTLD, 基础结构域名ID(Infrastructure Domain). 基础结构域名只有一个即 arpa. 用于反向域…

MyBatis 学习(二)之 第一个 MyBatis 案例

目录 1 配置 MyBatis 方式 1.1 XML 配置文件 1.2 Java 注解配置 1.3. Java API 配置 2 在 MySQL 中创建一张表 3 创建一个基于 Maven 的 JavaWeb 工程 4 编写 User 实体类 5 创建 Mybatis 全局配置文件 6 编写一个 DAO 或 Mapper 接口 7 编写 SQL 映射配置文件&#…

Docker部署前后端服务示例

使用Docker部署js前端 1.创建Dockerfile 在项目跟目录下创建Dockerfile文件&#xff1a; # 使用nginx作为基础镜像 FROM nginx:1.19.1# 指定工作空间 WORKDIR /data/web# 将 yarn build 打包后的build文件夹添加到工作空间 ADD build build# 将项目必要文件添加到工作空间&a…

IPD(集成产品开发)—核心思想

企业发展到一定阶段就会遇到管理瓶颈&#xff0c;IPD流程是一种高度结构化的产品开发流程&#xff0c;它集成了业界很多优秀的产品开发方法论&#xff0c;像搭积木一样的组合成一种非常有效的流程。如果我们能根据企业的规模和行业特点&#xff0c;对全流程的IPD进行合适的裁剪…

【Python】FastAPI 项目创建 与 Docker 部署

文章目录 前言&需求描述1. 本地FastAPI1.1 Python 环境准备1.2 本地 Pycharm 创建FastAPI项目 2. Python FastAPI 部署2.1 服务器配置Python环境2.2.1 下载与配置Git、Pyenv等工具2.2.2 下载与配置Python 2.2 FastAPI 打包成镜像2.2.1 项目准备所需环境文件2.2.2 编写Docke…

【MATLAB】ICEEMDAN_ MFE_SVM_LSTM 神经网络时序预测算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~也可转原文链接获取~ 1 基本定义 ICEEMDAN是指“改进的完全扩展经验模态分解与自适应噪声”&#xff08;Improved Complete Ensemble Empirical Mode Decomposition with Adaptive Noise&#xff09;&#xff0c;它是CEEM…

行为树入门:BehaviorTree.CPP Groot2练习(叶子节点)(2)

以《行为树BehaviorTree学习记录1_基本概念》练习。 1 SequenceNode顺序控制节点 代码下载 git clone https://gitee.com/Luweizhiyuan2020/ros2_bt.git例程 1.1 sequence 顺序执行 下载版本SequenceNode1。 1.2 ReactiveSequence 异步执行 注意&#xff1a; ①only a…

金三银四面试必问:Redis真的是单线程吗?

文章目录 01 Redis中的多线程1&#xff09;redis-server&#xff1a;2&#xff09;jemalloc_bg_thd3&#xff09;bio_xxx&#xff1a; 02 I/O多线程03 Redis中的多进程04 结论▼延伸阅读 由面试题“Redis是否为单线程”引发的思考 作者&#xff1a;李乐 来源&#xff1a;IT阅读…