第三章  View的事件体系

  3.1 View基础知识

  3.1.1 什么是view

    View 是Android中所有控件的基类,是一种界面层的控件的一种抽象,它代表了一个控件。

  3.1.2 View的位置参数

    View的位置主要由它的四个顶点来决定,分别对应于View的四个属性:top,left,right,bottom;需要注意的是这些坐标都是相对于View的父容器来说的;(在Android中X轴和Y轴的正方向分别为右和下)。

    3.0开始新增的属性

    x-  view左上角横坐标;

    y-  view左上角纵坐标;

    translationX-  view左上角相对于父容器的水平偏移量;

    translationY-  view左上角相对于父容器的垂直偏移量;

    View在平移过程中,top和left表示的是原始左上角的位置信息,其值并不会发生变化,此时发生变化的是x,y,translationX,translationY这四个参数。

  3.1.3 MotionEvent和TouchSlop

    1 MotionEvent

    getX和getY方法返回的是当前View左上角的x和y坐标,而getRawC和getRawY方法返回的是相对于手机屏幕左上角的x和y坐标。

    2 TouchSlop

    TouchSlop是系统所能识别出的被认为是滑动的最小距离,是一个常量,通过如下方式可以获得这个常量:ViewConfiguration.get(getContext()).getScaledTouchSlop();

  3.1.4 VelocityTracker,GestureDetector和Scroller

    1 VelocityTracker

    VelocityTracker—速度追踪,追踪手指在滑动过程中的速度(水平垂直两个方向),注意速度可以为负值,当手指从右向左滑动时,水平方向速度即为负值。

    2 GestureDetector

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

    建议:如果只是监听滑动相关的,在onTouchEvent中实现,如果要监听双击行为就用GestureDetector。

    3 Scroller

    Scroller—弹性滑动,用于实现View的弹性滑动(有过渡效果的滑动),需要配合View的computeScroll方法使用。

  3.2 View的滑动

    实现View滑动的三种方式:

    1 通过View本身的scrollTo/scrollBy方法实现滑动;

    2 通过动画给View施加平移效果实现滑动;

    3 通过改变View的LayoutParams使得View重新布局实现滑动;

  3.2.1 使用scrollTo/scrollBy

    scrollTo/scrollBy的源码如下:

public void scrollTo(int x ,int y){
if(mScrollX!=x||mScrollY!=y){
int oldX=mScrollX;
int oldY=mScrollY;
mScrollX=x;
mScrollY=y;
invalidateParentCaches();
onScrollChanged(mScrollX,mScrollY,oldX,oldY);
if(!awakenScrollBars()){
postInvalidateOnAnimation();
    }
  }
} public void scrollBy(int x,int y){
scrollTo(mScrollX+x,mScrollY+y);
}

    其中mScrollX的值总是等于View左边缘和View内容左边缘在水平方向的距离,mScrollY的值总是等于View的上边缘和View内容上边缘在垂直方向上的距离;

     scrollTo/scrollBy只能改变View内容的位置而不能改变自身在布局中的位置。

     也就是说使用scrollTo/scrollBy来实现View的滑动只能将View的内容进行移动,并不能将View本身进行移动。

  3.2.2 使用动画

    使用动画来移动View 主要是操作View的translationX和translationY属性。(传统的View动画和属性动画)

     View动画是对View的影像操作,它并不能真正改变View的位置参数,包括宽/高。

  3.2.3 改变布局参数

    主要是通过改变View的LayoutParams来实现;下面的代码展示如何给一个mButton1重新设置LayoutParams:

MarginLayoutParams params=(MarginLayoutParams)mButton1.getLayoutParams();
params.width+=100;
params.leftMargin+=100;
mButton1.requestLayout();
//或者mButton1.setLayoutParams(params)

  3.2.4各种滑动方式的对比

    scrollTo/scrollBy:操作简单,适合对View内容的滑动;

     动画:操作简单,主要适用于没有交互的View和实现复杂的动画效果;

     改变布局参数:操作稍微复杂,适用于有交互的View。

  3.3弹性滑动

  3.3.1使用Scroller

    注意Scroller产生的滑动也是对View内容的滑动而非View本身位置的改变。

    Scroller的典型使用方式如下:

Scroller scroller=new Scroller(context);
//缓慢滚动到指定位置
private void smoothScrollTo(int destX,int destY){
int scrollx=getScrollX();
int deltaX=destX-scrollX;
//1000ms内滑向destX,效果就是慢慢滑动
scroller.startScroll(scrollX,0,deltaX,0,1000);
invalidate();
}
@Override
public void computeScroll(){
if(scroller.computeScrollOffset()){
scrollTo(scroller.getCurrX(),scroller.getCurrY());
postInvalidate();
}
}

    Scroller的工作机制:Scroller本身并不能实现View的滑动,它需要配合View的computeScroll方法才能完成弹性滑动的效果,它不断的让View重绘,而每一次重绘距滑动起始时间会有一个时间间隔,通过这个时间间隔Scroller就可以得出View当前的滑动位置,知道了滑动位置就可以通过scrollTo方法来View的滑动。就这样,View的每一次重绘都会导致View进行小幅度的滑动,而多次的小幅度滑动就组成了弹性滑动。

  3.4 View的时间分发机制

  3.4.1 点击事件的传递规则

    点击事件的分发过程主要由三个方法来完成。

    dispatchTouchEvent() :用来进行事件的分发,如果事件能够传递给当前View,此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法影响,表示是否消耗当前事件。

    onInterceptTouchEvent() :在dispatchTouchEvent方法内部调用,判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列中,此方法不会在被调用,返回结果表示是否拦截当前事件。

    onTouchEvent() :在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到其他事件。

    事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程中所产生的一系列事件,这个事件序列以down事件开始,中间含有数量不固定的move事件,最终以up事件结束。

    理解了这三个方法的工作过程也就理解了事件的分发机制。伪代码表示三个方法的关系如下:

public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume=false;
if(onInterceptTouchEvent(ev)){
consume=onTouchEvent(ev);
}else{
consume=child.dispatchTouchEvent(ev);
}
return consume;
}

    具体传递规则:对于一个根ViewGroup来说,点击事件产生以后,首先会传递给它,这时它的dispatchTouchEvent就会被调用,如果这个ViewGroup的onInterceptTouchEvent方法返回true就表示它要拦截当前事件,接着事件就会交给这个ViewGroup处理,即它的onTouchEvent方法就会被调用,如果这个ViewGroup的onInterceptTouchEvent返回false就表示它不拦截当前事件,这时当前事件就会继续传递给它的子元素,接着子元素的dispatchTouchEvent方法就会被调用,如此反复直到事件被最终处理。

     当一个点击事件产生后,它的传递过程遵循如下顺序:Activity->Window->View,即事件总是先传递给Activity,Activity在传递给Window,最后Window在传递给顶级View。顶级View接收到事件后,就会按照事件分发机制去分发事件。考虑一种情况,如果一个View的onTouchEvent返回false,那么它的父容器的onTouchEvent将会被调用,依此类推。如果所用的元素都不处理这个事件,那么这个事件将会最终传递给Activity处理,即Activity的onTouchEvent方法被调用。
    几个重要结论:

    正常情况下,一个事件序列只能被一个VIew拦截且消耗,因为一旦一个元素拦截了某个事件,那么同一个事件序列内的所有事件都会直接交给它处理,因此同一个事件序列中的事件不能分别有两个View同时处理,但是通过特殊手段可以做到,比如一个View将本该自己处理的事件通过onTouchEvent强行传递给其他View处理。

    某个View一旦决定拦截,那么这一个事件序列都只能由它处理,并且它的onInterceptTouchEvent不会再被调用。

    某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件序列中的其他事件都不会再交给它处理,并且事件将重新交由它的父元素去处理,即父元素的onTouchEvent会被调用。意思就是事件一旦交给一个View处理,那么它就必须消耗掉,否则同一事件序列中剩下的事件就不再交给它来处理了。

    如果View不消耗出ACTION_DOWN以外的其他事件,那么这个点击事件会消失,最终这些消失的点击事件会传递给Activity处理。

    ViewGroup默认不拦截任何事件。

    View没有onInterceptTouchEvent方法,一旦有事件传递给它,那么它的onTouchEvent方法就会被调用。

    View的OnTouchEvent方法默认都会消耗事件(返回true)。

    View的enable属性不影响onTouchEvent的默认返回值。

    onClick会发生的前提是当前View是可点击的,并且它收到了down和up的事件。

    事件的传递过程是由外向内的,即事件总是项传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外。(requestDisallowInterceptTouchEvent方法主要设置父元素中的FLAG_DISALLOW_INTERCEPT标记位,一旦设置后,ViewGroup将无法拦截除了ACTION_DOWN以外的其他点击事件)

  3.5 View的滑动冲突

    常见的滑动冲突场景

    

     解决滑动冲突的方式:外部拦截法和内部拦截法。

    1 外部拦截法

    点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截。

    实现方法主要是重写父容器的onInterceptTouchEvent方法,代码如下:

  @Override
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;
}

    2 内部拦截法

    父容器不拦截任何事件,所用的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理。需要配合requestDisallowInterceptTouchEvent方法才能正常工作,同时重写子元素的dispatchTouchEvent方法,代码如下:

    子元素:

   @Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x=(int)event.getX();
int y=(int)event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int deltaX=x-mLastX;
int deltaY=y-mLastY;
if(父容器需要当前点击事件){
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
mLastX=x;
mLastY=y;
return super.dispatchTouchEvent(event);
}

    父元素:

    @Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int action=event.getAction();
if (action==MotionEvent.ACTION_DOWN){
return false;
}else{
return true;
}
}

    以上就是滑动处理滑动冲突的典型代码,当面对不同的滑动策略时(场景1,2,3)只需要修改里面的条件即可。

            

Android开发艺术探索学习笔记(三)的更多相关文章

  1. Android开发艺术探索学习笔记(一)

    第一章 Activity的生命周期和启动模式 1.1Activity的生命周期全面解析 1.1.1典型情况下的生命周期分析 (1)在两个Activity进行切换时,当前的Activity的onPaus ...

  2. Android开发艺术探索学习笔记(十一)

    第十一章  Android的线程和线程池 从用途上来说,线程分为子线程和主线程,主线程主要处理和界面相关的事情,而子线程往往用于执行耗时的操作.AsyncTask,IntentService,Hand ...

  3. Android开发艺术探索学习笔记(十)

    第十章  Android的消息机制 面试中经常会被问到的一个问题:handler是如何在子线程和主线程中进行消息的传递的,这个问题通过了解Android的消息机制可以得到一个准确的答案. Androi ...

  4. Android开发艺术探索学习笔记(六)

    第六章 Android的Drawable  Drawable的优点:使用简单,比自定义view的成本要低:非图片类型的Drawable占用空间小,有利于减小APK安装包的大小. 6.1Drawable ...

  5. Android开发艺术探索学习笔记(四)

    第四章 View的工作原理 4.1初识ViewRoot和DecorView ViewRoot是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成 ...

  6. Android开发艺术探索读书笔记——进程间通信

    1. 多进程使用场景 1) 应用某些模块由于特殊需求须要执行在单独进程中. 如消息推送,使消息推送进程与应用进程能单独存活,消息推送进程不会由于应用程序进程crash而受影响. 2) 为加大一个应用可 ...

  7. Android开发艺术探索读书笔记——01 Activity的生命周期

    http://www.cnblogs.com/csonezp/p/5121142.html 新买了一本书,<Android开发艺术探索>.这本书算是一本进阶书籍,适合有一定安卓开发基础,做 ...

  8. android开发艺术探索学习 之 结合Activity的生命周期了解Activity的LaunchMode

    转载请标明出处: http://blog.csdn.net/lxk_1993/article/details/50749728 本文出自:[lxk_1993的博客]: 首先还是先介绍下Activity ...

  9. android开发艺术探索读书笔记之-------view的事件分发机制

    View的点击事件的分发,其实就是对MotionEvent事件的分发过程,即当一个MotionEvent产生后,系统需要把这个事件传递给一个具体的View,而这个过程就是分发过程. 分发过程主要由以下 ...

随机推荐

  1. C#对象与XMl文件之间的相互转换

    C#提供三种序列化方式,分别为: 1.是使用BinaryFormatter进行串行化: 2.使用SoapFormatter进行串行化: 3.使用XmlSerializer进行串行化.其中对于Binar ...

  2. IOS开发UI基础UIImageView属性属性

    UIImageView属性 1.Image 设置图片,默认显示 UIImageView *_imageView = [[UIImageView alloc]init]; _imageView.imag ...

  3. $.ajax、$.post、from表单序列化工具

    $.ajax\$.post <script type="text/javascript" language="javascript" src=" ...

  4. JS代码放置位置、变量与数据类型、运算符与逻辑表达运算符

    内容简要: 1.JS代码放置位置的问题: 2.变量与数据类型: 3.运算符与逻辑表达式的运算符   我的位置 全局问题:为何在网页推荐位置(一般在<head></head>内部 ...

  5. sql联合查询去除重复计算总和

    1.首先来个联合查询 SELECT 字段1, 字段2, 字段3, 字段4 FROM 表1 INNER JOIN 表2 ON 表1.字段x = 表2.字段x x:代表随意的一个,只要在联合查询的两张表都 ...

  6. 怎样用C#代码屏蔽任务管理器?

    这是我在网上找的并多加了一些我自己需要的代码,经过我的测试,可以屏蔽任务管理器,但还有一些瑕疵. 首先,我在vs2012中新建一个项目,选择window下的window窗体应用程序,把窗体form1拉 ...

  7. Csharp: read excel file using Open XML SDK 2.5

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  8. Delphi又要换东家了

    前几天听到这个消息,搞个FMX出来,64位还没搞清楚,又开始折腾了!http://www.deltics.co.nz/blog/posts/2371 No Seriously – Let’s Buy ...

  9. 利用php实现:当获取的网址不是特定网址时候跳转到指定地址

    这个问题是在百度知道看到的问答,我不懂做,特定去百度了下.然后结合别人获取域名和跳转的知识,综合做了这个功能,以下是实现代码: <?php //获取当前的域名: echo "获取到的域 ...

  10. java入门基础知识点总结

    JavaScript他是一种描述性语言,其实他并不难学,只要用心学,一定会学好,我相信大家在看这篇文章的时候,一定也学过HTML吧,使用JavaScript就是为了能和网页有更好的交互,下面切入主题. ...