View的事件体系
View概念:
view是界面层的控件的一种抽象,ViewGroup就是一组view,viewgroup继承于view,常见的view有button,viewgroup有L
inearlayout,不能套的就是view,能套的就是viewgroup.
View的坐标系
view的相对父容器坐标:top(左上角纵坐标getTop())、left(左上角横坐标getLeft())、right(右下角横坐标getRight())
、bottom(右下角纵坐标getBottom())。左上角为(0,0)点,右方和下放为正方向。
view的宽:right-left:
view的高:bottom-top;
android3.0开始:x,y为View的左上角的坐标,translationX,translationY是View左上角相对于父容器的偏移量。
x=getLeft()+translationX(),y=getTop()+translationY().
MotionEvent和TouchSlop
ACTION_DOWN 手指刚接触屏幕
ACTION_MOVE 手指在屏幕上移动
ACTION_UP 手指从屏幕上松开瞬间
点击事件:DOWN–>UP
滑动事件:DOWN–>MOVE–>…–>MOVE–>UP TouchSlop为滑动的最小距离ViewConfiguration.get(this).getScaledTou
chSlop()
MotionEvent点击事件的x,y坐标,getX/getY/(返回的是相当于当前View左上角的X,Y坐标)和getRawX/getRawY(返回的是相对于
手机屏幕左上角的x和y坐标),
GestureDetector 手势
GestureDetector gestureDetector= new GestureDetector(this, new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
return false;
//手指轻轻触摸屏幕的一瞬间,由一个action_down触发
}
@Override
public void onShowPress(MotionEvent e) {
//手指轻轻触摸屏幕,尚未松开或拖动,由一个actiondown触发
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
//单击
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
//拖动
}
@Override
public void onLongPress(MotionEvent e) {
//长按
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
//快速滑动行为
}
});
onSingleTapUp(单击)、OnFling(快速滑动)、onScroll(拖到)、onLongPress(长按)、onDoubleTap(双击)、如果只是监听滑动
相关的,建议使用onTouchEvent,如果监听双击行为,就使用GestureDetector
VelocityTracker
获取滑动屏幕或单击屏幕的速度:
//滑动前就需要设置
VelocityTracker mvelocityTracker=VelocityTracker.obtain();
mvelocityTracker.addMovement(event);
//获取x和y方向的速度,当向正方向的反方向滑动时就变成了负值
mvelocityTracker.computeCurrentVelocity(1000);//1s内
int xVelocity=(int)velocityTracker.getXVelocity();
int yVelocity=(int)velocityTracker.getYVelocity();
//不需要时重置并回收内存
mvelocityTracker.clear();
mvelocityTracker.recyle();
Scroller弹性滑动对象
Scroller用于实现view的弹性滑动,view的ScrollTo、ScrollBy瞬间完成滑动,它只能改变View内容的位置而不能改变View在布局中的位置。若需非瞬间,就使用Scroller实现由过渡效果的滑动,当Scroller和View的ComputeScroll配合时,就能实现有过渡效果的滑动。
**scroller的工作原理就是:**scroller本身并不能实现view的滑动,它需要配合View的computeScrooll方法才能完成弹性滑动的效果,它不断的让view重绘,而每一次重绘距滑动起始时间会有一个时间间隔,通过这个时间间隔scroller就可以得出view当前的滑动位置,
知道了滑动位置就可以通过scrollTo方法来完成View的滑动,就这样,View的每一次重绘都会导致View进行小幅度的滑动,而多次小幅度的滑动就组成了弹性滑动,这就是scroller的工作机制。
View的滑动有三种方式,第一种就是通过View本身提供的scrollTo/scrollBy方法来实现滑动,但是缺点就是它只能滑动view的内容,
并不能滑动view本身。第二种就是通过动画给view施加平移效果来实现滑动,主要适用于没有交互的View和实现复杂的动画效果
第三种是通过改变view的LayOutParams使得view重新布局从而实现滑动。这种操作复杂,适用于有交互的view.
public class ZyTextView extends TextView
{
private Scroller scroller;
public ZyTextView(Context context) {
super(context);
this.scroller=new Scroller(context);
}
public ZyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
this.scroller=new Scroller(context);
}
public ZyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.scroller=new Scroller(context);
}
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(),scroller.getCurrY());
postInvalidate();
}
}
//缓慢的滚动到指定位置
public void smoothScrollTo(int destx, int desty) {
int scrollx = getScrollX();
int scrolly=getScrollY();
int deltax = destx - scrollx;
int deltay=desty-scrolly;
//1000ms内滑到destx,效果就是慢慢滑动
scroller.startScroll(scrollx, scrolly, deltax, deltay, 1000);
invalidate();
}
}
动画实现滑动
ObjectAnimator.ofFloat(editText, “translationX”,0, 50).setDuration(1000).start();
LayoutParams实现滑动
ViewGroup.MarginLayoutParams mparams=(ViewGroup.MarginLayoutParams)button.getLayoutParams();
mparams.width+=100;
mparams.leftMargin+=100;
button.setLayoutParams(mparams);
事件分发机制
public boolean dispatchTouchEvent(MotionEvent ev);
是否消耗当前的事件
public boolean onInterceptTouchEvent(MotionEvent ev);
是否拦截某事件,当拦截后同一个事件序列此方法不会被再次调用
public boolean onTouchEvent(MotionEvent ev);
是否消耗当前事件,如果不消耗,同一个事件序列此方法不会被再次调用
此三个方法关系如下:(伪代码)
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume=false;
if(onInterceptTouchEvent(ev)){
consume=onTouchEvent(ev);
}else{
consume=child.dispatchTouchEvent(ev);
}
return consume;
}
事件传递顺序:Activity–>Window–>顶级View,开始事件分发
当点击事件发生后,首先是根viewGroup收到事件,当不拦截的此事件的时候,就传给子view的dispatchTouchEvent,若拦截了就交给viewGroup的onTouchEvent处理,当viewGroup的onTouchEvent返回false时,事件就交给viewgroup的上级Activity处理,此时activity的onTouchEvent被调用。
当view需要处理事件时,如果设置了OnTouchListener,那么它里面的onTouch会被优先调用,如果返回flase才轮到当前view的OnTouchEvent.onClickListener处于事件传递的尾端。
总结:
1、事件是从按下屏幕开始到离开屏幕为终,那么是从down开始,up结束。
2、通常一个事件序列只能被一个View拦截且消耗,当事件被拦截后,事件序列的所有事件都会交给它处理,但是通过特殊手段可以实现吧一个view本该自己处理的OnTouchEvent强行传递给其他的view.
3、当view拦截某事件时,它的onInterceptTouchEvent不会再次被调用。
4、若一个view开始处理事件,如果它不消耗down事件(OnTouchEvent返回了false),那么同一事件的其他事件都不会交给它处理,事件将重新交给它的父元素去处理。
5、如果view不消耗down以外的其他事件,那么这个点击事件会消失,父元素的OnTouchEvent不会被调用,并且当前View可以持续收到后续事件,最终这些消失的点击事件会传递给activity处理
6、viewgroup默认不拦截任何事件,源码onInterceptTouchEvent返回false.
7、view没有onInterceptTouchEvent方法,一旦点击事件传递给它,那么它的OnTouchEvent方法就会被调用.
8、view只要clickable和longClickable有一个为true那么它的OnTouchEvent就返回true.当这2个属性同时为false时,就不会消耗事件了。
9、onclick会发生的前提是view可点击,并且收到了down和up的事件。
10、事件传递由外到内,即事件总是先传递给父元素再给子元素,通过requestDisallowInterceptTouchEvent方法可在子元素中干预父元素的事件分发过程。
View的滑动冲突
常见冲突有如下几种:
场景1:外部滑动的方向和内部滑动的方向不一致
场景2:外部滑动的方向和内部滑动的方向一致
场景3:场景1和场景2的嵌套
场景1处理规则:
当用户左右滑动的时候,需要让外部的view拦截点击事件,当用户上下滑动的时候,需要让内部view拦截点击事件,根据滑动的方向决定由谁拦截。那个方向滑动的距离大就判定为哪个方向。
外部拦截法,需重写父容器的onInterceptTouchEvent方法
private int mLastXIntercept,mLastYIntercept;
@Override
public boolean onInterceptTouchEvent(MotionEvent event){
boolean intercepted = false;
int x=(int)event.getX();
int y=(int)event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
intercepted=false;
//当用户正在水平滑动时,用户再迅速进行竖直滑动,就会导致界面在水平方向无法滑到终点的中间状态,
// 为避免不好的用户体验可将下一个序列的点击事件任然交给父容器处理。
// if(!mScroller.isFinish()){
// mScroller.abrotAnimation 优化滑动体验
// intercepted=true;
// }
break;
case MotionEvent.ACTION_MOVE:
// int deltax=x-mLastXIntercept;根据具体的业务来处理,不能照抄
// int deltay=x-mLastYIntercept;
// Math.abs(deltax)>Math.abs(deltay)
if(父容器需要当前的点击事件){
intercepted=true;
}else {
intercepted=false;
}
break;
case MotionEvent.ACTION_UP:
intercepted=false;
break;
default:
break;
}
mLastXIntercept=x;
mLastYIntercept=y;
return intercepted;
}
场景2的处理规则:
根据业务的规定制定规则,比如当处于某种状态的时候需要外部view响应用户的滑动,而处于另一种状态时则需要内部view来响应view的滑动,依然仿照场景1的实现来重写父容器的onInterceptTouchEvent
场景3的处理规则:
根据具体的业务制定规则