本章包括: Android的View体系,View的滑动,View的事件及滑动冲突
第三章,View事件体系
View基础
View滑动
弹性滑动
事件分发
滑动冲突
View基础
View参数
View的四个基本属性
left = getLeft();
top = getTop();
bottom = getbottom();
right = getRight();
其中参数都是相对于父容器的.
从Android3.0增加了几个参数:x、y、translationX、translationY,
其中x和y是view左上角的坐标,而translationX和translationY是view左上角相对于父容器的偏移量,
这几个参数也是相对于父容器的坐标,并分别提供了get\set方法,换算关系如下:
x = translationX + left;
y = translationY + top;
需要注意的是left和top表示的是原始左上角的位置信息,其值并不会改变
view平移时发生改变的是x、y、translationX、translationY。
所以如果你的view位置不动的话,getX和getLeft的值是一样的。
width = right - left;
height = bottom - top;
MotionEvent&TouchSlop
MotionEvent主要包含了一系列事件
其中,getX/getY
返回的是相对于当前view左上角的x和y坐标,getRawX/getRawY
返回的是相对手机屏幕左上角的x和y坐标。
TouchSlop 被认为是系统所能识别的最小滑动距离,和设备有关,不同设备上的值可能不同.
ViewConfiguration.get(getContext()) .getScaledTouchSlop();
VelocityTracker
速度追踪,用于追踪手指在滑动过程中的速度. 左 -> 右:速度为正值
// 在onTouchEvent()方法中追踪当前点击事件的速度
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
//获取滑动速度
//计算速度,设置时间单元,单位毫秒
velocityTracker.computeCurrentVelocity(1000);
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();
//速度 = (终点位置 - 起始位置) / 时间段
//不需要的时候回收内存
velocityTracker.clear();
velocityTracker.recycle();
GestureDetector
手势检测,检测用户的单击、滑动、长按、双击等行为.
//创建GestureDetector对象并实现OnGestureListener接口,建议使用V4包中的GestureDetectorCompat兼容包
mGestureDetectorCompat =
new GestureDetectorCompat(this, new GestureDetector.SimpleOnGestureListener());
//SimpleOnGestureListener包含了一系列手势事件的默认实现
@Override public boolean onTouchEvent(MotionEvent event) {
boolean b = mGestureDetectorCompat.onTouchEvent(event);
return b;
}
建议: 监听滑动相关,自己处理
监听双击这种行为,使用GestureDetector处理
Scroller
弹性滑动对象,用于实现View的弹性滑动,需要和View的computeScroll方法配合使用.
private Scroller mScroller = new Scroller(mContext);
/**
* 缓慢移动到指定位置
* @param destX 指定位置x坐标
* @param destY 指定位置y坐标
*/
private void smoothScrollTo(int destX, int destY) {
int scrollerX = getScrollX();
int deltaX = destX - scrollerX;
// 1000ms内滑向destX
mScroller.startScroll(scrollerX,0,deltaX,0,1000);
invalidate();
}
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
其实还有一个叫做OverScroller
的增强工具类,增加了过渡滑动效果.
View的滑动
scrollTo/scrollBy : 左->右:mScrollX为负值,上->下: mScollY为负值
动画: nineoldanimations在3.0以下的本质依然是使用view动画实现.
LayoutParams: 适用于有交互的View
群英传将其分为七种,之前的总结 Android移动View的几种方式
弹性滑动
scrollTo/scrollBy 和computeScroll
配合使用- 动画,本身具备弹性滑动效果
- 延时策略,通过
Handler,postDelayed
来实现弹性效果.
事件分发
主要涉及到三个方法,之前的总结Android 6.0事件分发机制源码解析
伪代码说明一切:
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;
if(onInterceptTouchEvent(ev)){
consume = onTouchEvent(ev);
}else{
consume = child.dispatchTouchEvent(ev);
}
return cosume;
}
View.onTouchListener#onTouchEvent
|false -> View.onTouchEvent -> View.onClickListener#onClick
- View.onTouchEvent 返回false,则parent的onTouchEvent将会被调用:false抛给上级
- 一个事件序列只能被一个View拦截且消耗,也可以通过特殊手段传递(一锤子买卖)
- 事件一旦交给一个view处理,则之后不会继续调用这个
View
的onInterceptTouchEvent
- View一旦开始处理事件,如果不消耗
ACTION_DOWN
,则后续事件也不会再交给它处理。 - 如果View不消耗ACTION_DOWN以外的其他事件,这个点击事件会消失.
- ViewGroup默认不处理事件。
- View#onTouchEvent默认会消耗事件,除非
clickable == false && longClickable == false
- View的
enable不影响
onTouchEvent的默认返回值 onClick
会发生的前提是View
是可点击的,并且接受了down
和up
事件- 事件由外向内传递,通过
requestDisallowInterceptTouchEvent
可以干预父元素的事件分发
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;
}
- 内部拦截法: 子元素需要事件就直接消耗掉,否则交给父容器处理.需要重写子元素的
dispatchTouchEvent
并配合requestDisallowInterceptTouchEvent
处理.
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
if (父容器需要此类点击事件) {
mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
return super.dispatchTouchEvent(event);
}
推荐采用外部拦截法