本文尝试着从View的一些基本概念,view的滑动,以及令人头疼的滑动冲突等来解析一下View.
什么是View
我们都知道Activity在Android中承担着可视化的功能,而显示的往往就是各种控件的组合,例如Button,TextView,甚至是复杂的ListVIew……而这些都是View.而我们所用的布局LinearLayout,RelativeLayout等就是一组View,也就是ViewGroup,但他们的本质还是一个View.View是一个抽象类.
View的滑动
在讲View的滑动之前,有必要先讲一下MotionEvent和TouchSlop等几个概念:
MotionEvent
在手指触摸屏幕后产生的事件中,最常用也就三个:
ACTION_DOWN:手指刚碰到屏幕
ACTION_MOVE:手指已经开始移动(移动的时候一直触发事件)
ACTION_UP:手指从屏幕上松开
而每一次触摸屏幕时都会触发相关事件,
只是点击屏幕然后松开:DOWN —> UP,
滑动一会然后松开:DOWN —> MOVE—> MOVE —> MOVE —> MOVE…… —>UP,
同时,我们可以通过系统提供的两组方法来获取相关坐标:
getX/getY:返回的是触摸部位相对于当前View左上角的坐标,
getRawX/getRawY:返回的是触摸部位相对于屏幕左上角的坐标,
TouchSlop
是一个系统默认滑动距离的常量,当你所滑动距离小于这个距离的时候,就会认为你没有做滑动操作,可通过:ViewConfiguration.get(getContext()).getScaledTouchSlop
来获取.,默认是8dp.
VelocityTracker
速度追踪,用于追踪手指在滑动过程中的速度,包括水平和垂直方向的,一般是在View的onTouchEvent()中使用:
//获取当前事件的速度
VelocityTracker velocityTracker = VelocityTracker.ontain();
velocityTracker.addMovement(event);//添加追踪事件
velocityTracker.computeCurrentVelocity(1000);//计算速度,一段时间内滑动的像素
int xVelocity = velocityTracker.getXVelocity();//获取水平速度
int yVelocity = velocityTracker.getyVelocity();//获取垂直速度
velocityTracker.();//清除
velocityTracker.recycle();//回收
以上是VelocityTracker的基本使用流程.
GestureDetector
手势检测器,包括检测用户的单击,滑动,长按,双击等.使用过程:
首先是创建GestureDetector对象,并实现OnGestureListener接口,
//要想让手势是识别器生效,必须将手势识别器注册到屏幕的触摸事件中
GestureDetector gestureDetector = new GestureDetector(this);
//接着,接管目标View的onTouchEvent()方法,
public boolean onTouchEvent(MotionEvent event) {
gestureDetector.onTouchEvent(event);
return super.onTouchEvent(event);
}
此外,GestureDetector并不是必须使用的,我们完全可以自己在目标View中实现相关滑动的判断.
Scroller
弹性滑动对象.用于实现View的弹性滑动,但它本身无法让VIew弹性滑动,需要结合View的computeScroller()配合来实现这个功能.
Scroller scroller = new Scroller(mContext);
//缓慢滑动到指定位置
private void smoothScrollerTo(int destX, int destY){
int scrollerX = getScrollerX();
int delta = destX-scrollerX;
//1000ms内慢慢滑向destX
scroller.startScroll(scrollerX,0,delta,1000);
invalidate();//申请重绘界面
}
@Override
private void computeScroller(){
if(scroller.computeScrollOffset()){
scrollTo(scroller.getCurrX(),scroller.getCurrY());
postInvalidate();
}
}
接下来开始正式讲View的滑动,有了前面的一点小概念,相信理解起来不会很枯燥.实现滑动主要有三种方法:使用View本身提供的scrollTo/scrollBy方法来实现滑动,通过动画给VIew施加平移实现滑动,改变View的layoutParams使得View重新布局从而实现滑动.
使用scrollTo/scrollBy
为了实现滑动,View提供来专门的方法来实现这个功能,其中scrollBy本质上也是调用scrollTo来实现的,它实现了基于当前位置的相对滑动,而scrollTo实现了基于所传递参数的绝对滑动.但它们均只能改变View位置的内容,而不能改变View在布局中的位置.
其中需要注意的是View内部的两个属性mScrollX和mScrollY,前面也说到了,可以分别使用getScrollerX和getScrollerY来得到.mScrollX指的是View的左边缘和View内容在左边缘的距离,mScrollY指的是View的上边缘和View内容在上边缘的距离.
使用动画
通过动画我们可以让一个View进行一个平移,平移本身就是一种滑动.实现也比较简单,主要是操作translationX和translationY属性.
//属性动画实现,改变的是View的位置
ObjectAnimator.ofFloat(targetView,"translationX",0,100).setDuration(100).start();
当然还可以用View的补间动画来实现.不过,我们需要知道的是:补间动画平移的也只是内容,而View的布局属性仍然是不变的,如果是想实现点击事件,那是无法完成的,而点击原来的位置又还能相应事件.
改变布局参数
就是改变LayoutParams,,这是比较灵活,也是比较简单的,直接获取控件的LayoutParams,然后设置变化的距离即可.
前面所讲的三种滑动方式只是比较生硬地滑动过去,为了良好的用户体验,我们最好使用弹性滑动.
View的弹性滑动
本质是:将一次大的滑动分成若干次小的滑动并在一定的时间内完成,就像静态画面向动态画面的过度,也就是渐进式.实现的方式有:前面说到的Scroller结合computeScroller,通过动画,通过延时机制等等
使用Scroller
前面也说了,也给了简单的demo:
Scroller scroller = new Scroller(mContext);
//缓慢滑动到指定位置
private void smoothScrollerTo(int destX, int destY){
int scrollerX = getScrollerX();
int delta = destX-scrollerX;
//1000ms内慢慢滑向destX
scroller.startScroll(scrollerX,0,delta,1000);
invalidate();//申请重绘界面
}
@Override
private void computeScroller(){
if(scroller.computeScrollOffset()){
scrollTo(scroller.getCurrX(),scroller.getCurrY());
postInvalidate();//再次申请重绘
}
}
那么这段代码是如何实现弹性滑动的呢?
其实关键是invalidate(),该方法会请求重绘界面,在View的draw方法中会调用computeScroller(),而computeScroller()内部又通过postInvalidate()申请重绘界面,同样还是会调用computeScroller()……想想,绘制的内容是一样,而且是在一定时间内重复这个动作的,那肯定是会形成一种动画效果的.者也是符合我们前面所说的那个本质的:将一次大的滑动分成若干次小的滑动并在一定的时间内完成.
通过动画
动画本来就是一种渐进的方式实习那滑动的.这个这里不多讲,动画也是Android中比较重要的,需要一定的篇幅来讲.大家有兴趣可以去看看相关的博客.
使用延时策略
核心是通过发送一系列的延时信息从而达到一种渐近式的方式,使滑动具有渐进式.还是比较简单的,无非是通过Handler或postDelayed().
事件分发机制
所谓事件分发,只的也就是MotionEvent事件的分发,主要涉及三个方法:
单个View需要重写onTouchEvent()和dispatchEvent()方法,ViewGroup需要多重写一个onInterceptTouchEvent()(拦截事件的核心)
dispatchTouchEvent:
触摸控件(View)首先执行dispatchTouchEvent方法。而在dispatchTouchEvent方法中先执行onTouch方法,后执行onClick方法(onClick方法在onTouchEvent中执行)。
onTouchEvent:
onTouchEvent方法中会在ACTION_UP分支中触发onClick的监听。
onInterceptTouchEvent:用来判断是否拦截某个事件,如果当前VIew拦截了某个事件,那么在同一个事件序列中,此方法不会再被调用.
我们可以设想一个场景来理解事件分发机制:假设ViewGroupA,ViewGroupB(在ViewGroupA中)和View分别代表公司里的总经理,部门经理和苦逼的你.那么有一个大客户(点击事件)有个需求,那么任务先经过总经理(ViewGroupA)分发下来(dispatchTouchEventA),总经理觉得这个任务要让下属来完成,那么不拦截(onInterceptTouchEventA返回false),那么任务需求分发(dispatchTouchEventB)到了部门经理(ViewGroupB)中,他也觉得任务需要手下来完成(onInterceptTouchEventB返回false),那么任务需求分发(dispatchTouchEventC)到了苦逼的你的手中,既然收人钱财,当然要干活啊(onTouchEventC),活干完了就要提交任务给部门经理,让部门经理来处理(onTouchEventB),部门经理完成后,觉得还行就提交给总经理,让他来处理(onTouchEventA),那么一次完整的任务就完成了...
分发过程:Activity–phoneWindow–DecorView–MyView
事件处理:onTouch(OnTouchListener)–onTouchEvent–onClick(OnClickListener)
这个View事件到此就分析完毕,还有很多细节的东西,大家最好还是自己去看看,接下来会讲一下View的滑动冲突...