View基础知识
- View本身可以是单个控件,也可以是多个控件组成的一组控件
- ViewGroup也继承了View
View的位置参数
-
top:左上角纵坐标
-
left:左上角横坐标
-
bottom:右下角纵坐标
-
right:右下角横坐标
-
x,y:左上角坐标
-
translatioX,translationY:左上角相对于父容器的坐标偏移量,默认值为0
x = left + translationX;
y = top + translationY;
MotionEvent和TouchSlop
- MotionEvent
在手指接触到屏幕,触发的典型事件类型:
- ACTION_DOWN:手指刚接触屏幕
- ACTION_MOVE:手指在屏幕上移动
- ACTION_UP:手指离开屏幕
通过MotionEvent对象可得点击事件发生的x,y坐标
- getX/getY:获得相对于View左上角x,y的坐标
- getRowX/getRowY:获得相对于手机屏幕左上角的x,y坐标
- TouchSlop
系统所能识别滑动的最小距离,是个常量,也就是滑动的距离小于这个常量系统就认为没有进行滑动操作
获得常量方式:ViewConfiguration.get(getContext()).getScaledTouchSlop()
VelocityTracker、GestureDetector和Scroller
- VelocityTracker:速度追踪
用于追踪手指在滑动过程中的速度,包含水平和垂直上的速度
- 首先在View的onTouchEvent方法中追踪当前单击事件的速度
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
- 接着,计算速度,获取速度之前要计算速度,这里的速度指一段时间手指所划过的像素数
velocityTracker.computeCurrentVelocity(1000);//计算速度
int xVelocity = (int)velocityTracker.getXVelocity();
int yVelocity = (int)velocityTracker.getYVelocity();
- 最后,调用clear方法重置并回收内存
velocityTracker.clear();
velocityTracker.recycle();
- GestureDetector:手势检测
用来辅助检测用户的单击、滑动、长按、双击等行为
- Scroller:弹性滑动,实现有过渡效果的滑动
View的滑动
方式:
- 通过View的ScrollTo/ScrollBy方法
- 通过动画给View施加平移效果来实现滑动
- 通过改变View的LayoutParams使得View重新布局从而实现滑动
使用scrollTo/scrollBy
scrollBy最终也是使用scrollTo来实现的,他们都只能该变View内容的位置,不能该变View的布局位置,从左往右滑,mScrollX为正,反之为负,从下往上滑mScrollY为正,反之为负
使用动画方式(属性动画)
例:将一个View在100ms内从原始位置向右平移100像素
ObjectAnimator.ofFloat(targetView, "translationX", 0, 100).setDuration(100).start();
View动画是对View的影像做操作,并没有该变View的位置参数,要使动画的状态得以保存,要将属性fillAfter设置为true,否则动画完成后结果就会消失,属性动画不会出现此问题
改变布局参数
即改变LayoutParams,例如通过marginLeft参数可以达到向右平移的效果,还可以预先设置一个空View等
3种滑动方式的比较
- ScrollTo/ScrollBy:操作简单,适合对View内容的滑动,不影响内部元素的单击事件,缺点:只能滑动View的内容,不能滑动View本身
- 动画:不能该变View本身属性,适合不与用户交互的View和实现复杂动画
- 该变布局:操作复杂,适用于与用户交互的View
弹性滑动
即渐进式滑动,主要思想是将一次很大的滑动分成若干个小的滑动,并且在一段时间内完成
实现方式:Scroller、Handler.postDelayed、Thread.sleep
Scroller
典型使用方法:
Scroller scroller = new Scroller(mContext);
//缓慢移动到指定位置
private void smoothScrollerTo(int destX, int destY) {
int scrollX = getScrollX();
int destX = destX - scrollX;
//1s内滑动destX
mSroller.startScroll(scrollX, 0, deltaX, 0, 1000);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mSroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
注意:这里的滑动是指内容上的滑动,View本身是没有改变的,在上述中,仅仅调用startScroll方法是无法让View进行滑动的,它的内部没有做滑动相关的事情,只是保存了些数据而已,真正让View滑动的是invalidate方法,这个方法导致View重绘,在View重绘的draw方法中又会去调用computeScroll方法,这个方法又会去向Scroller获取当前的scrollX和scrollY,然后通过scrollTo方法实现滑动
computeScrollOffset方法返回的是true表示滑动没有结束,返回false,表示滑动结束
Scroller本身不能实现View滑动,要配合View的computeScroll方法才能完成弹性滑动的效果
View事件分发机制
点击事件传递规则
点击事件分析的对象是MotionEvent,点击事件分发过程是由:dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent方法共同完成的
- dispatchTouchEvent:用于事件的分发,如果事件传递到当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent影响
- onInterceptTouchEvent:在上述方法内部调用,判断是否拦截此事件,true为拦截
- onTouchEvent:在dispatchTouchEvent方法中调用,用来处理点击事件
对于ViewGroup来说,点击事件后,调用dispatchTouchEvent方法,此时ViewGroup的onInterceptTouchEvent方法被调用,返回true代表拦截,接着调用onTouchEvent处理事件,如果返回false,分发给子元素
当View要处理事件时,如果设置了onTouchListener,此时onTouch方法会被回调,返回false,view的ouTouchEvent会被调用,返回true,不会调用。onTouchListener优先级比onTouchEvent优先级高,onClickListener优先级最低
事件点击后传递顺序:Acticity,window,view,如果view不处理事件就会向上传递处理
总结:
- 同一个事件序列指手指触摸屏幕到离开这一过程事件
- 正常情况,一个事件序列只能给一个View处理
- 一旦一个View拦截了一个事件,那么它的同一个事件序列都直接交给它处理,并且它的onInterceptTouchEvent方法不再调用
- 某个View一旦开始处理事件,如果不消耗ACTION_DOWN事件的话,那么其它同一系列事件它也不会消耗,并且重新交给父元素处理
- 如果View不消耗ACTION_DOWN以外的其他事件,那么这个点击事件就会消失,并且父元素的onTouchEvent方法也不会调用,View可以持续收到后续的事件,最终这些事件都会交由Activity来处理
- ViewGroup默认不拦截任何事件
- View没有onInterceptTouchEvent方法,一旦有事件传递给它,他就调用onTouchEvent方法
- View的onTouchEvent方法都默认会消耗事件的,除非它是不可点击的
- View的enable属性不影响onTouchEvent的返回值
- 事件的传递方向是从外向内的,总是由父元素传递给子元素,子元素再传递给View
- onClick发生的前提是View可点击并且收到了down和up事件
网友评论