我的“View的事件体系”知识点总结
2017-10-09 21:46
369 查看
文章总结自《android开发艺术探索》一书。
2、在Android中View呈现出树的结构,ViewGroup继承了View,这代表View本身可以是单个控件也可以是多个控件组成的控件组
3、View中的四个属性:top、left、right、bottom,top是左上角纵坐标,left是左上角横坐标,right是右下角横坐标,bottom是右下角纵坐标;Android3.0开始增加了x、y、translationX、translationY四个参数,x、y是View左上角的坐标,另外两个代表View左上角相对于父容器的偏移量,View在平移的时候top、left不会变,x、y等4个参数变化;另外View中坐标系X轴正方向向右,Y轴正方向向下。
4、获取属性的方法:
Left =getLeft(); 剩下几个类比;
getX/getY,返回的是控件或事件相对于当前View左上角的坐标
getRawX/getRawY,返回的是控件或事件相对于手机屏幕左上角的坐标
5、 MotionEvent典型事件类型:
ACTION_DOWN
ACTION_MOVE
ACTION_UP
6、 TouchSlop:滑动最小距离,即两次滑动之间的距离小于这个值,系统默认不是滑动操作,获取方式:ViewConfiguration.get(getContext()).getScaledTouchSlop()
7、 VelocityTracker,速度追踪器,追踪手指在滑动过程中的速度,使用方法:
8、GestureDetector,手势检测器,辅助检测单击、滑动等事件,用法:
9、Scroller,用于实现View的弹性滑动。View的srcollTo/scrollBy过程是瞬间的,Scroller本身无法滑动View,需要和View的computeScroll配合使用,用法:
startScroll方法只是做一些赋值计算的工作,实际上起作用的是下面的invalidate,该方法导致View重绘,View的draw方法中会去调用computeScroll方法,computeScroll去获取Scroller当前的属性,然后开始滑动,再通知重绘,如此反复。
computeScrollOffset()方法是根据当前时间流逝的百分比来计算scrollX和scrollY改变的百分比并计算当前值,类似于属性动画中的插值器和估值器。返回值决定滑动是否结束。
View自身提供的scrollTo/scrollBy方法
动画给View施加平移效果
改变View的LayoutParams使View重新布局
1、scrollTo/scrollBy
scrollBy调用了scrollTo,前者是基于当前距离的相对滑动,后者是绝对滑动。其源码中的mScrollX总是等于View左边缘和View内容左边缘的水平距离,mScrollY上边缘和内容上边缘垂直距离,从左往右滑,mScrollX为负,从上往下滑,mScrollY为负,并且只能改变View内容的位置而不能改变View在布局中的位置。
2、使用动画
android3.0以上可以使用属性动画,基本没缺点,3.0以下版本的View动画也可以移动,但是不能改变对象的属性,所以onClick等事件需要解决。
3、改变布局参数
改变LayoutParams中的marginXXX参数的值或者在需要的地方放上空的View,需要时改变View的宽度高度用来“挤走”目标控件,例如:
4、三者比较
scrollTo/scrollBy唯一缺点:只能滑动内容,不能改变View本身。
使用动画:3.0以上属性动画没有明显缺点,3.0以下View动画不能改变View的属性。动画缺点:不适用于与用户交互的控件。
改变布局:操作复杂,没有明显缺点。
5、使用延时策略
发送一系列的延时消息,可以使用Handler或View的postDelayed方法,也可以使用线程的sleep方法。
public boolean dispatchTouchEvent(MotionEvent ev),用来进行当前事件的分发,如果事件能传递给当前View,此方法一定会调用,返回结果受当前View的onTouchEvent和下级的dispatchTouchEvent方法影响,表示是否消耗该事件
public boolean onInterceptTouchEvent(MotionEvent event),在上述方法内部调用,用来表示是否拦截该事件,如果View拦截了该事件,那么同一事件序列的所有事件都交给该View处理,并且此方法不会再被调用
public boolean onTouchEvent(MotionEvent event),在dispatchTouchEvent方法中调用,处理点击事件,返回结果表示是否消耗该事件。
关系伪代码:
2、点击事件的激发顺序:onTouchListener—>onTouchEvent—>onClickListener
3、点击事件的传递顺序: Activity—>Window—>View,顶层View拿到事件以后进行分发。分发时如果子View无法处理事件,会上抛事件,父View的onTouchEvent会被调用,如果所有元素都不处理这件事,就会抛给Activity,Activity的onTouchEvent会被调用。
4、同一事件序列:指的是从手指触摸屏幕的那一刻,中间经历了若干个move事件,到手指离开屏幕的那一刻,中间产生的所有事件。
5、正常情况下,同一事件序列的所有事件只能被一个View拦截并且消耗。
6、某个View一旦开始处理事件,如果onTouchEvent返回false,即不消耗事件,那么同一事件序列中剩下的所有事件都不交给它处理,转交给父View去处理。
7、ViewGroup默认不拦截任何事件,View没有onInterceptTouchEvent方法,事件传给它直接调用onTouchEvent。
8、View的onTouchEvent默认都会消耗事件,除非是不可点击的,View的enable属性不影响onTouchEvent的默认返回值。
9.onClick事件发生的前提是当前View可点击,并且接收到了down和up事件。
10、可以通过requestDisallowInterceptTouchEvent方法在子元素中干预父元素的分发过程,但是ACTION_DOWN事件除外。
外部滑动方向和内部滑动方向不一致,比如外层一个ScrollView,内层一个ListView
外部滑动方向和内部滑动方向一致,比如内外层同时能上下滑动或者左右滑动
上面两种情况的嵌套
第一种的处理规则是:根据用户不同的滑动方向,让不同层的View拦截点击事件。具体来说根据滑动过程中两个点的坐标来判断,比如说两点坐标的竖直距离差与水平距离差的大小比较,水平竖直方向的速度差等。
第二、三种的处理规则:根据业务逻辑,在View的拦截中加上相应的业务逻辑判断是否拦截处理。
2、冲突的解决方式
外部拦截法
点击事件都先经过父容器的拦截处理,如果父容器需要就拦截,不需要就不拦截。需要重写父容器的onInterceptTouchEvent方法。
典型逻辑代码:
ACTION_DOWN事件一定不能拦截,否则后续事件都交给父容器处理,如果父容器处理不好会上抛给上层父容器,不会传递到其子元素,另外ACTION_UP事件没有多大意义,也要返回false。
内部拦截法
父容器不拦截任何事件,所有事件都传递给子元素,如果子元素需要此事件就消耗掉,否则就上抛给父容器进行处理。需要配合requestDisallowInterceptTouchEvent方法并重写子元素的dispatchTouchEvent:
父元素默认拦截除了ACTION_DOWN以外的其他事件:
3.1 View基础知识
1、View是Android中所有控件的基类,是一种界面层的控件的一种抽象,也代表了一种控件。2、在Android中View呈现出树的结构,ViewGroup继承了View,这代表View本身可以是单个控件也可以是多个控件组成的控件组
3、View中的四个属性:top、left、right、bottom,top是左上角纵坐标,left是左上角横坐标,right是右下角横坐标,bottom是右下角纵坐标;Android3.0开始增加了x、y、translationX、translationY四个参数,x、y是View左上角的坐标,另外两个代表View左上角相对于父容器的偏移量,View在平移的时候top、left不会变,x、y等4个参数变化;另外View中坐标系X轴正方向向右,Y轴正方向向下。
4、获取属性的方法:
Left =getLeft(); 剩下几个类比;
getX/getY,返回的是控件或事件相对于当前View左上角的坐标
getRawX/getRawY,返回的是控件或事件相对于手机屏幕左上角的坐标
5、 MotionEvent典型事件类型:
ACTION_DOWN
ACTION_MOVE
ACTION_UP
6、 TouchSlop:滑动最小距离,即两次滑动之间的距离小于这个值,系统默认不是滑动操作,获取方式:ViewConfiguration.get(getContext()).getScaledTouchSlop()
7、 VelocityTracker,速度追踪器,追踪手指在滑动过程中的速度,使用方法:
//build VelocityTracker vt = VelocityTracker.obtain(); VelocityTracker.addMovement(event); //use vt.computeCurrentVelocity(1000); int Vx = (int) vt.getXVelocity(); int Vy = (int) vt.getYVelocity(); //recyle vt.clear(); vt.recyle();
8、GestureDetector,手势检测器,辅助检测单击、滑动等事件,用法:
//build GestureDetector mGestureDetector = new GestureDetector(this); //implement Interface: OnGestureListener || OnDoubleTapListener .... //code in Method: mView.onTouchEvent boolean consume = mGestureDetector.onTouchEvent(event); return consume;
9、Scroller,用于实现View的弹性滑动。View的srcollTo/scrollBy过程是瞬间的,Scroller本身无法滑动View,需要和View的computeScroll配合使用,用法:
Scroller mScroller = new Scroller(mContext); private void smoothScrollTo(int destX,int destY){ int scrollX = getScrollX(); int delta = destX - scrollX; //slide slowly to destX in 1000ms mScroller.startScroll(scrollX,0,delta,0,1000); invalidate(); } @Override public void computeScroll(){ if(mScroller.computeScrollOffset()){ scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } }
startScroll方法只是做一些赋值计算的工作,实际上起作用的是下面的invalidate,该方法导致View重绘,View的draw方法中会去调用computeScroll方法,computeScroll去获取Scroller当前的属性,然后开始滑动,再通知重绘,如此反复。
computeScrollOffset()方法是根据当前时间流逝的百分比来计算scrollX和scrollY改变的百分比并计算当前值,类似于属性动画中的插值器和估值器。返回值决定滑动是否结束。
3.2 View的滑动
三种方式实现View的滑动:View自身提供的scrollTo/scrollBy方法
动画给View施加平移效果
改变View的LayoutParams使View重新布局
1、scrollTo/scrollBy
scrollBy调用了scrollTo,前者是基于当前距离的相对滑动,后者是绝对滑动。其源码中的mScrollX总是等于View左边缘和View内容左边缘的水平距离,mScrollY上边缘和内容上边缘垂直距离,从左往右滑,mScrollX为负,从上往下滑,mScrollY为负,并且只能改变View内容的位置而不能改变View在布局中的位置。
2、使用动画
android3.0以上可以使用属性动画,基本没缺点,3.0以下版本的View动画也可以移动,但是不能改变对象的属性,所以onClick等事件需要解决。
3、改变布局参数
改变LayoutParams中的marginXXX参数的值或者在需要的地方放上空的View,需要时改变View的宽度高度用来“挤走”目标控件,例如:
MarginLayoutParams params = (MarginLayoutParams)mButton.getLayoutParams(); params.width += 100; params.height += 100; mButton.requestlayout();
4、三者比较
scrollTo/scrollBy唯一缺点:只能滑动内容,不能改变View本身。
使用动画:3.0以上属性动画没有明显缺点,3.0以下View动画不能改变View的属性。动画缺点:不适用于与用户交互的控件。
改变布局:操作复杂,没有明显缺点。
5、使用延时策略
发送一系列的延时消息,可以使用Handler或View的postDelayed方法,也可以使用线程的sleep方法。
3.4 View的事件分发机制
1、针对MotionEvent的事件分发,主要由三个方法完成:public boolean dispatchTouchEvent(MotionEvent ev),用来进行当前事件的分发,如果事件能传递给当前View,此方法一定会调用,返回结果受当前View的onTouchEvent和下级的dispatchTouchEvent方法影响,表示是否消耗该事件
public boolean onInterceptTouchEvent(MotionEvent event),在上述方法内部调用,用来表示是否拦截该事件,如果View拦截了该事件,那么同一事件序列的所有事件都交给该View处理,并且此方法不会再被调用
public boolean onTouchEvent(MotionEvent event),在dispatchTouchEvent方法中调用,处理点击事件,返回结果表示是否消耗该事件。
关系伪代码:
public boolean dispatchTouchEvent(MotionEvent event){ boolean consume = false; if(onInterceptTouchEvent){ consume = onTouchEvent(event); }else{ consume = child.dispatchTouchEvent(event); } return consume; }
2、点击事件的激发顺序:onTouchListener—>onTouchEvent—>onClickListener
3、点击事件的传递顺序: Activity—>Window—>View,顶层View拿到事件以后进行分发。分发时如果子View无法处理事件,会上抛事件,父View的onTouchEvent会被调用,如果所有元素都不处理这件事,就会抛给Activity,Activity的onTouchEvent会被调用。
4、同一事件序列:指的是从手指触摸屏幕的那一刻,中间经历了若干个move事件,到手指离开屏幕的那一刻,中间产生的所有事件。
5、正常情况下,同一事件序列的所有事件只能被一个View拦截并且消耗。
6、某个View一旦开始处理事件,如果onTouchEvent返回false,即不消耗事件,那么同一事件序列中剩下的所有事件都不交给它处理,转交给父View去处理。
7、ViewGroup默认不拦截任何事件,View没有onInterceptTouchEvent方法,事件传给它直接调用onTouchEvent。
8、View的onTouchEvent默认都会消耗事件,除非是不可点击的,View的enable属性不影响onTouchEvent的默认返回值。
9.onClick事件发生的前提是当前View可点击,并且接收到了down和up事件。
10、可以通过requestDisallowInterceptTouchEvent方法在子元素中干预父元素的分发过程,但是ACTION_DOWN事件除外。
3.5 View的滑动冲突
1、常见的滑动冲突场景外部滑动方向和内部滑动方向不一致,比如外层一个ScrollView,内层一个ListView
外部滑动方向和内部滑动方向一致,比如内外层同时能上下滑动或者左右滑动
上面两种情况的嵌套
第一种的处理规则是:根据用户不同的滑动方向,让不同层的View拦截点击事件。具体来说根据滑动过程中两个点的坐标来判断,比如说两点坐标的竖直距离差与水平距离差的大小比较,水平竖直方向的速度差等。
第二、三种的处理规则:根据业务逻辑,在View的拦截中加上相应的业务逻辑判断是否拦截处理。
2、冲突的解决方式
外部拦截法
点击事件都先经过父容器的拦截处理,如果父容器需要就拦截,不需要就不拦截。需要重写父容器的onInterceptTouchEvent方法。
典型逻辑代码:
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; break; } case MotionEvent.ACTION_MOVE:{ if(父容器需要当前点击事件){ intercepted = true; }else{ intercepted = false; } break; } case MotionEvent.ACTION_UP:{ intercepted = false; break; } default: break; } mLastXIntercept = x; mLastYIntercept = y; return intercepted; }
ACTION_DOWN事件一定不能拦截,否则后续事件都交给父容器处理,如果父容器处理不好会上抛给上层父容器,不会传递到其子元素,另外ACTION_UP事件没有多大意义,也要返回false。
内部拦截法
父容器不拦截任何事件,所有事件都传递给子元素,如果子元素需要此事件就消耗掉,否则就上抛给父容器进行处理。需要配合requestDisallowInterceptTouchEvent方法并重写子元素的dispatchTouchEvent:
public boolean dispatchTouchEvent(MotionEvent event){ int x = (int) event.getX(); int y = (int) event.getY(); switch(event.getAction()){ case MotionEvent.ACTION_DOWN:{ parent.requestDisallowInterceptTouchEvent(true); break; } case MotionEvent.ACTION_MOVE:{ int deltaX = x - mLastX; int deltaY = y - mLastY; if(父类需要此类点击事件){ parent.requestDisallowInterceptTouchEvent(false); } break; } case MotionEvent.ACTION_UP:{ break; } default: break; } mLastX = x; mLastY = y; return super.dispatchTouchEvent(event); }
父元素默认拦截除了ACTION_DOWN以外的其他事件:
public boolean onInterceptTouchEvent(MotionEvent event){ int action = event.getAction(); if(action==MotionEvent.ACTION_DOWN){ return false; }else{ return true; } }
相关文章推荐
- Android之View基础总结(View的事件体系一)
- android中View的事件体系总结
- Android View事件体系全面总结+实践分析
- Android自定义View滑动事件处理总结
- 《Android开发艺术探索》——View的事件体系
- View的事件体系基础知识
- View事件体系之View的的滑动
- Android面试题(三)——View的事件体系和工作原理
- 《View的事件体系》(三)弹性滑动
- VIew的事件体系(二)
- View的事件体系——事件分发机制
- View的事件体系续
- 【知识点总结2】事件流
- View的事件体系(下)(事件分发,滑动冲突)
- Android View事件分发机制总结(上)
- Android View事件分发机制总结(下)
- Android开发总结笔记 View的事件分发机制 3-9
- Android Webview知识点和遇到过的坑全总结
- Android开发总结笔记 ViewGroup的事件分发机制 3-10
- 第3章 view的事件体系