Android事件分發機制二

創建Demo

MainActivity.java

public class MainActivity extends Activity {

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

      //初始化控件
      MyLinearLayout mgLayout = (MyLinearLayout) findViewById(R.id.mylayout);
      Button btn = (Button) findViewById(R.id.btn);

      //添加onClick事件
      mgLayout.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View view) {
              Log.i("chuyibo","點擊了自定義線性佈局MyLinearLayout");
          }
      });
      //添加onClick事件
      btn.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View view) {
              Log.i("chuyibo","點擊了btn");
          }
      });
  }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<bo.yi.chu.chuyibo2.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mylayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:background="#fff">

    <Button
        android:layout_width="150dp"
        android:layout_height="50dp"
        android:id="@+id/btn"
        android:background="#684132"
        android:text="點擊"
        android:textSize="16sp"
        android:textColor="#fff"/>
</bo.yi.chu.chuyibo2.MyLinearLayout>

MyLinearLayout.java

public class MyLinearLayout extends LinearLayout {

  public MyLinearLayout(Context context, AttributeSet attrs) {
      super(context, attrs);
  }

  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
      return false;
  }
}

運行

這裏寫圖片描述

點擊Button查看Log日誌

這裏寫圖片描述

點擊空白區域查看Log日誌

這裏寫圖片描述

查看日誌得出結論,myLayout爲btn的父類,兩者同時添加onClick監聽,點擊btn,只有btn的onClick()方法執行。點擊myLayout(空白區域)只有myLayout的onClick()方法執行,Demo中MyLinearLayout繼承LinearLayout,並重寫了onInterceptTouchEvent()。

onInterceptTouchEvent()方法中有一個返回值,Demo中的返回值爲false。把該方法的返回值改爲true,重新點擊Button和空白區域查看Log日誌。

public boolean onInterceptTouchEvent(MotionEvent ev) {
    return true;//返回值改爲true
}

點擊Button查看Log日誌

這裏寫圖片描述

點擊空白區域查看Log日誌

這裏寫圖片描述

查看日誌得出結論,當onInterceptTouchEvent()方法返回true時,無論點擊btn還是myLayout,都只執行myLayout的onClick()方法。

android中的佈局直接或者間接的繼承了ViewGroup,ViewGroup又是View的子類,但是ViewGroup重寫了dispatchTouchEvent()方法。ViewGroup中可以存放多個View,當點擊一個ViewGroup中的子View時觸摸事件是先傳遞給了ViewGroup,然後有ViewGroup的dispatchTouchEvent()分發給子View,最後再有子View的dispatchTouchEvent()來處理觸摸事件。同時ViewGroup也可以攔截事件從而使觸摸事件不傳遞給子View,onInterceptTouchEvent()方法就是事件傳遞的攔截器。

源碼分析

dispatchTouchEvent()

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)) {
                // 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;
                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;
                                return true;
                            }
                            // The event didn't get handled, try the next view.
                            // Don't reset the event's location, it's not
                            // necessary here.
                        }
                    }
                }
            }
        }

        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.
            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);
    }

第21行是一個if判斷,disallowIntercept表示是否禁止掉攔截事件的功能,默認爲false,因此if判斷爲true或false取決於onInterceptTouchEvent()方法的返回值。

onInterceptTouchEvent()方法返回false表示不攔截事件。if條件判斷成立,在第30行對ViewGroup中的子View進行遍歷,並在第35行進行判斷當前子View是否是正在點擊的View。如果是,執行View的dispatchTouchEvent()。因爲View的dispatchTouchEvent()方法始終返回true,所以40行if條件判斷成立,執行return,後面的代碼不執行。

onInterceptTouchEvent()方法返回true表示攔截事件,if條件判斷不成立。或ViewGroup中沒有子View,以及ViewGroup中的所有子View都不是當前正在點擊的View時執行後面的語句。第66行判斷target是否爲null。target一般不會爲null,執行第70行super.dispatchTouchEvent()就是調用父類View中的dispatchTouchEvent()方法。

流程圖

這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章