1. 创建一个布局文件,布局如下,只有一个TextView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView 
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/hello"
    android:layout_gravity="center"
    android:gravity="center"
    android:textAppearance="?android:attr/textAppearanceSmall"
    android:textSize="30sp"
    android:textStyle="bold"
    />
</LinearLayout>

2. 创建HomeActivity,显示这个布局,同时给TextView配置touch事件

public class HomeActivity extends Activity {
    private TextView tv;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

3. 在View类的onTouchEvent方法上打上断点

准备工作结束,下面开始debug调试

触摸界面上的TextView,程序运行到断点处停下,看一下执行流程

从这张图上可以看出:

1. HomeActivity的dispatchTouchEvent方法可以捕获所有的在屏幕上触发的事件,从他开始事件的分配

2. 事件的分派是从activity上的最顶级view开始进行分配,一层层往下传递给触发事件的view,可以看一下界面的hiberarchy views图

3. dispatchTouchEvent方法的作用是对事件进行分发,将事件分发给他的子view

3.所有的view控件都有dispatchTouchEvent方法,ViewGroup类重写了View类中的dispatchTouchEvent方法。

看一看具体的方法:

Activity:

/**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.

事件首先分发给activity,然后由activity分发给window,activity是当前window的一个逻辑管理类,他不是类似于XXXLayout的布局容器。
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }

onUserInteraction();这个方法在Activity中是空函数体,当按键,触摸,或者是轨迹球事件被分配到Activity的时候,这个方法就会触发。如果你想知道activity运行时,

用户与当前activity进行了那些交互,可以实现这个方法。这个方法最主要的还是帮助activity维护状态栏通知,这个方法仅仅在action down的时候才会触发,move,up时都不会触发**********************************************************

//将事件传递给该activity管理的视图层,该视图层的顶层容器是Window,如果该window中没有一个view处理这个事件,就会返回false,就会执行onTouchEvent方法了,这就实现了事件由Activity进行分发,分发给视图层进行事件处理,如果事件没有被吃掉,再由Activity进行处理。

if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
 }

每个Activity的显示视图都是放在Window中的,Window是个容器,负责渲染视图。

ev事件最终是被ViewGroup类中的dispatchTouchEvent(MotionEvent ev)方法进行了处理,我认为他是进行了事件在view hiberarchy中的传递,如果有view处理了这个事件,并且返回了ture,那么这个事件就算consume了,不会再交给activity中的方法进行处理。如果视图中没有一个view处理这个事件,或者是处理了,但是返回了false,那么还会去调用activity中的处理各种事件的方法onTouchEvent

**********************************************************
        return onTouchEvent(ev);

在Activity中,onTouchEvent(ev);方法是空函数体,也就是说这个方法是对外提供的,看看这个方法的说明,Called when a touch screen event was not handled by any of the views under it.  This is most useful to process touch events that happen  outside of your window bounds, where there is no view to receive it.

如果视图中没有一个view控件处理这个事件,那么就由onTouchEvent处理,这个方法在activity中重写即可。

}

Activity中完成了事件的分发,交给了视图层,从顶层开始一层层传递给底层的view

从视图层中可以看到,最顶层的是PhoneWindow,activity首先将事件传递给PhoneWindow,PhoneWindow不是View,他是Window的实现类,他将事件传递给DecorView,DecorView是继承了FrameLayout的子类,FrameLayout没有重写dispatchTouchEvent()方法,他继承自ViewGroup的dispatchTouchEvent方法。他首先会调用自己的onInterceptTouchEvent方法自己处理该事件(这个方法view是没有的,只有ViewGroup类才有,因为Viewgroup类统一监控他的子所有view),如果自己没有处理或者是返回了false,就会遍历自己的所有的子view,找到合适的view进行事件的处理,首先处理down事件,找到合适的view,记录,这样再处理随后的up,cancle等事件,就不用重新去查找了。

/**
       我们知道无论是ACTION_DOWN,还是ACTION_UP,或者是ACTION_CANCEL,这几种事件肯定是顺序的,
       一个view触发了ACTION_DOWN,那么ACTION_UP,ACTION_CANCEL事件也会随之而来,ACTION_DOWN事件肯定是先触发,
       所以在处理ACTION_DOWN的时候,我们就能知道是谁触发了这个事件,记住它,等处理以后的ACTION_UP,ACTION_CANCEL事件
       就不用在去重新获得target了
      
       */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        final float xf = ev.getX();
        final float yf = ev.getY();
        final float scrolledXFloat = xf + mScrollX;
        final float scrolledYFloat = yf + mScrollY;
        final Rect frame = mTempRect;//得到一个矩形,包含自个坐标,上下左右

boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//是否允许处理这个事件

if (action == MotionEvent.ACTION_DOWN) {
            if (mMotionTarget != null) {
                // this is weird(怪异的), we got a pen down, but we thought it was
                // already down!
                // XXX: We should probably send an ACTION_UP to the current
                // target.
                mMotionTarget = null;
            }
            // If we're disallowing intercept or if we're allowing and we didn't
            // intercept
            if (disallowIntercept || !onInterceptTouchEvent(ev)/**自己先进行处理,返回true,就不会分发该事件了*/) {
                // reset this event's action (just to protect ourselves)
                ev.setAction(MotionEvent.ACTION_DOWN);
                // We know we want to dispatch the event down, find a child
                // who can handle it, start with the front-most child.
                final int scrolledXInt = (int) scrolledXFloat;
                final int scrolledYInt = (int) scrolledYFloat;
                final View[] children = mChildren;
                final int count = mChildrenCount;
                //遍历自己的子view
                for (int i = count - 1; i >= 0; i--) {
                    final View child = children[i];
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                            || child.getAnimation() != null) {
                        child.getHitRect(frame);
                        if (frame.contains(scrolledXInt, scrolledYInt)) {
                            // offset the event to the view's coordinate system
                            final float xc = scrolledXFloat - child.mLeft;
                            final float yc = scrolledYFloat - child.mTop;
                            ev.setLocation(xc, yc);
                            if (child.dispatchTouchEvent(ev))  {
                                // Event handled, we have a target now.
                                mMotionTarget = child;//这个child View 就是触发事件的那个view,就交交给他处理,不用再分发了,结束
                                return true;
                            }
                            // The event didn't get handled, try the next view.
                            // Don't reset the event's location, it's not
                            // necessary here.
                        }
                    }
                }
            }
        }
        上面处理了ACTION_DOWN,下面的代码处理其他的action
      // ***************************************************************************
      //如果是up事件,或者是cancle事件,isUpOrCancel为true
        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
                (action == MotionEvent.ACTION_CANCEL);

if (isUpOrCancel) {
            // Note, we've already copied the previous state to our local
            // variable, so this takes effect on the next event
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }
       
        // The event wasn't an ACTION_DOWN, dispatch it to our target if
        // we have one.
        final View target = mMotionTarget;
        if (target == null) {
            // We don't have a target, this means we're handling the
            // event as a regular view.(不是ViewGroup,而只是veiw)
            ev.setLocation(xf, yf);
            return super.dispatchTouchEvent(ev);
        }

// if have a target, see if we're allowed to and want to intercept its
        // events
        if (!disallowIntercept && onInterceptTouchEvent(ev)) {
            final float xc = scrolledXFloat - (float) target.mLeft;
            final float yc = scrolledYFloat - (float) target.mTop;
            ev.setAction(MotionEvent.ACTION_CANCEL);
            ev.setLocation(xc, yc);
            if (!target.dispatchTouchEvent(ev)) {
                // target didn't handle ACTION_CANCEL. not much we can do
                // but they should have.
            }
            // clear the target
            mMotionTarget = null;
            // Don't dispatch this event to our own view, because we already
            // saw it when intercepting; we just want to give the following
            // event to the normal onTouchEvent().
            return true;
        }

if (isUpOrCancel) {
            mMotionTarget = null;
        }

// finally offset the event to the target's coordinate system and
        // dispatch the event.
        final float xc = scrolledXFloat - (float) target.mLeft;
        final float yc = scrolledYFloat - (float) target.mTop;
        ev.setLocation(xc, yc);

return target.dispatchTouchEvent(ev);
    }

框架不断分发这个事件,最后找到TextView是这个事件的触发者,TextView得到这个事件后,又是怎么处理的呢,首先是TextView的dispatchTouchEvent方法(继承自View)捕获这个事件,然后调用自己的onTouchEvent方法处理这个事件

public boolean dispatchTouchEvent(MotionEvent event) {

/**检查是否有touch事件的监听器,如果有,就调用监听器处理,如果没有,就调用缺省的处理方法
       if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
               mOnTouchListener.onTouch(this, event)) {
           return true;
       }
       return onTouchEvent(event);
   }

TextView重写了View类的onTouchEvent方法,这个方法没有看懂

@Override
    public boolean onTouchEvent(MotionEvent event) {
        final int action = event.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            // Reset this state; it will be re-set if super.onTouchEvent
            // causes focus to move to the view.
            mTouchFocusSelected = false;
        }
       
        final boolean superResult = super.onTouchEvent(event);

/*
         * Don't handle the release after a long press, because it will
         * move the selection away from whatever the menu action was
         * trying to affect.
         */
        if (mEatTouchRelease && action == MotionEvent.ACTION_UP) {
            mEatTouchRelease = false;
            return superResult;
        }

if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) {
           
            if (action == MotionEvent.ACTION_DOWN) {
                mScrolled = false;
            }
           
            boolean handled = false;
           
            int oldSelStart = Selection.getSelectionStart(mText);
            int oldSelEnd = Selection.getSelectionEnd(mText);
           
            if (mMovement != null) {
                handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
            }

if (mText instanceof Editable && onCheckIsTextEditor()) {
                if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) {
                    InputMethodManager imm = (InputMethodManager)
                            getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                   
                    // This is going to be gross...  if tapping on the text view
                    // causes the IME to be displayed, we don't want the selection
                    // to change.  But the selection has already changed, and
                    // we won't know right away whether the IME is getting
                    // displayed, so...
                   
                    int newSelStart = Selection.getSelectionStart(mText);
                    int newSelEnd = Selection.getSelectionEnd(mText);
                    CommitSelectionReceiver csr = null;
                    if (newSelStart != oldSelStart || newSelEnd != oldSelEnd) {
                        csr = new CommitSelectionReceiver();
                        csr.mNewStart = newSelStart;
                        csr.mNewEnd = newSelEnd;
                    }
                   
                    if (imm.showSoftInput(this, 0, csr) && csr != null) {
                        // The IME might get shown -- revert to the old
                        // selection, and change to the new when we finally
                        // find out of it is okay.
                        Selection.setSelection((Spannable)mText, oldSelStart, oldSelEnd);
                        handled = true;
                    }
                }
            }

if (handled) {
                return true;
            }
        }

return superResult;
    }

到此结束

有个问题不明白:android框架是如何区分touch和click事件的呢

实现,给界面上的TextView出侧两个监听器,一个是View.OnClickListener,另一个是View.OnTouchListener,分别打上两个断点

运行程序,首先程序运行到onTouch方法的断点处,此时是ACTION_DOWN产生的事件,F8,又停在了这个断点,这时action为ACTION_MOVE,F8,又停在了这个断点处,此时actrio为ACTION_UP,到现在,touch事件正式结束,F8,停在了onClick方法的断点处,看图

从上图可以看出,红线标注的三行与touch事件的分发不同,也就是说在view类的onTouchEvent方法里,认为该事件是click事件,调用了click事件的响应处理。

程序一共四次遇到断点,断了四次,分别记录View的onTouchEvent方法的event的参数的id值

"event"     (id=830060441712)    down
   
"event"     (id=830060509040)    move
   
"event"     (id=830060441712)    up

"event"     (id=830060441712)    up

也就是说除了move事件外,其他的三个事件都是使用的同一个MotionEvent。框架在处理完touch之后,立刻处理的click,具体原理还有待研究。

一些相关的代码:

Activity中mWindow是如何初始化的?
PolicyManager:
public static Window makeNewWindow(Context context) {
        return sPolicy.makeNewWindow(context);
}
IPolicy:
public interface IPolicy {
    public Window makeNewWindow(Context context);

public LayoutInflater makeNewLayoutInflater(Context context);

public WindowManagerPolicy makeNewWindowManager();
}
sPolicy是怎么来的?
PolicyManager:
Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
sPolicy = (IPolicy)policyClass.newInstance();
private static final String POLICY_IMPL_CLASS_NAME =
        "com.android.internal.policy.impl.Policy"
  // Simple implementation of the policy interface that spawns the right
// set of objects

public class Policy implements IPolicy {
    private static final String TAG = "PhonePolicy";

private static final String[] preload_classes = {
        "com.android.internal.policy.impl.PhoneLayoutInflater",
        "com.android.internal.policy.impl.PhoneWindow",
        "com.android.internal.policy.impl.PhoneWindow$1",
        "com.android.internal.policy.impl.PhoneWindow$ContextMenuCallback",
        "com.android.internal.policy.impl.PhoneWindow$DecorView",
        "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState",
        "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState",
    };

static {
        // For performance reasons, preload some policy specific classes when
        // the policy gets loaded.
        for (String s : preload_classes) {
            try {
                Class.forName(s);
            } catch (ClassNotFoundException ex) {
                Log.e(TAG, "Could not preload class for phone policy: " + s);
            }
        }
    }

public PhoneWindow makeNewWindow(Context context) {
        return new PhoneWindow(context);
    }

public PhoneLayoutInflater makeNewLayoutInflater(Context context) {
        return new PhoneLayoutInflater(context);
    }

public PhoneWindowManager makeNewWindowManager() {
        return new PhoneWindowManager();
    }
}

PhoneWindow:
public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
   
DecorView extends FrameLayout:
     public boolean superDispatchTouchEvent(MotionEvent event) {
            return super.dispatchTouchEvent(event);

}

转载于:https://www.cnblogs.com/transmuse/archive/2010/12/03/1895772.html

Android UI 事件研究相关推荐

  1. Android UI事件传递就是这么个事儿

    正文 ###聊聊UI事件传递 什么是UI事件? 触摸屏幕中UI控件的那一刻即为事件发生 MotionEvent对象包含了所有的触摸事件,如触摸的位置.多指触摸等 MotionEvent描述了当前的操作 ...

  2. AndroidのUI设计研究(一)——自定义ProgressBar

    最近迷上进度条,使用进度条可以增强用户体验,让用户心里有个底,再无奈的等待中体会loading的乐趣. 记得以前优乐美的官网,进入主页加载资源,显示给用户看的就是,炫彩背景下,一个杯子里的奶茶随着加载 ...

  3. Android学习笔记--处理UI事件

    Handling UI Events 在Android里, 有不只一种方式可以截获用户与你的应用程序交互的事件. 在你的界面上处理事件时,你需要捕获用户与某个View实例交互时所产生的事件.View类 ...

  4. Android UI滑动加载源码

    2019独角兽企业重金招聘Python工程师标准>>> android UI 往右滑动,滑动到最后一页就自动加载数据并显示 如图: Java代码 package cn.anycall ...

  5. Wiew 像写 Android UI 一样写小游戏布局

    Wiew 项目地址: https://github.com/onlynight/Wiew 简易微信小游戏view系统以及touch系统.你可以想写Android UI一样写界面布局,处理点击事件. 预 ...

  6. Android的事件分发

    1. Touch事件和绘制事件的异同之处 Touch事件和绘制事件很类似,都是由ViewRoot派发下来的,但是不同之处在绘制事件是由应用中的某个View发起请求,一层一层上传到ViewRoot,再有 ...

  7. Android的事件分发实例分析

    如果对Android的事件分发不熟悉,可以看Android的事件分发 瀑布流 实现的功能:滑动左边的RecyclerView区域,左边的RecyclerView滚动:滑动中间的RecyclerView ...

  8. android 组件 线程,Android UI线程和非UI线程

    UI线程及Android的单线程模型原则 当应用启动,系统会创建一个主线程(main thread). 这个主线程负责向UI组件分发事件(包括绘制事件),也是在这个主线程里,你的应用和Android的 ...

  9. Android在UI线程访问数据库,Android UI Operation in Thread

    Painless Threading (无痛苦使用线程) 本文讨论Android应用程序的线程模型以及应用程序应该如何创建工作线程而不是使用主线程来处理长期运行的操作, 以得到好的UI性能. 本文还解 ...

最新文章

  1. python自带图形模块_检查单击是否在图形对象内[Python图形模块]
  2. Loj #2036. 「SHOI2015」自动刷题机
  3. iOS - UISearchController
  4. The server time zone value ‘XXXXXX’ is unrecognized or represents more than one time zone
  5. 如何把新建的UI component添加到新的workcenter里
  6. qdu_ACM集训队3月5号组队训练
  7. drill apache_使用Apache Drill深入研究当今的大数据
  8. MySQL 多表查询、连接查询(内连接、外连接)
  9. Sphinx index.rst
  10. 如何定制日历控件显示的星期文字
  11. 深入浅出BP神经网络(反向传播算法)
  12. 如何使用 Python 开发加权平均集成
  13. 热带鱼屏保(Marine Aquarium 3),如何才能绕过它的Key Code?
  14. 一阶线性微分方程的初等积分法
  15. 刷脸支付代理收益盈利模式
  16. 虚拟网络的无损保证-zOVN
  17. 阿里云python自测答案_阿里云技能测试python初级中级高级
  18. thinksns java_ThinkSNS+ 更新播报
  19. 从阿里巴巴发行价看A股新股投资机会
  20. pip安装matplotlib

热门文章

  1. error code ELIFECYCLE
  2. idea工具修改Git路径
  3. 【HDOJ7059】Counting Stars(线段树,区间加,乘,标记)
  4. 2021牛客暑期多校训练营7,签到题FHI
  5. 2021牛客寒假算法基础集训营1,签到题ABFIJ
  6. JavaScript数据类型之字符串型(4)
  7. Flex练习-打游戏
  8. linux邮件收发程序流程图,[源码和文档分享]基于C语言和TCP Socket实现的Linux环境下的邮件收发客户端程序...
  9. Codeforces Round #493 (Div. 2):C. Convert to Ones
  10. 用SQL表达内连接和外链接