自定义控件(视图)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.当用户触摸屏幕产生一系列事件时,事件会 ...
随机推荐
- InputStream的封装类
package ex03.pyrmont.connector.http; import java.io.IOException; import java.io.InputStream; import ...
- 对rsync进行封装的shell脚本
抓取 #!/bin/bash . push.sh # 错误处理:尝试查找备份文件 function onError() { local errFile="err" local se ...
- OGNL学习-静态方法调用
<constant name="struts.ognl.allowStaticMethodAccess" value="true"/> 1.小写 & ...
- bzoj 3632: 外太空旅行 最大团
3632: 外太空旅行 Time Limit: 5 Sec Memory Limit: 128 MBSubmit: 389 Solved: 129[Submit][Status] Descript ...
- mysql 海量数据的存储和访问解决方案
第1章 引言 随着互联网应用的广泛普及,海量数据的存储和访问成为了系统设计的瓶颈问题.对于一个大型的互 联网应用,每天几十亿的PV无疑对数据库造成了相当高的负载.对于系统的稳定性和扩展性造成了极大的 ...
- What does the number on the visual studio solution icon represent?
The numbers correspond to the internal version numbers of various editions of Visual Studio http://e ...
- 通过UIImagePickerController完成照相和相片的选取
UIImagePickerController是用于选取现有照片,或者用照相机现场照一张相片使用的 定义: @interface ShowViewController : UIViewControll ...
- [git] git 分支管理和工作流程
分支管理 列举本地分支.下面的 * 是 HEAD 所指向的分支,标识当前工作目录所用的分支.其他分支隐藏在 git 仓库中,通过 git checkout 命令才能访问和修改. $ git branc ...
- Shtirlits - SGU 125(搜索)
题目大意:B[i, j]表示周围有多少个比它大的数,能否用B数组构造出一个A数组,如果不能输出“NO SOLUTION”. 分析:数据规模比较小,可以直接暴力枚举每个点的值. 代码如下: #inclu ...
- 创建oracle 密码文件
orapwd file='$ORACLE_HOME/dbs/oratest' password=oracle entries=5 force=y; 说明:●FILE参数指定口令文件存放的全路径及文件名 ...