View 的滑动原理和实现方式

开发中,为了增加更多炫丽的效果,我们经常在应用中添加滑动效果,今天就来分析一下 View 中滑动效果的实现原理以及几种常见的实现方式。

一、滑动原理

1. Android 中的坐标系

View 基础 中我们提到了 View 中的 X、Y、mLeft、mTop... 等属性,其中这些属性的值都是相对坐标系来说的,Android 中有两种坐标系,这里一一来简单说一下

Android 坐标系: 以屏幕左上角为坐标原点,向右为 X 轴正方向,向下为 Y 轴正方向,MotionEvent 的 getRawX()、getRawY() 方法获取的是点击位置在 Android 坐标系中的坐标

视图坐标系: 以当前控件左上角为坐标原点,向右为 X 轴正方向,向下为 Y 轴正方向,MotionEvent 的 getX()、getY() 方法获取的是点击位置在视图坐标系中的坐标,View 的 mLeft、mTop 等属性也是 View 在父控件的视图坐标系中的坐标

2. 滑动原理

了解了 Android 中的坐标系,再说 View 的滑动原理,其实滑动的原理与动画效果的实现非常相似,都是通过不断改变 View 的坐标来实现这一效果。所以要实现滑动效果就必须要监听用户的触摸事件,并根据事件传入的坐标,动态且不断的改变 View 的坐标,从而实现 View 跟随用户触摸的滑动而滑动

二、滑动方式

滑动过程中触摸坐标改变的监听功能我们可以通过重写 onTouchEvent() 方法实现,onTouchEvent 中根据事件类型确定当前的触摸坐标,如果如果需要实现滑动,再调用实现滑动的方法。

通过滑动原理我们知道所有修改 View 坐标的方法都可以实现滑动功能,而改标 View 坐标的方式有很多种,这里我们就介绍几种常见的滑动方式

1. 通过 layout() 方法

View 中通过滑动事件中计算滑动距离,调用 layout 方法,其原理是改变 View 的 mLeft、mTop 等坐标,将初始位置值及偏移量传入,即需要滑动到的位置的坐标,由上篇文章介绍的 layout 过程 可知,View 完成重新布局后,就达到了移动 View 的效果

2. offsetLeftAndRight() 方法和 offsetTopAndBottom()

offsetLeftAndRight(offset) offsetTopAndBottom(offset) 方法的原理是通过修改 View 的 mLeft、mTop 坐标完成滑动。

在滑动事件中,得到偏移量,调用 offsetLeftAndRight 和 offsetLeftAndRight 方法实现滑动

3. 通过修改 LayoutParams 实现滑动

通过改变 View 的 LayoutParams 参数中的 margin 值,在父 View 的布局过程中会将 View 的坐标加上相应的 margin 偏移量,从而改变 View 在父容器中的坐标,完成滑动

4.使用动画

  • 使用 View 动画,只能改变 View 内容的位置,不能改变 View 的真正坐标

  • 使用属性动画完成滑动,在动画执行的过程中,通过改变 View 的真正坐标实现滑动

这里总结一下,以上介绍的前三种滑动方式以及使用属性动画完成滑动,这些方式都是通过改变 View 在其父容器中的坐标从而实现的滑动,实现的是控件整体发生了滑动

5. View 的 scrollTo()、scrollBy() 方法实现滑动

scrollTo、scrollBy 实现的是 View 内容的滑动,是区别于上面提到的控件整体滑动的,其效果是 View 控件并没有滑动,而是控件上绘制的内容在控件范围内发生了滑动

在发生滑动事件时,通过调用 scrollTo 或者 scrollBy 方法完成 View 内容的滑动。

如果想要通过 scrollTo/scrollBy 方法实现 View 控件的滑动,就在需要滑动的时候,调用 view.getParent().scrollTo ,通过让其父 View 滑动其父 View 中的内容,实现该 View 的滑动效果。

并且还有一点需要注意:如果通过其父 View 调用 scrollTo/scrollBy 方法改变所以子 View 在父 View 中的位置时,并没有修改子 View 真正的坐标位置,而是修改了坐标的偏移量 translateX、translateY 、x、y 的值,其中 x = mLeft + translateX ,y 同理

scrollTo/scrllBy 方法的区别

  • scrollTo(int x,int y) 实现的是相对于参数的绝对滑动,即滑动结果为相对于内容的原始位置,原始位置就是 mScrollX 和 mScrollY 都是 0 的位置,滑动后 mScrollX = x ; mScrollY = y;

  • scrollBy(int x,int y) 中调用了 scrollTo() 方法,不过 scrollBy() 实现的是相对于当前位置的相对滑动,即相对于当前 mScrollX 和 mScrollY 的值进行的滑动,滑动结果为 mScrollX = x + mScrollX(滑动前) mScrollY = y + mScrollY(滑动前)

View 的 mScrollX 和 mScrollY 参数

  • mScrollX 可由 getScrollX() 方法得到,表示的是,View 左边缘跟 View 内容左边缘在水平方向的距离,并且,如果 View 左边缘在内容左边缘左侧时该值为负,View 左边缘在内容左边缘右侧时该值为正。

  • mScrollY 可由 getScrollY() 方法得到,表示的是,View 上边缘跟 View 内容上边缘在竖直方向的距离,并且,如果 View 上边缘在内容上边缘上侧时该值为负,View 左边缘在内容左边缘右侧时该值为正。

6. 通过 Scroller 类实现滑动

Scroller 实现滑动的原理是通过调用 scrollTo 方法实现的,所以实现的也是内容的滑动,有关内容滑动的知识请看上一节通过 scrollTo 方法实现滑动效果

实现方式:

  1. 初始化 Scroller ,调用 Scroller 的 startScroll 方法,将 X,Y 方向的初始、需要偏移值、滑动初始时间以及滑动时间传入,如果不传入滑动时间,则默认时间为 250 毫秒,接着调用 invalidate() 方法执行重绘

  2. 重写 View 的 cumputeScroll 方法,在重绘过程中调用该方法。其中通过调用 Scroller 对象的 computeScrollOffset 方法测量当前时间对应的应该发生滑动值,并将最新的需要滑动的值保存,该方法返回是否滑动完成的 boolean 值,true 滑动未完成,返回 false 表示滑动已经完成。computeScrollOffset 方法中通过时间的流逝计算当前需要滑动到的位置。

  3. cumputeScroll 中,如果滑动未完成,通过 scroller 的 getScrollX getScrollY ,得到当前需要滑动到的位置,调用 View 的 scrollTo 方法滑动到指定位置,

  4. 再次调用 invalidate 方法,实现 View 的重绘。

再总结一下 Scroller 实现滑动的过程,invalidate 方法执行重绘,重绘过程中会调用 cumputeScroll 方法,cumouteScroll 方法中又会通过 Scroller 来计算当前需要滑动到的位置并调用滑动方法实现滑动,接着在调用 invalidate 方法重绘,从而循环绘制,直到滑动完成

7. ViewDragHelper 实现滑动

ViewDragHelper 是 Google 提供的一个类,通过 ViewDragHelper 来实现各种不同的滑动,拖放需求

  1. ViewGroup 中初始化,传入 ViewGroup 和 回调 CallBack ,Callback 中的方法返回值表示哪个 View 可以被移动
  2. 拦截事件,将 View 的触摸事件使用 ViewDragHelper 的方法拦截
  3. 重写 View 的 computeScroll 方法
  4. 在回调中重写监听方法,clampViewPositionHorizontal clampViewPositionVertical 实现横向纵向滑动
  5. 可以通过重写不同状态的回调实现在不同回调时的实现。

好啦,到这里有关 View 滑动的内容就介绍的差不多了,其中要区分滑动实现的是 View 控件的滑动,还是 View 内容的滑动。并且通过动画、Scroller、Handler/postDelay 等方式还可以实现弹性滑动的效果,普通滑动都是瞬时完成的,而弹性滑动则是渐进完成的,如果不太了解弹性滑动的只需要实现前面提到的几种滑动方式,一对比就明白啦。

请期待下篇文章触摸事件的分发和处理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容

  • 什么是View View 是 Android 中所有控件的基类。 View的位置参数 View 的位置由它的四个顶...
    acc8226阅读 1,139评论 0 7
  • 内容是博主照着书敲出来的,博主码字挺辛苦的,转载请注明出处,后序内容陆续会码出。 当了解了Android坐标系和触...
    Blankj阅读 6,593评论 3 61
  • 在Android中想要实现实现滑动有很多方法,这篇博客将提供一些实现滑动的思路,希望可以帮助到有需要的人。 一、A...
    冰鉴IT阅读 14,370评论 11 77
  • 目前移动设备流行,我们要在如此小的屏幕上尽可能给用户展现更多的内容,就需要在应用上通过滑动来显示和隐藏部分内容,V...
    shenhuniurou阅读 950评论 2 1
  • 1. wait()函数 头文件:#include / 函数的一般形式: 参数设置: 返回值:如果执行成功则返回子...
    一叶之界阅读 20,990评论 0 2