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

《Android 开发艺术探索》笔记——(3)View 的事件体系

2016-05-09 16:12 513 查看

View 基础知识

View 是 Android 中所有控件的基类,ViewGroup 也继承了 View。

Android 中,x 轴和 y 轴的正方向分别为右和下。

位置参数(相对于父容器):

(left , top ): View 左上角原始坐标

(right, bottom): View 右下角原始坐标

(x , y ): View 左上角最终坐标

translationX: View 左上角横向偏移量

translationY: View 左上角纵向偏移量

x = left + translationX

y = top + translationY (setX/Y() 时其实就是改变 translationX/Y 的值)

width = right - left

height = bottom - top

MotionEvent 和 TouchSlop

MotionEvent

典型事件:ACTION_DOWN, ACTION_MOVE,ACTION_UP

意思也很容易理解,分别是落,动,起

一次触摸会触发一系列事件:

点击屏幕后离开松开:DOWN -> UP

点击屏幕滑动再松开:DOWN -> MOVE ->…-> MOVE -> UP

通过 MotionEvent 获得点击事件的坐标:

getX / getY : 相对于当前 View 左上角的 x 和 y 坐标

getRawX / getRawY:相对于手机屏幕左上角的 x 和 y 坐标

(View.getX / getY 获得的是相对于父容器的 x 和 y 坐标)

TouchSlop

滑动的最小距离,若没达到,则不认为是滑动,默认 8dp。

VelocityTracker、GestureDetector 和 Scroller

VelocityTracker

速度追踪,用于追踪手指在滑动过程中的速度。

在 View 的 onTouchEvent 方法中:

VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
velocityTracker.computeCurrentVelocity(1000);// 1000ms内划过的像素数
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();


回收:

velocityTracker.clear();
velocityTracker.recycle();


GestureDetector

手势检测,用于辅助检测用户的单击、滑动、长按、双击等行为。

一般监听滑动相关,在 onTouchEvent 中自己实现,若是监听双击,则使用 GestureDetector。

Scroller

弹性滑动对象,用于实现 View 的弹性滑动,即有过过渡效果的滑动,与 View 的 computeScroll 方法配合使用。(下面会详细介绍)

Viewd 的滑动

三种方式:

scrollTo/scrollBy

操作简单,适合对 View 内容的滑动

典型应用:ScrollView 的滑动

动画

操作简单,适合没有交互的 View 和实现复杂的动画效果

改变 LayoutParams

操作稍复杂,适合有交互的 View

弹性滑动

Scroller

典型用法:

private Scroller mScroller = new Scroller(context);

public void smoothScrollTo(int destX, int destY) {
int scrollX = getScrollX();
int delta = destX - scrollX;
mScroller.forceFinished(true);
mScroller.startScroll(scrollX, 0, delta, 0, 5000);
invalidate();
}

@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollBy(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}


当我们调用 startScroll() 时,Scroller 内部其实什么也没做,只是保存了我们传递的几个参数。所以仅仅调用 startScroll() 是不会产生滑动的,因为它内部没有做与滑动相关的事,Scroller 产生滑动,其实是因为 invalidate()。

invalidate() 会导致 View 重绘,在 View 的 Draw 方法中会调用 computeScroll(),computeScroll() 在 View 中是一个空实现,所以我们要自己实现,正如上所示。

过程:

invalidate() –>

View 重绘, draw() –>

computeScroll() ==>

scrollTo() –>

postInvalidate() 再次重绘 –>



computeScrollOffset() 会根据时间流逝算出当前的 ScrollX/Y,返回 true 即表示滑动还没有结束,继续滑动。

动画

动画本身就是一种渐进的过程,所以利用动画天然就具有弹性效果。

另外通过动画,也可以实现类似 Scroller 对 View 的弹性滑动。

使用延时策略

核心思想是通过发送一系列延时消息从而达到一种渐进式的效果,具体来说可以使用 Handler 或 View 的 postDelayed 方法,也可以使用线程的 sleep 方法。

View 的事件分发机制

优先级:

onTouchListener > onTouchEvent > onClickListener

传递过程:

Activity –> Window –> View

事件总是先传给 Activity,Activity 再传给 Window,Window 再传给顶级 View,接着顶级 View 就按照事件分发机制去分发事件。如果一个 View 的 onTouchEvent 返回 false,那么它的父容器的 onTouchEvent 就会被调用,若全部 View 都不处理这个事件,那这个事件将最终传递给 Activity 来处理,即 Activity 的 onTouchEvent 会被调用。

View 的滑动冲突

常见的三种滑动冲突场景:

外部滑动与内部滑动的方向不一致

外部滑动与内部滑动的方向一致

以上两种的嵌套



滑动冲突的处理规则

对场景一:根据滑动是水平滑动还是竖直滑动来判断到底是由谁来拦截事件。由滑动轨迹的起终点的坐标即可判断为水平还是竖直(距离差、角度、速度差等)。

对场景二:根据业务需求具体分析。

对场景三:以上两者的混合。

滑动冲突的解决方式

外部拦截法

指事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截。需重写父容器的 onInterceptTouchEvent 方法。

伪代码:

public boolean onInterceptTouchEvent(MotionEvent event){
boolean intercepted = false;
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;
}
return intercepted;
}


对于 ACTION_DOWN 事件,父容器必须返回 false,因为一旦拦截了 ACTION_DOWN,后续的 ACTION_MOVE,ACTION_UP 都会交给父容器处理。

对于 ACTION_UP 事件,父容器也返回 false,一旦拦截,子元素的 onClick 事件便无法触发。父容器(ViewGroup)比较特殊,一旦开始拦截(ACTION_DOWN 的 onTouchEvent 返回 true),那么后续事件也都会交给它处理(并且 onInterceptTouchEvent 不会再被调用),而 ACTION_UP 作为最后一个事件也一定能够传到父容器(因为 onInterceptTouchEvent 不会被调用,所以返回 false 也就不起作用)。

内部拦截法

内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器进行处理,需要配合 requestDisallowInterceptTouchEvent 方法来设置(控制 FLAG_DISALLOW_INTERCEPT 标志位,设置后 ViewGroup 将无法拦截除了 ACTION_DOWN 以外的点击事件,ACTION_DOWN 不受此标志位约束),需重写子元素的 dispatchTouchEvent 方法(子元素,view 无 onInterceptTouchEvent 方法)。

子元素:

public boolean dispatchTouchEvent(MotionEvent event){
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
parent.requestDisallowInterceptTouchEvent(true);
case MotionEvent.ACTION_MOVE:
if(父容器需要当前点击事件){
parent.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return super.dispatchTouchEvent(event);
}


因为 ACTION_DOWN 不受 requestDisallowInterceptTouchEvent 方法影响,父元素也要做相应处理:

public boolean onInterceptTouchEvent(MotionEvent event){
int action = event.getAction();
if (action == ACTION_DOWN){
return false;
} else {
return true;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  笔记 View Android