一、View的位置参数
1.View的四个属性:top,left,right,bottom.注意,这些坐标都是相对于View的父容器来说的,是相对坐标
Left = getLeft();
Right = getRight();
Top = getTop();
Bottom = getBottom();
从Android3.0开始,View增加了额外的几个参数:x,y,translationX,translationY。 其中x,y是View的左上角的坐标,translationX和translationY是View左上角相对于父容器的偏移量,这几个参数也是相对于父容器的,translationX和translationY的,默认值是0
注意View在平移的过程中,top和left表示的是原始的左上角位置信息,其值并不会发生改变,此时发生改变的是x,y,translationX,translationY
2.MontionEvent和TouchSlop
通过MontionEvent对象可以得到点击事件发生的x和y坐标 getX/getY 返回的是相对于当前View左上角坐标 getRawX/getRawY返回相对于手机屏幕的左上角坐标
TouchSlop
是系统所能识别的滑动的最小距离 ViewConfiguration.get(getContext()).getScaleTouchSlop()
3.VelocityTracker,GestureDetector和Scroll
VelocityTracker速度追踪,用于追踪手指滑动过程中的速度,包括水平和竖直的速度,在View的onTouchEvent方法中追踪当前单击事件的速度
VelocityTracker velocityTracker = VelocityTracker.obain()
velocityTracker.addMovement(event)
velocityTracker.computeCurrentVelocity(1000);
int xVelocity = (int)velocityTacker.getXVelocity();
最后在不需要使用的时候,调用clear方法来重置回收内存velocityTracker.clear(),velocityTrackler.recycle();
4.GestureDetector
方法名 | 描述 | 所属接口 |
onDown | 手指轻轻触摸屏幕的一瞬间,由一个ACTION_DOWN触发 | OnGestureListener |
onShowPress | 手指轻轻触摸屏幕,尚未松开或拖动,由一个ACTION_DOWN触发 *注意和onDown()的区别,他强调没有松开或者拖动的状态 | OnGestureListener |
onSingleTapUp | 手指(轻轻触摸屏幕后)松开,伴随着1个MontionEvent ACTON_UP而触发,这是单击行为 | OnGestureListener |
onScroll | 手指按下屏幕并拖动,由1个ACTION_DOWN,多个ACTION_MOVE触发,这是拖动行为 | OnGestureListener |
onLongPress | 用户长时间按着不放,即长按 | OnGestureListener |
onFling | 用户按下触摸屏,快速滑动,由1个ACTION_DOWN.多个ACTION_MOVE,和一个ACTION_UP触发,这是快速滑动行为 | OnGestureListener |
onDoubleTap | 双击,由两次连续的单击组成,他不可能和onSingleTapConfirm共存 | OnDoubleTapListener |
onSingleTapConfirm | 严格的单击行为 他与onSingleTap的区别,如果触发了onSingleTapConfirm,那么后面不可能在紧跟另一个单击行为,即只可能是单击,而不可能是双击中的一次单击 | |
onDoubleTapEvent | 表示发生了双击行为,在双击期间,ACTION_DOWN,_ACTION_UP都会触发此回调 |
二、View的滑动
1.使用scrollTo/scrollBy
View内部的两个属性mScrollX,mScrollY,这两个属性通过getScrollX和getScrollY获得,在滑动过程中,mScrollX的值为View左边缘和View内容边缘的水平方向的距离,而mScrollY的值等于View上边缘和View内容上边缘在竖直方向的距离,scrollBt和scrollTo只能翻改变View内容上的位置而不能改变view在布局中的位置
2.使用动画
ObjectAnimator.ofFloat(targetView,"translationX",0,100).setDuration(100).start();注意,View动画是对View的影像做操作,并不是真正的改变View的位置参数,如果希望动画后的状态能够保留还必须将fillAfter属性市设为true
3.改变布局参数
MarginLayoutParams params = (MarginLayoutParams)mButton.getLayoutParams();
params.width +=100;
params.leftMargin +=100;
mButton.requestLayout();//mButton.setLayoutParams();
4.弹性滑动
4.1使用Scroller
Scroller scroller = new Scroller(context);
private void smoothScrollTo(int destX,int destY){
int scrollX = getScrollX();
int deltaX = dextX-scrollX;
mScroller.startScroll(scroll,0,destX,0,1000);
invalidate();
}
@override
public void computerScroll(){
if(mScroller.computerScrollOffset()){
scrollTo(mScroller.getCurrX(),mScroll.getCurrY());
postInvalidate();
}
}
4.2通过动画
ValueAnimator animator = ValueAnimator.ofInt(0,1).setDuration(1000);
animation.addUpdateListener(new AniatorUpdateListener(){
@override
public void onAniamtionUpdate(ValueAnimator animator){
float fraction = animator.getAnimatedFraction();
mButton.scrollTo(startX+(int)(deltaX*fraction),0);
}
})
animation.start();
4.3使用延迟策略
核心思想:通过发送一系列延迟消息从而达到一种渐进式的效果,具体说可以使用Handle或View的postDelayed方法
private Handler mHandler = new Handle(){
public void handleMessaege(Message msg){
switch(msg.what){
case MESSAGE_SCROLL_TO:
mCount++;
if(mCount<=FRAME_COUNT){
float fraction = mCount/(int)FRAME_COUNT;
int scrollX = (int)(fraction*100);
mButton.scrollTo(scrollX,0);
mHandler.sentEmptyMessageDelayed(MESSAGE_SCROLL_TO,DDEAYED_TIME);
}
BREAK;
}
}
}
三、View的事件分发机制
1.点击事件传递规则
public boolean dispatchTouchEvent(MontionEvent e)
用来进行事件分发,如果时间能够传递给当期View,那么此方法一定会调用,返回的结果手当前View的onTouchEvent和下级的View的dispatchTouchEvent方法影响,表示是否消耗当期事件
public boolean onInterceptTouchEvent(MontionEvent e)
用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会再次被调用,返回结果表示是否拦截当前事件
public boolean onTouchEvent(Montion e)
在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件
伪代码:
public void dispatchTouchEvent(MontionEvent ev){
boolean consume = false;
if(onInterceptTouchEvent(ev)){
consume = onTouchEvent(ev);
}else{
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
事件传递Activity -> window -> View 如果所有元素都不处理这个事件,那么这个事件最终传递给Activity处理,即Activity的onTouchEvent方法会被调用
关于时间传递的结论
(1)同一个事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程中所产生的一系列事件,这个事件以DOWN时间开始,中间含有数量不定的move事件,最终以up结束
(2)正常情况下,一个事件序列只能被一个View拦截且消耗,因为一旦一个元素拦截了此事件,那么同一个事件序列内的所有事件都会直接交给他处理,因此同一个事件序列中的事件不能分别有两个View同时处理,但是通过特殊手段可以做到,比如一个View将本该自己处理的事件通过onTouchEvent强行传递个给其他View处理
(3)某个View一旦决定拦截,那么这个事件序列都只能有他来处理,并且它的onInterceptTouchEvent不会再被调用
(4)某个View一旦开始处理事件,如果不消耗ACTION_DOWN事件(onTouchEvent返回false),那么同一事件序列中的其他事件都不会再交给他来处理,并且事件将重新交由它的父元素去处理,即父元素的onTouchEvent会被调用。意思是事件一旦交给一个View处理,那他必须消耗掉,否则同一事件中剩下的事件就不再交给他来处理了。
(5)如果View不消耗ACTION_DOWN以外的其他事件,那么这个点击事件会消失。此时父元素的onTouchEvent事件并不会调用,并且当前的View可以持续收到后续的事件,最终这些消失的事件会传递给Avtivity处理。
(6)ViewGroup默认不拦截任何事件,Android源码中ViewGroup的onInterceptTouchEvent方法默认返回false
(7)View没有onInterceptTouchEvent方法,一旦事件传递给他,那么它的onTouchEvent方法就会被调用
(8)View的onTouchEvent默认都会消耗事件(返回true),除非他是不可点击的(clickable和longClickable同时为false)
(9)View的enable属性不影响onTouchEvent的默认返回值,只要clickable或者longClickable有一个为true,那么他的onTouchEvent就返回true
(10)onClick会发生的前提是当前View是点击的,并且他收到了down和up事件
(11)事件传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过requeseDisallowInterceptTouchEvent方法可以子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外
总结onInterceptTouchEvent不是每次事件都会调用的,如果想提前处理所有点击事件,要选择dispatchT ouchEvent方法,只有这个方法确保每次都会调用