View是Android中所有控件的基类,不管是Button和TextView还是复杂的RelativeLayout等,它们的共同基类都是View。所以说,View是一种界面层控件的一种抽象,它代表了一个控件。除了View还有ViewGroup,内部包含了许多个控件,即一组View。在Android的设计中,ViewGroup也是继承了View,这就意味着View本身就是可以是单个控件也可以是多个控件组成的一组控件,通过这种关系就形成了View的树结构,这和Web前端中的Dom树的概念都是相似的。根据这个概念,我们知道,Button显然是个View,而LinearLayout不但是View而且还是一个ViewGroup,而ViewGroup内部是可以有子View的,这个子View同样还可以是ViewGroup。
View的位置主要是由它的四个顶点来决定的,分贝对应于View的四个属性:top,left,right,bottom。
从Android3.0开始,View增加了几个额外的参数:x,y,translationX,translationY。其中X和Y是View左上角的坐标,而translationX和translationY是View左上角相对于父容器的偏移量。这几个参数也是相对于父容器的坐标,并且translationX和translationY的默认值是0。View的几个参数之间的换算关系如下:
x = left + translationX
y = top + translationY
需要注意的是,View在平移过程中,top和left表示的是原始左上角的位置信息,其值并不会发生改变,此时发生的改变的是x,y,translationX和translationY这四个参数。
ACTION_DOWN
ACTION_MOVE
ACTION_UP
一次触摸屏幕的过程大约是这样
ACTION_DOWN -> ACTION_MOVE ... ACTION_MOVE ->ACTION_UP
上述三种情况是典型的事件序列,同时通过MotionEvent对象我们可以得到点击事件发生的
X,Y坐标。getX,getY得到的是相对于当前View的x,y坐标,getRawX,getRawY得到的是相对于手机屏幕左上角的x,y坐标
TouchSlop是系统所能识别出的被认为是滑动的最小距离,当手指在屏幕上滑动时,如果两次滑动之间的距离小于这个常量,那么系统就不认为你是在进行相应的滑动操作。我们通过如下方式可以获得到这个常量:ViewConfiguration.get(getContext()).getScaledTouchSlop().
速度追踪,用于追踪手指在滑动的过程中的速度,包括水平和竖直方向的速度,它的使用过程很简单。首先,在View的onTouchEvent方法中追踪当前点击事件的速度:
VelocityTracker v = VelocityTracker.obtain();
v.addMovement(event);
当我们知道当前的滑动速度时,这个时候就可以采用如下的方式来获取当前的速度:
v.conputeCurrentVelocity(1000);//将时间间隔设置为 1s
int xVelocity = (int) v.getXVelocity();
int yVelocity = (int) v.getYVelocity();
在这一步中有两个需要注意:
第一点,获取速度前必须先计算速度。
第二点:速度指的是一段时间内,手指所滑动经过的像素数,而且规定,从左到右速度为正,从右到左速度为负。
手势检测,用于辅助检测用户的点击,滑动,长按,双击等行为。要使用GestureDetector可参考如下过程。
首先,创建一个GestureDetector对象并实现OnGestureListener接口,根据我们的需要,我们还可以实现OnDoubleTapListener从而来监听双击行为。
GestureDetector gd = new GestureDetector(this);
gd.setIsLongPressEnabled(false);//解决长按屏幕后无法拖动的现象
其次,接管目标View的onTouchEvent方法,在监听View的onTouchEvent方法中添加如下实现:
boolean consume = gd.onTouchEvent(event);
return consume;//false 表示触摸事件可以继续传递,true表示传递到此终止,事件被消费
做完了上面两步,我们可以有选择的来实现OnGestureListener和OnDoubleTapListener中的方法了。
OnDoubleTapListener里面的方法有(来自Android Developer):
OnGestureListener里面的方法有(来自Android Developer):
上面表里面的方法中常用的有:onSingleTapUp(单击),onFling(快速滑动),onScroll(拖动),onLongPress(长按)和onDoubleTap(双击)。在实际的开发中,可以不使用GestureDetector,完全可以自己在onTouchEvent方法中实现自己的监听。对于GestureDetector有个比较好的练习:
在fragment里面监听触摸事件,大家如果感兴趣可以了解一下。
弹性滑动对象,用于实现View的弹性滑动。我们知道,当使用View的scrollTo/scrollBy方法进行滑动时,其过程是瞬时间的,这个没有过程的滑动用户体验不好。这个时候我们就可以使用Scroller来实现有过度效果的滑动。Scroller本身无法让View弹性滑动,它需要和View的computeScroll方法配合使用才能共同完成这个功能。
前言:在有限的屏幕空间内为用户呈现出更多彩的内容,我们就需要通过View的滑动来实现。
掌握滑动是实现绚丽的自定义控件的基础。通过三种方式可以实现View的滑动:
1、通过View本身提供的scrollTo/scrollBy方法来实现滑动
2、通过动画给View施加平移效果来实现滑动
3、通过改变View的layoutParams使得View重新布局而实现滑动
为了使用View的滑动,View提供了专门的方法来实现这个功能,那就是scrollTo和scrollBy。下面是两者的实现。
scrollBy实际上也是调用了scrollTo方法,它实现了当前位置的相对滑动,scrollTo它实现了基于当前位置的绝对滑动。利用scrollTo和scrollBy来实现View的滑动不是一件困难的事,但是我们要明白滑动过程中View内部的两个属性mScrollX和mScrollY的改变规则。
在滑动过程中,mScrollX的值总是等于View左边缘和View中的内容左边缘在水平方向上的距离,而mScrollY的值总是等于View上边缘和View内容上边缘在竖直方向上的距离,scrollTo和scrollBy只能改变View内容的位置而不能该百年View在布局中的位置。mScrollX,和mScrollY的单位为像素,并且当View左边缘在View内容左边缘右边时,mScrollX为正值,反之为负。当View上边缘在View内容上边缘的下边时,mScrollY为正值,反之为负。换句话说,如果从左向右滑动。那么mScrollX为负值,反之为正。如果从上往下滑动,mScrollY为负值,反之为正。
不管怎么样滑动,View本身的位置是不变的,改变的是VIew里面的内容
通过动画我们能够让一个View进行平移,而平移就是一种滑动。使用动画来移动View主要是操作View的translationX和translationY属性,既可以采用传统的View动画,也可以采用属性动画。
采用如下代码在100ms内,将一个View从原始位置移动向右下角移动100像素
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
android:zAdjustment="normal">
<translate
android:duration="100"
android:fromXDelta="0"
android:fromYDelta="0"
android:interpolator="@android:anim/linear_interpolator"
android:toYDelta="100"
android:toXDelta="100"
/>
set>
如果是采用属性动画的话,则会更简单,下面的代码是将一个View从原始位置向右平移100像素
ObjectAnimator.ofFloat(targetView,"translationX",0,100).setDuration(100).start();
上面提到View动画并不能真正的改变View的位置,这回带来一个很严重的问题。试想一下,比如我们通过VIew动画将一个Button向右移动100px,并且这个View设置有单击事件,然后你会很惊奇的发现,单击新位置无法触发onClick事件,而单击原始位置仍然可以触发onClick事件,尽管button已经不在原始位置了。从Android3.0开始,使用属性动画可以解决上面的问题,但是由于要兼容Android2.2,Android2.2上无法使用属性动画,因此这里还是有问题。但是这个问题可以被间接解决,针对上面的动画问题,我们可以在新位置上预先创建一个和目标Button相同的Button,动画开始前它是隐藏的,动画开始后,隐藏原始Button,显示预留Button.
第三种实现View滑动的方法就是改变布局参数,即改变LayoutParams。如果我们想把一个Button向右平移100px,我们只需要将这个Button的LayoutParams里的marginLeft参数增加100px即可。除此之外,为了达到移动Button的目的,我们可以在Button的左边放一个空的View,这个空的View的默认宽度为0,当我们需要向右移动Button时,我们只需要重新设置空View的宽度即可(假设Button的父容器是水平方向的LinearLayout)。下面是重新设置一个View的LayoutParams方法:
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)mButton1.getLayoutParams();
layoutParams.width += 100;
layoutParams.leftMargin += 100;
mButton1.requestLayout();
//或者mButton1.setLayoutParams(layoutParams)
通过LayoutParams的方式去实现View的滑动同样是一种很灵活的方法。
上面分别介绍了三种不同的滑动方式,它们都能实现View的滑动,那么它们之间的区别是什么呢?
先看scrollTo/scrollBy这种方式,它是View提供的原生方法,起作用是专门用于View的滑动,它可以比较方便的实现滑动效果,并且不影响内部元素的单击事件..但是它的缺点也是明显的:它只可以滑动View的内容,不可以滑动View的本身。
通过动画实现View的滑动,如果是3.0以上系统,采用属性动画来做滑动这种方式没有明显的缺点。而且一些复杂的动画效果,必须要通过动画才能实现。
通过改变布局这种方式,它处理使用起来麻烦一些外也没有明显的缺点。主要的运用对象是一些具有交互性的View。因为这些View需要和用户交互,直接通过动画去实现会有问题,所以这个时候我们可以使用直接改变布局参数的方式去实现。
备注:节选自Android开发艺术探索–任玉刚