自定义控件(视图)28期笔记10:自定义视图之Touch事件传递机制("瀑布流"的案例)
2015-10-04 16:57
387 查看
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的点击监听和触摸监听:
我们简称为onClick监听和onTouch监听,一般程序会注册这两个监听。从上面可以看到,onTouch监听里默认return false。不要小看了这个return false,后面可以看到它有大用。
[b](2)Android中的dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()[/b]
dispatchTouchEvent:此方法一般用于处理触摸事件分发,事件(多数情况)是从Activity的dispatchTouchEvent开始的。通常会调用super.dispatchTouchEvent(ev),事件向下分发。这样就会继续调用[b]onInterceptTouchEvent,再由[b]onInterceptTouchEvent决定事件的流向。[/b][/b]
onInterceptTouchEvent:它是ViewGroup提供的方法,默认返回false,返回true表示拦截。
如返回值为true,事件会传递到自己的onTouchEvent();
如返回值为false,[b]事件传递到下一个view的dispatchTouchEvent();[/b]
onTouchEvent:它是View中提供的方法,ViewGroup也有这个方法,view中不提供onInterceptTouchEvent。view中默认返回true,表示消费了这个事件。
[b]如返回值为true,事件由自己处理消耗,后续动作序列让其处理;[/b]
[b]如返回值为false,自己不消耗事件了,向上返回让其他的父view的onTouchEvent接受处理;[/b]
View里,有两个回调函数 :
ViewGroup里,有三个回调函数 :
在Activity里,有两个回调函数 :
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()返回true,后续事件(ACTION_MOVE、ACTION_UP)会再传递,如果返回false,dispatchTouchEvent()就接收不到ACTION_UP、ACTION_MOVE。
下面的几张图参考自[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. 通过一个" 瀑布流 "[b]案例解析Touch事件的传递机制:[/b]
(1)工程一览图,如下:
(2)首先我们看看我们的主布局文件activity_main.xml,如下:
其中每一个ListView的Item子项目的布局文件lv_item.xml,如下:
这个lv_item.xml布局效果如下:
(3)自定义ViewGroup---MyLinearLayout,如下:
(4)回到了MainActivity.java,如下:
(5)布署项目到模拟器上,效果如下:
动态gif效果图,如下:
图解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,后面可以看到它有大用。
[b](2)Android中的dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()[/b]
dispatchTouchEvent:此方法一般用于处理触摸事件分发,事件(多数情况)是从Activity的dispatchTouchEvent开始的。通常会调用super.dispatchTouchEvent(ev),事件向下分发。这样就会继续调用[b]onInterceptTouchEvent,再由[b]onInterceptTouchEvent决定事件的流向。[/b][/b]
onInterceptTouchEvent:它是ViewGroup提供的方法,默认返回false,返回true表示拦截。
如返回值为true,事件会传递到自己的onTouchEvent();
如返回值为false,[b]事件传递到下一个view的dispatchTouchEvent();[/b]
onTouchEvent:它是View中提供的方法,ViewGroup也有这个方法,view中不提供onInterceptTouchEvent。view中默认返回true,表示消费了这个事件。
[b]如返回值为true,事件由自己处理消耗,后续动作序列让其处理;[/b]
[b]如返回值为false,自己不消耗事件了,向上返回让其他的父view的onTouchEvent接受处理;[/b]
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()返回true,后续事件(ACTION_MOVE、ACTION_UP)会再传递,如果返回false,dispatchTouchEvent()就接收不到ACTION_UP、ACTION_MOVE。
下面的几张图参考自[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. 通过一个" 瀑布流 "[b]案例解析Touch事件的传递机制:[/b]
(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效果图,如下:
相关文章推荐
- BZOJ4294 : [PA2015]Fibonacci
- win10和os x el capitan分屏操作对比视频
- c++大整数类的几种实现方法与解析
- 使用jpgraph
- 【软工视频—小小知识点(二)】
- 用MyEclipse10开发基于JAX-WS的Web Service实例
- Lighttpd插件链
- BZOJ1036 ZJOI2008 树的统计
- SQL--contains用法
- 归并
- linux sar
- Apache Ignite——新一代数据库缓存系统
- form elements
- iOS开发-使用Storyboard进行界面跳转及传值
- 如何盗取别人的微信密码
- 软考路之线性表
- C/C++字节与内存问题
- Scala学习笔记03【学习识别Scala函数式风格】
- 【Redis常见问题】
- salt stack的远程命令如何执行-笔记