您的位置:首页 > 其它

自定义控件(视图)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的点击监听和触摸监听:

 ******.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效果图,如下:



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: