1. Touch事件的传递:

  图解Touch事件的传递,如下:

当我们点击子View 02内部的Button控件时候,我们就触发了Touch事件。

• 这个Touch事件首先传递给了顶级父View,于是这个顶级父View开始遍历自己的子view(父View 01 和 父View 02 是顶级父View的子View),

判断这个Touch点击事件是在 父View 01上面 还是在 父View 02上面,判断知道在父 View 02上面。

• 父View 02再次遍历自己的子View(子View 01 和 子View 02 是父View 02的子View),判断得知这个Touch点击事件是在子View 02上面。

• 子View 02判断再次遍历自己的子View,判断得知这个Touch点击事件是在Button上面。

2. 深入研究android的事件传递机制:

(1)View的dispatchTouchEvent 和 onTouchEvent:

     探讨Android事件传递机制前,明确android的两大基础控件类型:View和ViewGroup。View即普通的控件,没有子布局的,如Button、TextView. ViewGroup继承自View,表示可以有子控件,如Linearlayout、Listview这些。而事件即MotionEvent,最重要的有3个:

• MotionEvent.ACTION_DOWN      按下View,是所有事件的开始
• MotionEvent.ACTION_MOVE       滑动事件
• MotionEvent.ACTION_UP            与down对应,表示抬起
 
另外,明确事件传递机制的最终目的都是为了触发执行View的点击监听和触摸监听:
 ******.setOnClickListener(new View.OnClickListener() {

             @Override
public void onClick(View v) {
// TODO Auto-generated method stub
Log.i(tag, "testLinelayout---onClick...");
}
}); *******.setOnTouchListener(new View.OnTouchListener() { @Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub return false;
}
});

我们简称为onClick监听和onTouch监听,一般程序会注册这两个监听。从上面可以看到,onTouch监听里默认return false。不要小看了这个return false,后面可以看到它有大用。

(2)Android中的dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()

     dispatchTouchEvent:此方法一般用于处理触摸事件分发,事件(多数情况)是从Activity的dispatchTouchEvent开始的。通常会调用super.dispatchTouchEvent(ev),事件向下分发。这样就会继续调用onInterceptTouchEvent,再由onInterceptTouchEvent决定事件的流向。

     onInterceptTouchEvent:它是ViewGroup提供的方法,默认返回false,返回true表示拦截。

                               如返回值为true,事件会传递到自己的onTouchEvent();

             如返回值为false,事件传递到下一个view的dispatchTouchEvent();

     onTouchEvent:它是View中提供的方法,ViewGroup也有这个方法,view中不提供onInterceptTouchEvent。view中默认返回true,表示消费了这个事件。               

             返回值为true,事件由自己处理消耗,后续动作序列让其处理;

                 返回值为false,自己不消耗事件了,向上返回让其他的父view的onTouchEvent接受处理;

  

     View里,有两个回调函数 :

 public boolean dispatchTouchEvent(MotionEvent ev);
public boolean onTouchEvent(MotionEvent ev);

     ViewGroup里,有三个回调函数 :

 public boolean dispatchTouchEvent(MotionEvent ev);
public boolean onInterceptTouchEvent(MotionEvent ev);
public boolean onTouchEvent(MotionEvent ev);

     在Activity里,有两个回调函数 :

 public boolean dispatchTouchEvent(MotionEvent ev);
public boolean onTouchEvent(MotionEvent ev);

      Android中默认情况下事件传递是由最终的view的接收到,传递过程是从父布局到子布局,也就是从Activity到ViewGroup到View的过程,默认情况,ViewGroup起到的是透传作用。Android中事件传递过程(按箭头方向)如下图,图片来自[qiushuiqifei],谢谢[qiushuiqifei]整理。

     触摸事件是一连串ACTION_DOWN,ACTION_MOVE..MOVE…MOVE、最后ACTION_UP,触摸事件还有ACTION_CANCEL事件。事件都是从ACTION_DOWN开始的,Activity的dispatchTouchEvent()首先接收到ACTION_DOWN,执行super.dispatchTouchEvent(ev),事件向下分发。

     dispatchTouchEvent()用于事件分发,如果事件能够传递给当前View,那么此方法一定会被调用,返回值受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消费当前事件。

     dispatchTouchEvent()返回true,表示当前View(ViewGroup)消费这个事件,后续事件(ACTION_MOVE、ACTION_UP)会再传递;如果返回false,表示当前View(ViewGroup)不消费这个事件,dispatchTouchEvent()就接收不到ACTION_UP、ACTION_MOVE。

   dispatchTouchEvent()作用是将touch事件向下传递直到遇到被触发的目标view如果返回true表示当前view就是目标view,事件停止向下分发。否则返回false表示当前view不是目标view需要继续向下分发寻找目标view。这个方法也可以被重载,手动分配事件。

下面的几张图参考自[eoe]

   图1. ACTION_DOWN都没被消费

图2-1.ACTION_DOWN被View消费了

图2-2. 后续ACTION_MOVE和UP在不被拦截的情况下都会去找VIEW

 
                                  图3.后续的被拦截了
 
 
                              图4 ACTION_DOWN一开始就被拦截

android中的Touch事件都是从ACTION_DOWN开始的:

单手指操作:ACTION_DOWN---ACTION_MOVE----ACTION_UP

多手指操作:ACTION_DOWN---ACTION_POINTER_DOWN---ACTION_MOVE--ACTION_POINTER_UP---ACTION_UP.

(3)关于ViewGroup中requestDisallowInterceptTouchEvent的用法:

void  requestDisallowInterceptTouchEvent(boolean  disallowIntercept):

这个方法的入参一个boolean 变量,用来表示是否需要调用onInterceptTouchEvent来判断是否拦截

该标记如果为True,就如它的字面意思一样---不允许调用onInterceptTouchEvent(),结果就是,所有的父类方法都不会进行拦截,而把事件传递给子View. 该方法属于ViewGroup ,并且是个递归方法,也就是说一旦调用后,所有父类的disallowIntercept都会设置成True。即当前View的所有父类View,都不会调用自身的onInterceptTouchEvent()进行拦截。

该标记如果为False,就如它的字面意思一样---允许调用onInterceptTouchEvent(),结果就是,父类可以拦截事件。

requestDisallowInterceptTouchEvent 是ViewGroup类中的一个公用方法,参数是一个boolean值,官方介绍如下:

Called when a child does not want this parent and its ancestors to intercept touch events with ViewGroup.onInterceptTouchEvent(MotionEvent).

This parent should pass this call onto its parents. This parent must obey this request for the duration of the touch (that is, only clear the flag after this parent has received an up or a cancel.

android系统中,一次点击事件是从父view传递到子view中,每一层的view可以决定是否拦截并处理点击事件或者传递到下一层,如果子view不处理点击事件,则该事件会传递会父view,由父view去决定是否处理该点击事件。在子view可以通过设置此方法去告诉父view不要拦截并处理点击事件,父view应该接受这个请求直到此次点击事件结束。

实际的应用中,可以在子view的ontouch事件中注入父ViewGroup的实例,并调用requestDisallowInterceptTouchEvent去阻止父view拦截点击事件

 public boolean onTouch(View v, MotionEvent event) {
ViewGroup viewGroup = (ViewGroup) v.getParent();
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
viewGroup.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
viewGroup .requestDisallowInterceptTouchEvent(false);
break;
}
}

3. 通过一个" 瀑布流 "例解析Touch事件的传递机制:

(1)工程一览图,如下:

(2)首先我们看看我们的主布局文件activity_main.xml,如下:

 <com.example.pinterestlistview.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/mll"
tools:context=".MainActivity" > <ListView
android:id="@+id/lv2"
android:scrollbars="none"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" /> <ListView
android:id="@+id/lv1"
android:scrollbars="none"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" /> <ListView
android:id="@+id/lv3"
android:scrollbars="none"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" /> </com.example.pinterestlistview.MyLinearLayout>

其中每一个ListView的Item子项目的布局文件lv_item.xml,如下:

 <ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:src="@drawable/default1" />

这个lv_item.xml布局效果如下:

(3)自定义ViewGroup---MyLinearLayout,如下:

 package com.example.pinterestlistview;

 import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout; public class MyLinearLayout extends LinearLayout { public MyLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
} @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
} /*
* 事件传递机制:
* 1. view执行dispatchTouchEvent方法,开始分发事件 ;
* 2. 执行onInterceptTouchEvent 判断是否是中断事件 ;
* 3. 执行onTouchEvent方法,去处理事件
*
*/ /**
* 分发事件的方法,最早执行
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(ev);
} @Override
public boolean onTouchEvent(MotionEvent event) { //这里getChildCount()是3,那么width是屏幕宽度的1/3
int width=getWidth()/getChildCount();
int height = getHeight();
//count=getChildCount()=3
int count=getChildCount(); float eventX = event.getX(); if (eventX<width){ // 滑动左边的 listView
event.setLocation(width/2, event.getY());//告诉左边的listView,事件触发点在左边的listview的x坐标的一半处(y坐标随意滑动)
float eventY = event.getY();
if (eventY < height / 2) {
event.setLocation(width / 2, event.getY());
getChildAt(0).dispatchTouchEvent(event);
getChildAt(2).dispatchTouchEvent(event); System.out.println("左边的listview的上半部分:"+eventY);
return true;
} else if (eventY > height / 2) {
event.setLocation(width / 2, event.getY());
try {
getChildAt(0).dispatchTouchEvent(event);
} catch (Exception e) {
e.printStackTrace();
} System.out.println("左边的listview的下半部分:"+eventY);
return true;
}
return true; } else if (eventX > width && eventX < 2 * width) { //滑动中间的 listView
float eventY = event.getY();
if (eventY < height / 2) {//滑动中间listView上半部分(0 < eventY < height /2)
event.setLocation(width / 2, event.getY());//告诉中间的listView,事件触发点在中间listview的x坐标的一半处(y坐标随意滑动)
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
try {
child.dispatchTouchEvent(event);
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("中间listview的上半部分:"+eventY);
return true;
} else if (eventY > height / 2) {//滑动中间listView下半部分(height / 2 < eventY < height)
event.setLocation(width / 2, event.getY());//告诉中间的listView,事件触发点在中间listview的x坐标的一半处(y坐标随意滑动)
try {
getChildAt(1).dispatchTouchEvent(event);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("中间listview的下半部分:"+eventY);
return true; }
}else if (eventX>2*width){// 滑动右边的 listView
//event.setLocation(width/2, event.getY());//告诉右边的listView,事件触发点在右边listview的x坐标的一半处(y坐标随意滑动)
//getChildAt(2).dispatchTouchEvent(event);
float eventY = event.getY();
if (eventY < height / 2) {
event.setLocation(width / 2, event.getY());
getChildAt(0).dispatchTouchEvent(event);
getChildAt(2).dispatchTouchEvent(event); System.out.println("右边listview的上半部分:"+eventY);
return true;
} else if (eventY > height / 2) {
event.setLocation(width / 2, event.getY());
try {
getChildAt(2).dispatchTouchEvent(event);
} catch (Exception e) {
e.printStackTrace();
} System.out.println("右边listview的下半部分:"+eventY);
return true;
} return true;
} return true;
} }

(4)回到了MainActivity.java,如下:

 package com.example.pinterestlistview;

 import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView; public class MainActivity extends Activity { private ListView lv1;
private ListView lv2;
private ListView lv3; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); lv1 = (ListView) findViewById(R.id.lv1);
lv2 = (ListView) findViewById(R.id.lv2);
lv3 = (ListView) findViewById(R.id.lv3); try {
lv1.setAdapter(new MyAdapter1());
lv2.setAdapter(new MyAdapter1());
lv3.setAdapter(new MyAdapter1());
} catch (Exception e) {
e.printStackTrace();
} } private int ids[] = new int[] { R.drawable.default1, R.drawable.girl1,
R.drawable.girl2, R.drawable.girl3 }; class MyAdapter1 extends BaseAdapter { @Override
public int getCount() {
return 3000;
} @Override
public Object getItem(int position) {
return position;
} @Override
public long getItemId(int position) {
return 0;
} @Override
public View getView(int position, View convertView, ViewGroup parent) { ImageView iv = (ImageView) View.inflate(getApplicationContext(),
R.layout.lv_item, null);
int resId = (int) (Math.random() * 4);
iv.setImageResource(ids[resId]);
return iv;
}
}
}

(5)布署项目到模拟器上,效果如下:

动态gif效果图,如下:

自定义控件(视图)2期笔记10:自定义视图之View事件分发机制("瀑布流"的案例)的更多相关文章

  1. 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...

  2. 【朝花夕拾】Android自定义View篇之(五)Android事件分发机制(上)Touch三个重要方法的处理逻辑

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/10998855.html]谢谢! 在自定义View中,经常需要处理Android事件分发的问题, ...

  3. Android程序员事件分发机制学习笔记

    通过问题来学习一个东西是很好的方法.学习Android中View的事件体系,我也通过给自己提问题,在解决问题的同时也就知道了其中原理. 首先来几个问题起步: 什么是事件?什么是事件分发机制? 在我们通 ...

  4. Cocos2d-x 3.2 学习笔记(九)EventDispatcher事件分发机制

    EventDispatcher事件分发机制先创建事件,注册到事件管理中心_eventDispatcher,通过发布事件得到响应进行回调,完成事件流. 有五种不同的事件机制:EventListenerT ...

  5. muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制

    目录 muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制 eventfd的使用 eventfd系统函数 使用示例 EventLoop对eventfd的封装 工作时序 runInLoo ...

  6. 【Android - 自定义View】之View的事件分发机制

    参考资料: View事件分发:http://blog.csdn.net/pi9nc/article/details/9281829 ViewGroup事件分发:http://blog.csdn.net ...

  7. 【朝花夕拾】Android自定义View篇之(七)Android事件分发机制(下)滑动冲突解决方案总结

    前言 转载请声明,转自[https://www.cnblogs.com/andy-songwei/p/11072989.html],谢谢! 前面两篇文章,花了很大篇幅讲解了Android的事件分发机制 ...

  8. flask_admin 笔记四 自定义视图

    定义自己的视图 对于您的要求非常具体的情况,您很难用内置的ModelView类来满足这些需求,Flask-Admin使您可以轻松地完全控制并将自己的视图添加到界面中. 1)独立视图 可以通过扩展Bas ...

  9. 10分钟了解Android的事件分发

    什么是事件分发? 大家知道Android中的视图是由一个个View嵌套构成的层级视图,即一个View里包含有子View,而这个子View里面又可以再添加View.当用户触摸屏幕产生一系列事件时,事件会 ...

随机推荐

  1. 一些有用的 Emacs 配置(窗口快速切换、一键透明效果、任意位置删除整行等)

    本篇文章记录的是一些有用的 Emacs 配置,有些是自己原创,有些是借鉴别人(能记起来出处的我放了链接). 规定:C 代表 Ctrl,M 代表 Alt. 1.设置一次跳跃 n 行的快捷键 按 C-M- ...

  2. 【HTTP】HTTP access control (CORS)

    https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS Cross-site HTTP requests are H ...

  3. C语言常用数学函数及其用法

    转自:http://blog.sina.com.cn/s/blog_8b5a0d0001011779.html 三角函数:(所有参数必须为弧度)  1.acos 函数申明:acos  (double ...

  4. Android+clipse导入工程提示:invalid project description

    今天遇到一个奇怪的问题.一个android的工程用eclipse导入的时候,提示错误.错误为:invalid project description . details为xxxx project ov ...

  5. 许令波老师的java的IO机制分析文章

    深入分析 Java I/O 的工作机制 I/O 问题可以说是当今互联网 Web 应用中所面临的主要问题之一,因为当前在这个海量数据时代,数据在网络中随处流动.这个流动的过程中都涉及到 I/O 问题,可 ...

  6. Linux学习笔记1——Linux的目录结构

    / 是根目录 ~是主目录 bin 存放二进制可执行文件(Is,cat,mkdir等) boot 存放用于系统引导时使用的各种文件 dev 用于存放设备文件 etc 存放系统配置文件 home 存放所有 ...

  7. mapreduce 倒排索引的建立

    大道至简 http://blog.csdn.net/hguisu/article/details/7969757 1.map的输入 key: 文档 id   value: 文档内容 输出: key   ...

  8. SDN基础理解

    本文转载自:http://blog.csdn.net/freezgw1985/article/details/16873677 个人觉得对很适合对SDN的入门级的概念性理解,先暂时copy一下,等研究 ...

  9. Transaction: atomicity, consistency, separability, persistence

    Transaction: atomicity, consistency, separability, persistence http://langgufu.iteye.com/blog/143440 ...

  10. Apache 一台主机绑定多个域名及虚拟主机

    今天研究了下Apache下如何使用一台主机绑定多个域名且使用80端口.说白了就是在一台主机上运行多个网站,并且网站域名都是使用的80端口. 具体方法如下: 1.进入Apache conf目录,找到ht ...