浅谈View的滑动

要理解 View 的滑动,我们可以从「底层原理」和「生活类比」两个维度展开。先看透本质,再用故事串起来,保证大家都能秒懂。

一、底层原理:滑动的本质是什么?

View 的滑动,本质是改变 View 在屏幕上的「绘制位置」。而位置的计算,依赖于 Android 的「坐标系」和「View 的位置参数」。

1. 坐标系:屏幕上的 "地图"

Android 规定了一套坐标系:

  • 原点(0,0)在屏幕左上角;

  • 向右为 x 轴正方向,向下为 y 轴正方向(和数学坐标系的 y 轴相反,记住 "下为正" 即可)。

每个 View 在屏幕上的位置,都用 4 个核心参数描述:

  • left:View 左边缘到父容器左边缘的 x 距离;

  • top:View 上边缘到父容器上边缘的 y 距离;

  • right:View 右边缘到父容器左边缘的 x 距离(= left + 宽度);

  • bottom:View 下边缘到父容器上边缘的 y 距离(= top + 高度)。

这 4 个参数就像 View 的 "坐标标签",决定了它在父容器中的位置。

2. 滑动的底层逻辑:改参数 = 改位置

要让 View 滑动,本质是修改这些位置参数,或者通过「偏移绘制」让 View "看起来动了"。具体有 3 种核心方式,对应不同的参数修改逻辑:

滑动方式 底层修改的参数 效果
scrollTo/scrollBy 父容器的mScrollX/mScrollY 父容器 "滚动",内部子 View 的显示位置偏移(子 View 自身位置不变)
LayoutParams View 的布局参数(如marginxy 父容器重新布局,View 的left/top等参数改变(View 自身位置真的动了)
offsetLeftAndRight/offsetTopAndBottom View 的left/right/top/bottom 直接修改 View 的位置参数,强制刷新显示(View 自身位置真的动了)

3. 触摸事件:滑动的 "动力源"

用户的滑动操作(手指在屏幕上拖动),会被包装成「MotionEvent」对象(包含触摸点的 x/y 坐标)。这个事件会从 Activity 传递到 ViewGroup,再到具体的 View(类似 "快递派送")。

当 View 收到ACTION_MOVE事件(手指滑动中)时,就可以根据「起始位置」和「当前位置」的差值,计算出需要滑动的距离(dx、dy),然后用上面 3 种方式中的一种执行滑动。

二、生活类比:用 "房间 + 家具" 讲透滑动

把手机屏幕比作一个「房间」,View 是房间里的「家具」(比如桌子、椅子),滑动就是 "改变家具的位置" 或 "改变看家具的视角"。

1. 坐标系:房间的 "定位规则"

房间的左上角墙角是「原点(0,0)」:

  • 沿着墙向右(x 轴),每走 1 厘米记为 x=1;

  • 沿着墙向下(y 轴),每走 1 厘米记为 y=1。

每个家具(View)的位置用 4 个参数标记:

  • left:家具左边缘到房间左墙的距离;

  • top:家具上边缘到房间上墙的距离;

  • right:家具右边缘到房间左墙的距离(= left + 家具宽度);

  • bottom:家具下边缘到房间上墙的距离(= top + 家具高度)。

比如一张桌子左边缘离左墙 30cm,上边缘离上墙 40cm,宽 50cm,高 60cm,那它的参数就是:left=30,top=40,right=80(30+50),bottom=100(40+60)。

2. 触摸事件:"推动家具的手"

用户的手指滑动,就像 "用手推家具":

  • 手指刚碰到家具(ACTION_DOWN):记录初始位置(比如手指在桌子上的点,距离房间左墙 x1=50,上墙 y1=60);

  • 手指拖动(ACTION_MOVE):实时记录当前位置(x2=70,y2=80),计算出滑动距离 dx=20(70-50),dy=20(80-60);

  • 手指离开(ACTION_UP):结束滑动。

这个 "dx、dy" 就是家具需要移动的距离,接下来就是 "怎么移" 的问题 ------ 对应 3 种滑动方式。

3. 三种滑动方式:怎么移动家具?

我们用 "移动桌子" 的场景,对应三种底层实现:

方式 1:scrollTo/scrollBy("人不动,移动视线")

底层逻辑 :父容器(比如房间地面)记录一个 "滚动偏移量"(mScrollX/mScrollY),绘制子 View 时,会根据这个偏移量 "偏移绘制位置"。子 View 的left/top没变,只是看起来动了。

生活类比

你站在房间中间看桌子(初始位置),现在你不移动桌子,也不自己动,而是让 "视线平移"(比如戴了一副可以左右滑动的眼镜)。此时桌子在地面上的实际位置(left/top)没变,但你看到的桌子位置 "偏移" 了。

举例

手机里的ListView滑动时,其实是ListView(父容器)通过scrollBy(dx, dy)改变mScrollY,每个 item(子 View)的left/top没变,但绘制时会随父容器的偏移而 "向上 / 向下移动",看起来就是列表在滚。

方式 2:LayoutParams("移动桌子的地脚")

底层逻辑 :修改 View 的布局参数(比如marginLeftwidth),父容器会触发requestLayout()(重新测量、布局、绘制),最终改变 View 的left/top,实现位置变化。

生活类比

桌子的的脚是可以调节的(类似LayoutParams)。你想把桌子向右移 10cm,就把左侧的脚移动 10cm(增加marginLeft)。此时房间会重新计算桌子的位置(left = 原来的 left + 10),桌子的实际位置真的变了。

举例

拖动一个按钮时,我们可以通过LayoutParams修改它的leftMargintopMargin,按钮会真的在父容器中 "平移",left/top参数也会同步更新。

方式 3:offsetLeftAndRight("直接推桌子")

底层逻辑 :直接修改 View 的left/right(水平方向)或top/bottom(垂直方向),然后调用invalidate()刷新绘制。View 的位置参数直接改变,无需父容器重新布局。

生活类比

你直接用手推桌子,桌子在地面上的实际位置(left/top)被你硬生生推远了 10cm。此时桌子的left从 30 变成 40,right从 80 变成 90,位置参数直接变了,房间不需要重新规划布局,桌子自己动了。

举例

自定义一个可拖动的 View 时,在ACTION_MOVE中调用offsetLeftAndRight(dx),可以直接让 View 随手指移动,效率很高(无需重新测量布局)。

三、总结:三种方式的核心区别

方式 本质 适用场景 特点
scrollTo/scrollBy 父容器偏移绘制,子 View 位置不变 滚动列表(ListView/RecyclerView)、滑动面板 效率高,适合大量子 View 滑动
LayoutParams 改布局参数,父容器重新布局 拖动 View(如按钮、悬浮窗) 位置真的改变,适合单个 View 移动
offset 系列 直接改位置参数,强制刷新 快速拖动单个 View 效率最高,适合高频滑动(如手势拖动)

最后:一句话记住

View 滑动的本质是 "改变绘制位置",要么通过父容器的偏移让它 "看起来动了"(scroll),要么通过修改自身参数让它 "真的动了"(LayoutParams/offset)。就像移动家具,要么你戴个偏移眼镜看它(scroll),要么直接推它(offset),要么移动桌子的脚让它换位置(LayoutParams)------ 核心都是 "位置变了"。

相关推荐
selt79121 小时前
Redisson之RedissonLock源码完全解析
android·java·javascript
Yao_YongChao21 小时前
Android MVI处理副作用(Side Effect)
android·mvi·mvi副作用
非凡ghost1 天前
JRiver Media Center(媒体管理软件)
android·学习·智能手机·媒体·软件需求
席卷全城1 天前
Android 推箱子实现(引流文章)
android
齊家治國平天下1 天前
Android 14 系统中 Tombstone 深度分析与解决指南
android·crash·系统服务·tombstone·android 14
maycho1231 天前
MATLAB环境下基于双向长短时记忆网络的时间序列预测探索
android
思成不止于此1 天前
【MySQL 零基础入门】MySQL 函数精讲(二):日期函数与流程控制函数篇
android·数据库·笔记·sql·学习·mysql
brave_zhao1 天前
达梦数据库(DM8)支持全文索引功能,但并不直接兼容 MySQL 的 FULLTEXT 索引语法
android·adb
sheji34161 天前
【开题答辩全过程】以 基于Android的网上订餐系统为例,包含答辩的问题和答案
android
easyboot1 天前
C#使用SqlSugar操作mysql数据库
android·sqlsugar