Android中View的滑动机制分析
来源:互联网 发布:软件测试就业前景 编辑:程序博客网 时间:2024/04/29 09:38
1、View基础知识
1.1 View介绍
View是Android中所有控件的基类,是一种界面层控件的抽象,它代表了一个控件。ViewGroup,控件组,内部包含了多个控件,即一组View。ViewGroup继承View,意味着View可以是单个控件,也可以是由多个控件组成的一组控件,形成了View的树结构。
举例说明View和ViewGroup的关系。Button是个View,LinearLayout是一个View,而且还是一个ViewGroup,而ViewGroup内部可以是有子View的,这个子View同样还可以是ViewGroup,以此类推。
View的层级结构:
1.2 View的位置参数
View的位置主要由它的四个顶点来决定,分别对应于View的四个属性:top,left,right,bottom。从android3.0开始 View增加了额外的几个参数:x,y,translationX,和translationY。
- top:左上角纵坐标
- left:左上角横坐标
- right:右下角横坐标
bottom:右下角纵坐标
View的宽高和坐标的关系:width = right - leftheight = bottom - top
- x和y:View左上角的坐标
- translationX和translationY:View左上角相对于父容器的偏移量
参数换算关系:x = left + translationXy = top + translationY
1.3 MotionEvent和TouchSlop
(1)MotionEvent
在手指接触屏幕后所产生的一系列事件中,典型的事件类型有如下几种:
- ACTION_DOWN : 手指刚接触屏幕
- ACTION_MOVE : 手指在屏幕上移动
- ACTION_UP : 手指离开屏幕的一瞬间
正常情况下,一次手指触摸屏幕的行为会触发一系列点击事件,例如:
- 点击屏幕后离开,事件序列为DOWN->UP
- 点击屏幕滑动一会松开,事件序列为DOWN->MOVE->…->MOVE->UP
通过MotionEvent的对象我们可以得到点击事件发生的x和y坐标。因此,系统系统提供了两组方法:getX/getY和getRawX/getRawY。它们的区别很简单,getX和getY返回的是相当于当前View左上角的x和y坐标,而getRawX和getRawY返回的是相对于手机屏幕左上角的x和y坐标。
(2)TouchSlop
TouchSlop是系统所能识别出的被认为是滑动的最小距离。当我们在处理滑动时,就可以利用这个常量做一些过滤,比如当两次滑动事件的滑动距离小于这个值,我们就可以认为未滑动。
这个值是一个常量与设备有关,获取这个常量方法:
ViewConfiguration.get(getContext()).getScaledTouchSlop()
1.4 VelocityTracker、GestureDetector和Scroller
(1)VelocityTracker速度追踪
VelocityTracker是速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直方向的速度。它的使用很简单,首先,在View的onTouchEvent方法中追踪当前单击事件的速度:
VelocityTracker vt=VelocityTracker.obtain();vt.addMovement(event);
接着就想知道当前的滑动速度时,就可以采用这个方式来获得速度:
//计算速度vt.computeurrentVelocity(100);//100表示时间间隔为100ms//获取速度Int x=(int)vt.getXVelovity();Int y=(int)vt.getYVelovity();
当不需要使用它的时候,用clear方法来重置回收内存:
vt.clear();vt.recycle();
速度 = (终点位置 - 起始位置)/ 时间段
(2)GestureDetector手势检测
手势检测,用于辅助检测用户的单击,滑动,长按,双击等行为。
首先,我们创建一个GestureDetector 对象并实现onGestureListener接口,根据需要我们还可以实现onDoubleTapListener从而能够监听双击行为:
GestureDetector mGestureDetector =new GestureDetector(this);//解决长按屏幕无法拖动的现象mGestureDetector.setIsLongPressEnable(false);
接着实现目标View的onTouchEvent方法,在待监听的View的onTouchEvent方法中添加如下实现:
Boolean consume=mGestureDetector.onTouchEvent(event);
做完上两步,我们就可以有选择地实现OnGestureListener和OnDoubleTapListenter中的方法了。另外,在实际开发中,我们可以不使用GestureDetector,完全可以自己在View的onTouchEvent方法中实现所需的监听,当需要监听双击这种行为时,再使用GestureDetector.
(2)Scroller弹性滑动现象
弹性滑动对象,用于实现View的弹性滑动。我们知道,当使用View的scrollTo/scrollBy方法进行滑动时,其过程是可以瞬间完成的,这个没有过渡效果的滑动用户体验不好。这个时候,我们就可以使用Scroller来实现由过渡效果的滑动,其过程不是瞬间完成的,而是在一定时间间隔内完成的。Scroller配合view的computeScroll就能完成这个功能。它的典型代码固定如下:
Scroller scroller =new Scroller(mContext);//缓慢滑动到制定位置private void smoothScrollTo(int destX, int destY) { int scrollX=getScrollX(); int delta = destX - scrollX; //1000ms内滑向destX,效果就是慢慢滑动 mSroller.startScroll(scrollX, 0, delta, 0, 1000); invalidate();}@Overridepublic void computeScroll(){ if(mSroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX().mScroller.getCurrY()); postInvalidate(); }}
2、View的滑动
不管一些滑动效果多么绚丽,归根到底,他们都是由不同的滑动外加一些特效所组成的。因此。掌握滑动的方法是实现绚丽的自定义控件的基础。通过三种方式可以实现View的滑动: 第一种事通过View 本身提供的scrollTo/scrollBy 方法来实现滑动;第二种是通过动画给View施加平移来实现滑动;第三种是通过改变View的LayoutParams使得View重新布局从而实现滑动。
2.1 使用scrollTo/scrollBy
为了实现View的滑动,View提供了专门的方法来实现这个功能。那就是scrollTo/scrollBy。
/** * Set the scrolled position of your view. This will cause a call to * {@link #onScrollChanged(int, int, int, int)} and the view will be * invalidated. * @param x the x position to scroll to * @param y the y position to scroll to */ 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(); } } }/** * Move the scrolled position of your view. This will cause a call to * {@link #onScrollChanged(int, int, int, int)} and the view will be * invalidated. * @param x the amount of pixels to scroll by horizontally * @param y the amount of pixels to scroll by vertically */ public void scrollBy(int x, int y) { scrollTo(mScrollX + x, mScrollY + y); }
经过分析源码得知,scrollBy实际上也是调用了scrollTo方法,而scrollTo则实现了基于所传递参数的绝对滑动。利用scrollTo和scrollBy来实现View的滑动,这不困难,但我们要知道。滑动过程中,View内部的两个属性mScrollX和mScrollY的改变规则。这两个属性可以通过getScrollX和getScrollY方法分别得到。
在滑动过程中,mScrollX的值总是等于View左边缘和View内容左边缘在水平方向上的距离,而mScrollY的值,总是等于View上边缘和View内容上边缘的竖直方向的距离。scrollTo和scrollBy只能改变View的内容的位置,而不能改变View在布局中的位置。mScrollX和mScrollY的单位是像素,并且当View左边缘在View内容左边缘右边时,mScrollX为正值,反之为负值。当View上边缘在View内容上边缘的下边时,mScrollY为正值,反之为负值。换句话说,如果从左向右滑动,那么mScrollX为负值,反之为正值。如果从上往下滑动,mScrollY为负值,反之为正值。
2.2 使用动画
使用动画来移动View,主要操作View的translationX和translationY属性,即可以使用传统的View动画来完成,也可以采用属性动画,如果采用属性动画的话,为了兼容3.0一下版本,需要采用开源动画库nineoldandroids。
使用传统的View动画,可以xml中的指定translate,平移动画。
采用属性动画,更简单了,比如:
ObjectAnimator.ofFloat(targetView,”translationX”,0,100).setDuration(100).start();
*注意
View动画是对View的影像的操作,它并不能真正改变View的位置包括宽/高,造成View无法响应点击事件,因为View的真身还在原来的位置,并且如果希望动画后的状态得以保留。还必须将fillAfter的属性设置为true,否则动画完成后,其动画效果就会消失。
属性动画解决兼容问题,不存在此问题。
2.3 改变布局参数
改变布局参数,即改变LayoutParams比较好理解,比如我们想把一个Button向右平移100px,我们主要将这个Button的LayoutParams里的marginLeft参数的值增加100px即可。
MarginLayoutParams params = (MarginLayoutParams )mButton1.getLayoutParams();Params.width +=100;params.leftMargin +=100;mButton1.requestLayout();//或者 mButton1.setLayoutParams(params);
2.4 各种滑动方式的对比
scrollTo/scrollBy:操作简单。适合View滑动,滑动后支持响应事件。
动画:操作简单,主要适用于没有交互的View和实现复杂的动画效果。推荐使用nineoldandroids兼容Android 3.0以下版本。
改变布局参数:操作稍微复杂,适用于有交互的Veiw.
3、弹性滑动
如果View生硬的滑动过去,用户体验实在太差。因此,我们要实现渐进式滑动。渐进式滑动有一个共同思想:将依次大的滑动分成若干次小的滑动,并在一个时间段内完成。常用的弹性滑动方式有Scroller,使用动画,还有延时策略。
(1) 使用Scroller
Scroller方法已经分析过了,如果有兴趣可以分析它的源码。原理如下:
Scroller本身并不能实现View的滑动,它需要配合View的computeScroll方法才能完成弹性滑动的效果,它不断的让View重绘,而每一次重绘滑动起始时间会有一个时间间隔,通过这个时间间隔Scroller就可以得出View的当前的滑动位置,知道了滑动位置就可以通过scrollTo方法来完成View的滑动。就这样,View的每一次重绘都会导致View进行小幅度的滑动,而多次的小幅度滑动就组成了弹性滑动。
(2) 通过动画
动画本身就是一种渐进过程,因此通过它来实现滑动天然就具有弹性效果,比如下面代码可以让一个View的内容在100ms内向左移动100ms.
ObjectAnimator.ofFloat(targetView,”translationX”,0,100).setDuration(100).start();
不过这里想说的并不是这个问题,我们可以利用动画特性来实现一些动画不能实现的效果。还拿scrollTo来说,我们也想模仿Scroller来实现View的弹性滑动。那么利用动画的特性。我们可以采用如下方式来实现。
final int startX=0;final int deltax=100;valueAnimator animator = ValueAnimator.ofInt(0,1).setDuration(1000);animator.addUpdateListener(new AnimatorUpdateListener(){@Overridepublic void onAnimatorUpdate(ValueAnimator animator){ float fraction=animator.getAnimatedFraction(); mButton1.scrollTo(startX+(int)(deltaX*fraction),0) }});animator.start();
上述代码,我们的动画本质上没有作用于任何对象上,它只是在1000ms内完成了整个动画过程。利用这个特性,我们就可以在动画的每一帧到来时获取动画完成比例,然后再根据这个比例计算当前View的滑动距离。注意,这里的滑动特性针对的是View的内容而非View本身。它与Scroller比较类似,都是通过改变一根百分比配合scrollTo方法来完成View的滑动。需要说明一点,采用这种方法除了能够完成弹性滑动以外,还可以实现其动动画效果,我们可以在onAnimationUpdate方法中加上我们想要的操作。
(3) 使用延时策略
延时策略的核心思想是通过发送一系列延时消息,从而达到渐进式的效果,具体来说,可以使用Handler 或View的postDelayed方法,也可因使用线程的sleep方法。使用起来比较简单。
4、实战手滑动效果
(1)效果说明
这是一个自定义View,拖动它可以让它在整个屏幕上随意滑动。
(2)实现思路
实现思路较为简单,重写它的onTouchEvent方法并处理ACTION_MOVE事件,根据两次滑动之间的距离就可以实现它的滑动了。为了实现全屏滑动,采用动画实现。这个效果无法通过scrollTo实现。改变布局方式也是可以的。
(3)代码实现
package com.chunsoft.testviewevent.ui;import android.annotation.SuppressLint;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.ViewConfiguration;import com.nineoldandroids.view.ViewHelper;//继承TextViewpublic class TestButton extends TextView { private static final String TAG = "TestButton"; private int mScaledTouchSlop; // 分别记录上次滑动的坐标 private int mLastX = 0; private int mLastY = 0; public TestButton(Context context) { this(context, null); } public TestButton(Context context, AttributeSet attrs) { super(context, attrs); init(); } public TestButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { //获取滑动最小距离,超过这个常量则认为滑动了 mScaledTouchSlop = ViewConfiguration.get(getContext()) .getScaledTouchSlop(); Log.d(TAG, "sts:" + mScaledTouchSlop); } @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getRawX(); int y = (int) event.getRawY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastX; int deltaY = y - mLastY; Log.d(TAG, "move, deltaX:" + deltaX + " deltaY:" + deltaY); //这里使用了动画兼容库,nineoldandroids int translationX = (int)ViewHelper.getTranslationX(this) + deltaX; int translationY = (int)ViewHelper.getTranslationY(this) + deltaY; ViewHelper.setTranslationX(this, translationX); ViewHelper.setTranslationY(this, translationY); break; } case MotionEvent.ACTION_UP: { break; } default: break; } mLastX = x; mLastY = y; return true; }}
首先通过getRawX和getRawY方法获取手指当前的坐标。其次,获取两次活动之间的位移;再,有了位移通过nineoldandroids中的ViewHelper类的setTranslationX和setTranslationY方法实现移动,如果给View添加o nClick事件3.0以下版本无法在新位置响应用户的点击。
- Android中View的滑动机制分析
- Android中View的滑动
- Android 中 View 的滑动
- Android View事件分发机制及View的滑动冲突
- Android 中View的绘制机制源码分析 三
- Android 中View的绘制机制源码分析 一
- Android 中View的绘制机制源码分析 二
- Android 中View的绘制机制源码分析 四
- Android 中View的绘制机制源码分析
- 菜鸟都能理解的Android中View的事件分发机制及滑动冲突处理
- 通俗理解Android中View的事件分发机制及滑动冲突处理
- 通俗理解Android中View的事件分发机制及滑动冲突处理
- Android中View的滑动冲突
- Android中View的滑动方式
- Android View的事件分发机制和滑动冲突解决方案
- Android View的事件分发机制与滑动冲突解决方案
- Android View的事件分发机制和滑动冲突解决方案
- Android View的事件分发机制和滑动冲突解决方案
- Maven工程下打War包
- 论文心得:BatchNorm及其变体
- powerdesigner中sql脚本反向生成pdm
- 源码研究mycat之mysql通信协议篇之握手认证协议
- 某老师给我的javascript学习建议
- Android中View的滑动机制分析
- MySQL存储过程详解
- 双向链表的插入及删除图解
- powerdesigner中使用mysql逆向工程生成pdm,附带解决注释乱码
- 欢迎使用CSDN-markdown编辑器
- MYSQL关联两表或多表执行修改删除操作
- c 程序设计语言 第二版 练习题 5-4
- JS模块化编程
- 蓝桥杯 生日蜡烛