Android事件分发详解(一)——View的事件分发

简介: PS: 该系列博客已更新,详情请参见: http://blog.csdn.net/lfdfhl/article/details/50707742 http://blog.

PS:

该系列博客已更新,详情请参见:


http://blog.csdn.net/lfdfhl/article/details/50707742


http://blog.csdn.net/lfdfhl/article/details/50707731


http://blog.csdn.net/lfdfhl/article/details/50707724


http://blog.csdn.net/lfdfhl/article/details/50707721


http://blog.csdn.net/lfdfhl/article/details/50707714


http://blog.csdn.net/lfdfhl/article/details/50707713


http://blog.csdn.net/lfdfhl/article/details/50707700


MainActivity如下:

package cc.cv;

import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.Button;
import android.widget.ImageView;
import android.app.Activity;
/**
 * Demo描述:
 * View的事件分发
 * 
 * 在View的事件分发过程中主要涉及到dispatchTouchEvent()和onTouch()以及onTouchEvent()
 * 
 * dispatchTouchEvent()返回true或者false表示是否继续事件分发
 * onTouch()返回 true或者false表示是事件是否被消耗
 * onTouchEvent()中主要处理点击Click事件
 * 
 * 事件的分发从dispatchTouchEvent()开始.
 * 方法dispatchTouchEvent()返回值为true时表示继续事件分发;返回值为false时表示终止事件分发.
 * 源码如下:
 * 
 * public boolean dispatchTouchEvent(MotionEvent event) {
 *   if (mOnTouchListener!= null&&(mViewFlags & ENABLED_MASK)==ENABLED&&mOnTouchListener.onTouch(this,event)){
 *        return true;
 *     }
 *   return onTouchEvent(event);
 * }
 * 
 * 该方法的返回值有两种情况:
 * 
 * 1 满足if条件时,返回true.注意该if条件的三个判断.
 *     1.1 mOnTouchListener不等于null
 *     1.2 当前控件是enable的
 *     1.3 调用mOnTouchListener.onTouch(this,event)返回的结果
 *     前两个条件没啥可多说的,主要看看第三个条件:
 *     在 onTouch(View v, MotionEvent event)中会处理一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP.
 *     该onTouch()方法返回true表示事件已经消耗,返回false表示事件未消耗.
 *     比如在处理ACTION_DOWN时返回true才会继续分发ACTION_MOVE事件
 *     比如在处理ACTION_MOVE时返回true才会继续分发ACTION_UP事件
 *     比如在处理ACTION_DOWN时返回false,那么后续的ACTION_MOVE,ACTION_UP就不会再继续分发.
 *     我们在代码中也就无法捕捉到ACTION_MOVE,ACTION_UP这两个Action了.
 *    
 * 2 假如该if条件不满足,那么就继续执行,返回 onTouchEvent(event)的执行结果.
 * 
 * 
 * 从该dispatchTouchEvent()的源码也可以看出
 * onTouch(this,event)和 onTouchEvent(event)的区别和关系:
 * 1 先调用onTouch()后调用onTouchEvent()
 * 2 在onTouch()方法中处理了Touch事件,即处理一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP事件
 *   返回false时表示事件(每个单独的ACTION_DOWN,ACTION_MOVE,ACTION_UP都叫一个事件,并不是说这三者联系在一起才是一个事件)
 *   未被消耗才会调用onTouchEvent(event).
 * 3 在onTouchEvent(event)中的ACTION_UP事件里会调用performClick()处理OnClick点击事件!!!!
 * 4 所以可知:
 *   4.1 Touch事件先于Click事件发生和处理,且注意onTouch()方法默认返回为false.
 *   4.2 只有在onTouch()返回false时(即事件未被消耗)才会调用onTouchEvent()
 *   4.3 在onTouchEvent()中的ACTION_UP事件会调用performClick()处理OnClick点击事件.
 * 5 参见下面的onTouchEvent()源码,请注意第三个if判断,这个if判断很重要!!!!!!!
 *   5.1 在该if条件中判断该控件是否是可点击的(CLICKABLE)或者是否是可以长按的(LONG_CLICKABLE).
 *   5.2 如果满足CLICKABLE和LONG_CLICKABLE中任一条件则始终会返回true给onTouchEvent()方法
 *   5.3 如果CLICKABLE和LONG_CLICKABLE这两个条件都不满足则返回false给onTouchEvent()方法
 *   
 *   请注意:
 *   Button默认情况下就是CLICKABLE和LONG_CLICKABLE的,但是ImageView在
 *   默认情况下CLICKABLE和LONG_CLICKABL均为不可用的.
 *   
 *   所以在用Button和ImageView分别实验OnTouchListener和OnClickListener是有区别的.
 *   再次提醒注意:onTouch()方法默认返回为false.
 *   1 Button做实验分析dispatchTouchEvent().
 *     mOnTouchListener.onTouch()返回false(默认值),所以dispatchTouchEvent()
 *     如上源码中的if不满足,于是继续调用onTouchEvent(event)时由于Button满足CLICKABLE和LONG_CLICKABLE
 *     所以最后返回给dispatchTouchEvent()的是true,即继续事件的分发.
 *     所以可以捕获到一系列的:ACTION_DOWN,ACTION_MOVE,ACTION_UP.
 *     这里就解释了为什么在Button中虽然onTouch()返回false(默认值)但是事件分发还在继续!!!!!!!!!!!!!
 *     
 *   2 用ImageView做实验分析dispatchTouchEvent().
 *     mOnTouchListener.onTouch()返回false(默认值),所以dispatchTouchEvent()
 *     如上源码中的if不满足,在调用onTouchEvent(event)时由于ImageView不满足CLICKABLE和LONG_CLICKABLE
 *     中任何一个所以最后返回给dispatchTouchEvent()的是false,即终止事件的分发.所以对于ImageView只有
 *     ACTION_DOWN没有ACTION_MOVE和ACTION_UP
 *     这里就解释了为什么在ImageView中在onTouch()返回里false(默认值)就终止了事件分发!!!!!!!!!!!!!
 *     
 *   如何才可以使ImageView像Button那样"正规的"事件分发,有如下两个方法:
 *   1 为ImageView设置setOnTouchListener,且在其onTouch()方法中返回true而不是默认的false.
 *   2 为ImageView设置android:clickable="true"或者ImageView设置OnClickListener.
 *      就是说让ImageView变得可点击.
 *   3 详情可见以下代码中的例子,关于这一点在下面的例子中有体现.
 *   
 *   
 * 
 * 参考资料:
 * http://blog.csdn.net/guolin_blog/article/details/9097463
 * Thank you very much
 * 
 * 代码随笔:
 * 以前也看过事件分发,也自己总结了;可理解得不够.
 * 最近打算结合以前的东西和郭大婶的博客重新整理事件分发.
 * 由于理解上的差异郭大婶的这边博客,我看得比较吃力;后来也和他沟通了一下.
 * 所以这里是自己的理解,是正确的;只是在表述上和他的博客有所不同.
 */


public class MainActivity extends Activity {
    private Button mButton;
    private ImageView mImageView;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		initButton();
		initImageView();
	}
	
	
	private void initButton(){
		mButton=(Button) findViewById(R.id.button);
		mButton.setOnTouchListener(new OnTouchListener() {
			@Override
			public boolean onTouch(View v, MotionEvent event) {
				switch (event.getAction()) {
				case MotionEvent.ACTION_DOWN:
					System.out.println("Button ACTION_DOWN");
					break;
				case MotionEvent.ACTION_MOVE:
					System.out.println("Button ACTION_MOVE");
					break;
				case MotionEvent.ACTION_UP:
					System.out.println("Button ACTION_UP");
					break;
				default:
					break;
				}
				return false;
			}
		});
		
		
		mButton.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				System.out.println("Button Clicked");
			}
		});
		
	}
	
	private void initImageView(){
		mImageView=(ImageView) findViewById(R.id.imageView);
		mImageView.setOnTouchListener(new OnTouchListener() {
			@Override
			public boolean onTouch(View v, MotionEvent event) {
				switch (event.getAction()) {
				case MotionEvent.ACTION_DOWN:
					System.out.println("ImageView ACTION_DOWN");
					break;
				case MotionEvent.ACTION_MOVE:
					System.out.println("ImageView ACTION_MOVE");
					break;
				case MotionEvent.ACTION_UP:
					System.out.println("ImageView ACTION_UP");
					break;
				default:
					break;
				}
				return false;
			}
		});
		
		
		
	    //因为ImageView默认是不可点击的,所以如果屏蔽掉以下的代码,则只有
		//ImageView的ACTION_DOWN没有ACTION_MOVE和ACTION_UP
		mImageView.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				System.out.println("ImageView Clicked");
			}
		});
		
	}

}




/*
 * 此处为onTouchEvent源码:
public boolean onTouchEvent(MotionEvent event) {
    final int viewFlags = mViewFlags;
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        // A disabled view that is clickable still consumes the touch
        // events, it just doesn't respond to them.
        return (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
    }
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
                    // take focus if we don't have it already and we should in
                    // touch mode.
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }
                    if (!mHasPerformedLongPress) {
                        // This is a tap, so remove the longpress check
                        removeLongPressCallback();
                        // Only perform take click actions if we were in the pressed state
                        if (!focusTaken) {
                            // Use a Runnable and post this rather than calling
                            // performClick directly. This lets other visual state
                            // of the view update before click actions start.
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                        }
                    }
                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }
                    if (prepressed) {
                        mPrivateFlags |= PRESSED;
                        refreshDrawableState();
                        postDelayed(mUnsetPressedState,
                                ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        // If the post failed, unpress right now
                        mUnsetPressedState.run();
                    }
                    removeTapCallback();
                }
                break;
            case MotionEvent.ACTION_DOWN:
                if (mPendingCheckForTap == null) {
                    mPendingCheckForTap = new CheckForTap();
                }
                mPrivateFlags |= PREPRESSED;
                mHasPerformedLongPress = false;
                postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                break;
            case MotionEvent.ACTION_CANCEL:
                mPrivateFlags &= ~PRESSED;
                refreshDrawableState();
                removeTapCallback();
                break;
            case MotionEvent.ACTION_MOVE:
                final int x = (int) event.getX();
                final int y = (int) event.getY();
                // Be lenient about moving outside of buttons
                int slop = mTouchSlop;
                if ((x < 0 - slop) || (x >= getWidth() + slop) ||
                        (y < 0 - slop) || (y >= getHeight() + slop)) {
                    // Outside button
                    removeTapCallback();
                    if ((mPrivateFlags & PRESSED) != 0) {
                        // Remove any future long press/tap checks
                        removeLongPressCallback();
                        // Need to switch from pressed to not pressed
                        mPrivateFlags &= ~PRESSED;
                        refreshDrawableState();
                    }
                }
                break;
        }
        return true;
    }
    return false;
}
 */




ButtonSubClass如下:

package cc.cv;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.widget.Button;

public class ButtonSubClass extends Button {

	public ButtonSubClass(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
	}

	public ButtonSubClass(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
	}

	public ButtonSubClass(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		// TODO Auto-generated constructor stub
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		return super.onTouchEvent(event);
	}
	
	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		return super.dispatchTouchEvent(event);
	}
	
	

}

main.xml如下:
<RelativeLayout 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" >

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="100dip"
        android:text="Button" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="200dip"
        android:src="@drawable/ic_launcher" />

</RelativeLayout>



PS:

以前也整理过事件分发,不过当时太肤浅。

最近重新整理了该部分;月底发出来,算是对今年的一个告别。

其实这部分的核心就在于ViewGroup的dispatchTouchEvent()源码部分。

这次整理,对于该部分还是没有完全看懂;期待以后有机会继续。

学习是一个过程,这就是体现。

相关文章
|
4月前
|
Android开发 UED 计算机视觉
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
本文介绍了一款受游戏“金铲铲之战”启发的Android自定义View——线条等待动画的实现过程。通过将布局分为10份,利用`onSizeChanged`测量最小长度,并借助画笔绘制动态线条,实现渐变伸缩效果。动画逻辑通过四个变量控制线条的增长与回退,最终形成流畅的等待动画。代码中详细展示了画笔初始化、线条绘制及动画更新的核心步骤,并提供完整源码供参考。此动画适用于加载场景,提升用户体验。
381 5
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
|
4月前
|
Android开发
Android自定义view之利用PathEffect实现动态效果
本文介绍如何在Android自定义View中利用`PathEffect`实现动态效果。通过改变偏移量,结合`PathEffect`的子类(如`CornerPathEffect`、`DashPathEffect`、`PathDashPathEffect`等)实现路径绘制的动态变化。文章详细解析了各子类的功能与参数,并通过案例代码展示了如何使用`ComposePathEffect`组合效果,以及通过修改偏移量实现动画。最终效果为一个菱形图案沿路径运动,源码附于文末供参考。
|
4月前
|
Android开发 开发者
Android自定义view之利用drawArc方法实现动态效果
本文介绍了如何通过Android自定义View实现动态效果,重点使用`drawArc`方法完成圆弧动画。首先通过`onSizeChanged`进行测量,初始化画笔属性,设置圆弧相关参数。核心思路是不断改变圆弧扫过角度`sweepAngle`,并调用`invalidate()`刷新View以实现动态旋转效果。最后附上完整代码与效果图,帮助开发者快速理解并实践这一动画实现方式。
120 0
|
4月前
|
Android开发 数据安全/隐私保护 开发者
Android自定义view之模仿登录界面文本输入框(华为云APP)
本文介绍了一款自定义输入框的实现,包含静态效果、hint值浮动动画及功能扩展。通过组合多个控件完成界面布局,使用TranslateAnimation与AlphaAnimation实现hint文字上下浮动效果,支持密码加密解密显示、去除键盘回车空格输入、光标定位等功能。代码基于Android平台,提供完整源码与attrs配置,方便复用与定制。希望对开发者有所帮助。
|
4月前
|
XML Java Android开发
Android自定义view之网易云推荐歌单界面
本文详细介绍了如何通过自定义View实现网易云音乐推荐歌单界面的效果。首先,作者自定义了一个圆角图片控件`MellowImageView`,用于绘制圆角矩形图片。接着,通过将布局放入`HorizontalScrollView`中,实现了左右滑动功能,并使用`ViewFlipper`添加图片切换动画效果。文章提供了完整的代码示例,包括XML布局、动画文件和Java代码,最终展示了实现效果。此教程适合想了解自定义View和动画效果的开发者。
205 65
Android自定义view之网易云推荐歌单界面
|
4月前
|
XML 前端开发 Android开发
一篇文章带你走近Android自定义view
这是一篇关于Android自定义View的全面教程,涵盖从基础到进阶的知识点。文章首先讲解了自定义View的必要性及简单实现(如通过三个构造函数解决焦点问题),接着深入探讨Canvas绘图、自定义属性设置、动画实现等内容。还提供了具体案例,如跑马灯、折线图、太极图等。此外,文章详细解析了View绘制流程(measure、layout、draw)和事件分发机制。最后延伸至SurfaceView、GLSurfaceView、SVG动画等高级主题,并附带GitHub案例供实践。适合希望深入理解Android自定义View的开发者学习参考。
488 84
|
4月前
|
前端开发 Android开发 UED
讲讲Android为自定义view提供的SurfaceView
本文详细介绍了Android中自定义View时使用SurfaceView的必要性和实现方式。首先分析了在复杂绘制逻辑和高频界面更新场景下,传统View可能引发卡顿的问题,进而引出SurfaceView作为解决方案。文章通过Android官方Demo展示了SurfaceView的基本用法,包括实现`SurfaceHolder.Callback2`接口、与Activity生命周期绑定、子线程中使用`lockCanvas()`和`unlockCanvasAndPost()`方法完成绘图操作。
|
4月前
|
Android开发 开发者
Android自定义view之围棋动画(化繁为简)
本文介绍了Android自定义View的动画实现,通过两个案例拓展动态效果。第一个案例基于`drawArc`方法实现单次动画,借助布尔值控制动画流程。第二个案例以围棋动画为例,从简单的小球直线运动到双向变速运动,最终实现循环动画效果。代码结构清晰,逻辑简明,展示了如何化繁为简实现复杂动画,帮助读者拓展动态效果设计思路。文末提供完整源码,适合初学者和进阶开发者学习参考。
Android自定义view之围棋动画(化繁为简)
|
4月前
|
Java Android开发 开发者
Android自定义view之围棋动画
本文详细介绍了在Android中自定义View实现围棋动画的过程。从测量宽高、绘制棋盘背景,到创建固定棋子及动态棋子,最后通过属性动画实现棋子的移动效果。文章还讲解了如何通过自定义属性调整棋子和棋盘的颜色及动画时长,并优化视觉效果,如添加渐变色让白子更明显。最终效果既可作为围棋动画展示,也可用作加载等待动画。代码完整,适合进阶开发者学习参考。
|
4月前
|
传感器 Android开发 开发者
Android自定义view之3D正方体
本文介绍了如何通过手势滑动操作实现3D正方体的旋转效果,基于Android自定义View中的GLSurfaceView。相较于使用传感器控制,本文改用事件分发机制(onTouchEvent)处理用户手势输入,调整3D正方体的角度。代码中详细展示了TouchSurfaceView的实现,包括触控逻辑、OpenGL ES绘制3D正方体的核心过程,以及生命周期管理。适合对Android 3D图形开发感兴趣的开发者学习参考。