自定义控件(视图)2期笔记10:自定义视图之View事件分发机制("瀑布流"的案例)
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个:
 ******.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,后面可以看到它有大用。
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


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事件分发机制("瀑布流"的案例)的更多相关文章
- 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象
		前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ... 
- 【朝花夕拾】Android自定义View篇之(五)Android事件分发机制(上)Touch三个重要方法的处理逻辑
		前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/10998855.html]谢谢! 在自定义View中,经常需要处理Android事件分发的问题, ... 
- Android程序员事件分发机制学习笔记
		通过问题来学习一个东西是很好的方法.学习Android中View的事件体系,我也通过给自己提问题,在解决问题的同时也就知道了其中原理. 首先来几个问题起步: 什么是事件?什么是事件分发机制? 在我们通 ... 
- Cocos2d-x 3.2 学习笔记(九)EventDispatcher事件分发机制
		EventDispatcher事件分发机制先创建事件,注册到事件管理中心_eventDispatcher,通过发布事件得到响应进行回调,完成事件流. 有五种不同的事件机制:EventListenerT ... 
- muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制
		目录 muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制 eventfd的使用 eventfd系统函数 使用示例 EventLoop对eventfd的封装 工作时序 runInLoo ... 
- 【Android - 自定义View】之View的事件分发机制
		参考资料: View事件分发:http://blog.csdn.net/pi9nc/article/details/9281829 ViewGroup事件分发:http://blog.csdn.net ... 
- 【朝花夕拾】Android自定义View篇之(七)Android事件分发机制(下)滑动冲突解决方案总结
		前言 转载请声明,转自[https://www.cnblogs.com/andy-songwei/p/11072989.html],谢谢! 前面两篇文章,花了很大篇幅讲解了Android的事件分发机制 ... 
- flask_admin 笔记四  自定义视图
		定义自己的视图 对于您的要求非常具体的情况,您很难用内置的ModelView类来满足这些需求,Flask-Admin使您可以轻松地完全控制并将自己的视图添加到界面中. 1)独立视图 可以通过扩展Bas ... 
- 10分钟了解Android的事件分发
		什么是事件分发? 大家知道Android中的视图是由一个个View嵌套构成的层级视图,即一个View里包含有子View,而这个子View里面又可以再添加View.当用户触摸屏幕产生一系列事件时,事件会 ... 
随机推荐
- HDFS 搭建记录
			1. 三台服务: 172.17.0.62(namenode) 172.17.0.68(datanode) 172.17.0.76(datanode) /etc/hosts包含的内容: 三台都包含的域名 ... 
- 浅析Linux操作系统工作的基础
			环境:lubuntu 13.04 kernel 3.9.7 作者:SA12226265 katao 简介: 本文根据 Linux™ 系统工作基础的分析,对存储程序计算机.堆栈(函数调用堆栈)机制和 ... 
- hustoj 1017 - Exact cover   dancing link
			1017 - Exact cover Time Limit: 15s Memory Limit: 128MB Special Judge Submissions: 5851 Solved: 3092 ... 
- [BZOJ 3620] 似乎在梦中见过的样子 【KMP】
			题目链接:BZOJ - 3620 题目分析 这道题使用 KMP 做 O(n^2) 的暴力就能过. 首先,我们依次枚举字串左端点 l ,然后从这个左端点开始向后做一次 KMP. 然后我们枚举右端点 r ... 
- Windows常见蓝屏故障分析
			转自Windows常见蓝屏故障分析 症状描述: 当您在运行Microsoft Windows 2000/XP/Server 2003.Microsoft Windows Vista/Server 20 ... 
- 转:synchronized和LOCK的实现原理---深入JVM锁机制
			JVM底层又是如何实现synchronized的? 目前在Java中存在两种锁机制:synchronized和Lock,Lock接口及其实现类是JDK5增加的内容,其作者是大名鼎鼎的并发专家Doug ... 
- ADI加速度计基础原理
- JSch - Java实现的SFTP(文件上传详解篇)(转)
			JSch是Java Secure Channel的缩写.JSch是一个SSH2的纯Java实现.它允许你连接到一个SSH服务器,并且可以使用端口转发,X11转发,文件传输等,当然你也可以集成它的功能到 ... 
- leetcode面试准备:Summary Ranges
			1 题目 Given a sorted integer array without duplicates, return the summary of its ranges. For example, ... 
- c++类型转换Type Cast)
			C风格的强制类型转换(Type Cast)很简单,不管什么类型的转换统统是:TYPE b = (TYPE)a.C++风格的类型转换提供了4种类型转换操作符来应对不同场合的应用. const_cast, ... 
