您的位置:首页 > 移动开发 > Android开发

android事件传递机制的详细了解

2015-09-19 13:13 597 查看
android项目开发的目的是为了让用户使用,那么用户使用的最直接的方式就是屏幕的点击操作,所以关于点击方面的事件我还是得好好了解的,所以,我开始去了解关于屏幕点击方面事件的传递机制

在屏幕接收触摸事件的情况中,总体来说是两种情况

1、View接收触摸事件

public boolean dispatchTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event)
至于我们常用的onClick事件,则是在onTouchEvent之后的,这个可以后面分析

2、ViewGroup接收触摸事件

public boolean dispatchTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event)
public boolean onInterceptTouchEvent(MotionEvent event)


那么,了解到有哪些方法之后就可以对方法进行了解

第一步、我们需要知道这些方法的作用以及参数作用

/**
     * 方法用于事件的分发,Android中所有的事件都必须经过这个方法的分发,然后决定是自身消费当前事件还是继续往下分发给子控件处理。
     * @param MotionEvent	MotionEvent继承于InputEvent,用于标记各种动作事件。
     * 						按下(ACTION_DOWN)
     *						移动(ACTION_MOVE)
     *						抬起(ACTION_UP)
     * @return	boolean	返回true表示不继续分发,事件没有被消费。返回false则继续往下分发,
     * 					如果是ViewGroup则分发给onInterceptTouchEvent进行判断是否拦截该事件。
     */
    public boolean dispatchTouchEvent(MotionEvent event)


/**
     * 方法用于事件的处理
     * @param MotionEvent	MotionEvent继承于InputEvent,用于标记各种动作事件。
     * 						按下(ACTION_DOWN)
     *						移动(ACTION_MOVE)
     *						抬起(ACTION_UP)
     * @return	boolean	返回true表示消费处理当前事件,返回false则不处理,交给子控件进行继续分发。
     */
    public boolean onTouchEvent(MotionEvent event)


/**
     * 是ViewGroup中才有的方法,View中没有,它的作用是负责事件的拦截
     * @param MotionEvent	MotionEvent继承于InputEvent,用于标记各种动作事件。
     * 						按下(ACTION_DOWN)
     *						移动(ACTION_MOVE)
     *						抬起(ACTION_UP)
     * @return	boolean	返回true的时候表示拦截当前事件,不继续往下分发,交给自身的onTouchEvent进行处理。
     * 					返回false则不拦截,继续往下传。这是ViewGroup特有的方法,因为ViewGroup中可能					还有子View,而在Android中View中是不能再包含子View的
     */
	public boolean onInterceptTouchEvent(MotionEvent ev)


那么在了解了这些方法的作用之后,我们还需要了解这些方法的执行顺序

第二步、方法的执行顺序

其实看了上面方法的注解后就应该能猜到一点调用的顺序了,但是实际我们还是需要在程序中验证的,首先来测试一下View

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}
	
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
    	switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			Log.i("main", "MainActivity---dispatchTouchEvent---DOWN");
			break;
		case MotionEvent.ACTION_MOVE:
			Log.i("main", "MainActivity---dispatchTouchEvent---MOVE");
			break;
		case MotionEvent.ACTION_UP:
			Log.i("main", "MainActivity---dispatchTouchEvent---UP");
			break;
		default:
			break;
		}
    	return super.dispatchTouchEvent(event);
    }
      
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
    	switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			Log.i("main", "MainActivity---onTouchEvent---DOWN");
			break;
		case MotionEvent.ACTION_MOVE:
			Log.i("main", "MainActivity---onTouchEvent---MOVE");
			break;
		case MotionEvent.ACTION_UP:
			Log.i("main", "MainActivity---onTouchEvent---UP");
			break;
		default:
			break;
		}
    	return super.onTouchEvent(event);
    }

}


public class MyView extends View{  
	/** 
     * 文本 
     */  
    private String mTitleText;  
    /** 
     * 文本的颜色 
     */  
    private int mTitleTextColor;  
    /** 
     * 文本的大小 
     */  
    private float mTitleTextSize;  
  
    /** 
     * 绘制时控制文本绘制的范围 
     */  
    private Rect mBound;  
    private Rect mBound2;
    private Paint mPaint;  
    private Paint mPaint2;  
    
    public MyView(Context context) {
		this(context,null);
	}
	  
    public MyView(Context context, AttributeSet attrs) {  
        this(context, attrs,0);  
    }  
    
	public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		initMyViewData(context, attrs);
	}
    
    private void initMyViewData(Context context,AttributeSet attrs){
    	//这里获取到我们在attrs.xml设置的declare-styleable
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyView);
        //然后通过declare-styleable获取到之前定义在里面的几个属性
        mTitleText = array.getString(R.styleable.MyView_titleText);
        mTitleTextColor = array.getColor(R.styleable.MyView_titleTextColor, Color.BLACK);
        mTitleTextSize = array.getDimension(R.styleable.MyView_titleTextSize, 36);
        array.recycle(); //一定要调用,否则这次的设定会对下次的使用造成影响  
        /** 
         * 获得绘制文本的宽和高 
         */  
        mPaint = new Paint();  
        mPaint2 = new Paint();
        mPaint.setTextSize(mTitleTextSize);  
        mPaint2.setTextSize(mTitleTextSize);  
        // mPaint.setColor(mTitleTextColor);  
        mBound = new Rect();  
        mBound2 = new Rect();
        //这句话我自己的理解是在mBound的矩形内,适配mTitleText第一个字符到最后一个字符的范围,这里是我第二和第三个参数填的0和字符串长度所以是第一个字符到最后一个字符的宽度范围
        mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
        Log.i("main", "结果:" + mBound.left + ",右边:" + mBound.right + ",上面:" + mBound.top + ",下面:" + mBound.bottom);
        
        mPaint2.getTextBounds("我是第二行吗", 0, 6, mBound2);
    }
    
    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
    		int bottom) {
    	super.onLayout(changed, left, top, right, bottom);
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    	super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    
    
    @Override
    protected void onDraw(Canvas canvas) {
    	// TODO Auto-generated method stub
    	super.onDraw(canvas);
    	Log.i("main", "开始画画");
        mPaint.setColor(Color.YELLOW);  
        //在画布的x、y、right、bottom范围内画,画笔为mPaint
        canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);  
        
        //设置画笔颜色
        mPaint.setColor(mTitleTextColor);  
//        canvas.drawText(text, x, y, paint),第一个参数是我们需要绘制的文本,第三个参数是我们的画笔,
//        这两个不用多说,主要是第二和第三个参数的含义,这两个参数在不同的情况下的值还是不一样的,x默认是这个字符串的左边在屏幕的位置,
//        如果设置了paint.setTextAlign(Paint.Align.CENTER);那就是字符的中心,y是指定这个字符baseline在屏幕上的位置
        canvas.drawText(mTitleText, 0, mBound2.height(), mPaint); 
        canvas.drawText("我是第二行吗", getWidth() / 2 - mBound2.width() / 2, getHeight() / 2 + mBound2.height() / 2, mPaint2);  
    	
    }
    
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
    	switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			Log.i("main", "MyView---dispatchTouchEvent---DOWN");
			System.out.println("MyView---dispatchTouchEvent---DOWN");
			break;
		case MotionEvent.ACTION_MOVE:
			Log.i("main", "MyView---dispatchTouchEvent---MOVE");
			break;
		case MotionEvent.ACTION_UP:
			Log.i("main", "MyView---dispatchTouchEvent---UP");
			break;
		default:
			break;
		}
    	return super.dispatchTouchEvent(event);
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
    	switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			Log.i("main", "MyView---onTouchEvent---DOWN");
			break;
		case MotionEvent.ACTION_MOVE:
			Log.i("main", "MyView---onTouchEvent---MOVE");
			break;
		case MotionEvent.ACTION_UP:
			Log.i("main", "MyView---onTouchEvent---UP");
			break;
		default:
			break;
		}
    	return super.onTouchEvent(event);
    }
}
我基本就加了一点点击的输出而已,得到的输出为:



可以发现,在接受到点击事件之后,首先是Activity的dispatchTouchEvent接收事件后分发给MyView,MyView接收到之后又返回给MainActivity处理,但是对于UP事件,MyView却始终没有接收到

然后,我再MyView的onTouchEvent事件中固定写了返回true之后



可见,当MyView的onTouchEvent中返回了true,表示了自己消化点击事件后,则MyView就可以接收到接下来的MOVE以及UP事件了,但是,MainActivity就接受不到了

说明:

1、在onTouchEvent事件中,上下层中,都可以接受到DOWN事件,但是如果某一层消化了点击事件,另一层就接收不到接下来的点击事件了,可以推断中间有一个对于下层是否接受事件有一个中间存储的变量

2、在dispatchTouchEvent事件中,上层总是可以接收到所有的点击事件,而下层能否接受到所有的点击事件取决于下层是否消化了onTouchEvent事件(返回true)

这样的话,在我们代码中涉及到上下层都需要处理的点击事件时就比较明了了

那么到这里是对onTouchEvent的接收影响比较了解,还有dispatchTouchEvent事件,从它的注释来理解的话,是掌控事件的对子控件的分发,还是比较好理解的,这个也可以实验

1、MainActivity dispatchTouchEvent retrun true

MyView
dispatchTouchEvent return false;



可以看到当MainActivity 的dispatchTouchEvent 返回true之后其后的所有点击事件都被截断了

2、MainActivity dispatchTouchEvent retrun false

MyView dispatchTouchEvent return
true;



可以看到,MyView的dispatchTouchEvent
返回true之后对于MainActivity的dispatchTouchEvent 是没有影响的

这一点是比较好理解的,但是还可以发现,这样返回之后,MainActivity的onTouchEvent也接受不到点击事件了,那么在这里我们可以发现一点

dispatchTouchEvent不仅会影响自身以及下层View的onTouchEvent事件,还会影响上层的onTouchEvent事件



在这里也可以推论出一点,onTouchEvent的调用是从下层往上层的调用,dispatchTouchEvent可以打断这个过程

那么到这里就是View在Activity中的处理,并且对于dispatchTouchEvent 以及onTouchEvent的处理方式可以说是比较了解了,那么还有的就是关于ViewGroup的点击事件的处理了

在ViewGroup的点击实验中,我布局文件如下修改了下

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:myview="http://schemas.android.com/apk/res/com.example.testview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
    
    <com.example.testview.MyViewGroup 
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
    <com.example.testview.MyView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        myview:titleText="我是文字内容"
        myview:titleTextColor="#ff0000"
        myview:titleTextSize="40sp"
        />
	</com.example.testview.MyViewGroup>
</LinearLayout>


就是在原本的MyView外面包了一层,然后在没有指定那一层确定返回true还是false的情况下点击后产生如下效果



那么,ViewGroup的调用顺序可以看到,onInterceptTouchEvent的调用是在dispatchTouchEvent之后的,并且onTouchEvent的执行顺序也是如我之前得出的结论,从最底层开始往上面调用

而我们这次的目的主要是测试onInterceptTouchEvent的用途,按照注释来看的话,主要是截断其内部子View的点击事件的接收,那么就来测试一下

我修改了onInterceptTouchEvent的返回值固定为true之后



可以看到,MyView所有点击事件都没有接收到,即使是dispatchTouchEvent也没有调用到,然而MainActivity的事件是都能接收到的

那么这一步可以看出,完全和注释一模一样,那么结合之前的测试,可以看出onInterceptTouchEvent的确有着其独特的作用

一、onInterceptTouchEvent与dispatchTouchEvent
比较


1、onInterceptTouchEvent与dispatchTouchEvent
的区别是它不会影响自身以及上层的onTouchEvent事件的执行

2、dispatchTouchEvent
比onInterceptTouchEvent多屏蔽了一层onTouchEvent的执行

二、onInterceptTouchEvent与onTouchEvent比较

1、onInterceptTouchEvent不会影响上层以及自身onTouchEvent的调用执行

2、onInterceptTouchEvent只会向下影响,而影响不到上层

这样,onInterceptTouchEvent的特性就比较明显了,到了这里,对于android中触摸事件的传递就有了一个非常清晰的流程了解,这样在处理点击等事件的时候,相信就能很好地处理了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: