您的位置:首页 > 移动开发 > Android开发

我的“View的事件体系”知识点总结

2017-10-09 21:46 369 查看
文章总结自《android开发艺术探索》一书。

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可点击,并且接收到了downup事件。

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;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息