Android事件分发机制_setonlicklistener-程序员宅基地

技术标签: 事件分发机制 onTouch onCl  android  View  

                                                      Android事件分发机制

一、Android分发机制概述:

             Android如此受欢迎,就在于其优秀的交互性,这其中,Android优秀的事件分发机制功不可没,那么,作为一个优秀的程序员,要想做一个具有良好交互性的应用,必须透彻理解Android的事件分发机制。  要想充分理解android的分发机制,需要先对以下几个知识点有所了解:

       1、View和ViewGroup是什么?

      2、事件。

      3、View事件的分发机制。

      4、ViewGroup事件的分发机制。

二、View和ViewGroup:

      Android的UI界面都是由View和ViewGroup及其派生类组合而成的,其中View是所有UI组件的基类,而ViewGroup是容纳这些组件的容器,其本身也是从ViewGroup中派生出来的,也就是说ViewGroup的父类就是View。通常来说,Button、ImageView、TextView等控价都是继承父类View来实现的。RelativeLayout、LinearLayout、FrameLayout等布局都是继承父类ViewGroup来实现的。

三、事件:

    1、 当手指触摸到View或ViewGroup派生的控件后,将会触发一系列触发响应事件,如:

     onTouchEvent、onClick、onLongClick等。每个View都有自己处理事件的回调方法,开发人员只需要重写这些回调方法,就可以实现需要的响应事件。

    2、  而事件通常重要的有如下三种:

      MotionEvent.ACTION_DOWN       按下View,是所有事件的开始
   MotionEvent.ACTION_MOVE        滑动事件
   MotionEvent.ACTION_UP              与down对应,表示抬起

   3、 事件的响应原理:

     在Android开发设计模式中,最广泛的应用就是监听、回调、进而形成了事件响应的过程。

      以Button的OnClick为例,因为Button也是一个View,所以它也拥有View父类的方法,在View中源码如下:

/**定义接口成员变量*/

protected OnClickListener mOnClickListener;

/**

 * Interface definition for a callback to be invoked when a view is clicked.

 */

public interface OnClickListener {

    /**

     * Called when a view has been clicked.

     *

     * @param v The view that was clicked.

     */

    void onClick(View v);

}

/**

 * Register a callback to be invoked when this view is clicked. If this view is not

 * clickable, it becomes clickable.

 *

 * @param l The callback that will run

 *

 * @see #setClickable(boolean)

 */

public void setOnClickListener(OnClickListener l) {

    if (!isClickable()) {

        setClickable(true);

    }

    mOnClickListener = l;

}



/**

 * Call this view's OnClickListener, if it is defined.

 *

 * @return True there was an assigned OnClickListener that was called, false

 *         otherwise is returned.

 */

public boolean performClick() {

    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);



    if (mOnClickListener != null) {

        playSoundEffect(SoundEffectConstants.CLICK);

        mOnClickListener.onClick(this);

        return true;

    }



    return false;

}

/**触摸了屏幕后,实现并调用的方法*/

public boolean onTouchEvent(MotionEvent event) {

    …..

    if (mPerformClick == null) {

        mPerformClick = new PerformClick();

    }

    if (!post(mPerformClick)) {

        performClick();

    }

    …..

    }
    以上是View源码中关键代码行,以Button为例,假设需要在一个布局上添加一个按钮,并实现它的OnClick事件,需要如下步骤。

   1、OnClickListener类是一个当控件被点击后进行回调的一个接口,他完成被点击后的回调通知。

    2、创建一个按钮Button,并设置监听事件,对这个Button进行setOnCklickListener操作。

    3、当手指触摸到Button按钮,通过一系列方法,触发并执行到OntouchEvent方法并执行mPerformClick方法,在mPerformClick方法中,首先会判断注册的mOnClickListener是否为空,若不为空,他就会回调之前注册的OnClick方法,进而执行用户自定义代码。

   事件响应机制,简单来说上面的例子就已经基本诠释了

    1、注册一个监听对象

    2、实现监听对象的监听事件

    3、当某一触发事件到来,在触发事件中通过注册过的监听对象,回调注册对象的响应事件,来完成用户自定义实现。

   但凡明白了这一个简单的事件响应过程,就离事件驱动开发的整个过程就不远了。

四、View事件的分发机制:

     通过上面的例子,我们初步的接触了View的事件分发机制,在进一步了解,首先,我们要熟悉dispatchTouchEvent和onTouchEvent两个函数,这两个函数都是View的函数,要理解view事件的分发机制,只要清楚这两个函数就基本清楚了。

    在这里先提醒一句,这里的“分发”是指一个触摸或点击事件发生,分发给当前触摸控件所监听的事件(如Click、onTouch等),进而来决定是控件的那个函数来响应此事件。

     1、dispatchTouchEvent:此函数负责事件的分发,你只需要记住当触摸一个View控件,首先会调用这个函数就行,在这个函数体里决定将事件分发给谁来处理。

      2、onTouchEvent:此函数负责执行事件的处理,负责处理事件,主要处理MotionEvent.ACTION_DOWN、
MotionEvent.ACTION_MOVE 、MotionEvent.ACTION_UP这三个事件。

      3、 public boolean onTouchEvent (MotionEvent event) :参数event为手机屏幕触摸事件封装类的对象,其中封装了该事件的所有信息,例如触摸的位置,触摸的类型以及触摸的时间等。该对象会在用户触摸手机屏幕时被创建。

     那么他是如何执行这个流程的呢,我们以布局上的一个按钮为例,看看他是如何实现的。

     我们知道,View作为所有控件的父类,他本身定义了很多接口来监听触摸在View上的事件,如OnClickListener(点击)、OnLongClickListener(长按)、OnTouchListener(触摸监听)等。那么当手指触摸到View的时候,该响应“点击”还是“触摸”呢,就是根据dispatchTouchEvent和onTouchEvent这两个函数组合实现的,我们之下的讨论,仅对常用的“点击onclick”和“触摸onTouch”来讨论,顺藤摸瓜,找出主线,进而搞清楚view的事件分发机制。

   对于上面的那个按钮,点击他一下,我们期望两种结果,第一种,他响应一个点击事件,第二种,不响应点击事件。

     第一种结果的源码

public class MainActivity extends AppCompatActivity implements View.OnClickListener,View.OnTouchListener {

        private Button btnButton;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            btnButton= (Button) findViewById(R.id.btn);
            btnButton.setOnClickListener(this);
            btnButton.setOnTouchListener(this);

        }


        @Override
        public void onClick(View view) {
            switch (view.getId()) {
                case R.id.btn:
                   //该方法被执行
                     break;
                default:
                    break;
            }
        }

        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            return false;
        }
}
     第二种结果的源码

    

public class MainActivity extends AppCompatActivity implements View.OnClickListener,View.OnTouchListener {

        private Button btnButton;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            btnButton= (Button) findViewById(R.id.btn);
            btnButton.setOnClickListener(this);
            btnButton.setOnTouchListener(this);

        }


        @Override
        public void onClick(View view) {
            switch (view.getId()) {
                case R.id.btn:
                   //该方法未被执行
                     break;
                default:
                    break;
            }
        }

        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            return true;
        }
}
     结果分析:

    上面两处代码,第一种执行了onClick函数和onTouch函数,第二种执行了onTouch函数,并没有执行onClick函数,而且对两处代码进行比较,发现只有在onTouch处返回值true和false不同。当onTouch返回值为false时,onClick被执行,返回true时,onClick未被执行。

    为什么这样呢?我们只有深入源码才能分析出来。

    前面提到,触摸一个view就会执行dispatchTouchEvent方法去“分发事件”,既然触发的按钮是Button,那么我们就去查看Button源码,寻找dispatchTouchEvent方法,Button源码中没有dispatchTouchEvent方法,但知道Button继承TextView,寻找TextView,发现他也没有dispatchTouchEvent方法。继续查找TextView的父类View,发现View有dispatchTouchEvent方法,那我们就分析dispatchTouchEvent方法。

   代码如下:

public boolean dispatchTouchEvent(MotionEvent event) {

        if (onFilterTouchEventForSecurity(event)) {

             //noinspection SimplifiableIfStatement

            if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&

                     mOnTouchListener.onTouch(this, event)) {

                 return true;

             }

             if (onTouchEvent(event)) {

                return true;

             }

        }

        return false;

 }

     分析:
    先来看dispatchTouchEvent函数返回值,如果返回true,表明事件被处理了,反之,表明事件未被处理。
    这个判定很重要,mOnTouchListener != null,判断该控件是否注册了OnTouchListener对象的监听,(mViewFlags & ENABLED_MASK) == ENABLED,判断当前的控件是否能被点击(比如Button默认可以点击,ImageView默认不许点击,看到这里就了然了),mOnTouchListener.onTouch(this, event)这个是关键,这个调用,就是回调你注册在这个View上的mOnTouchListener对象的onTouch方法,如果你在onTouch方法里返回false,那么这个判断语句就跳出,去执行下面的程序,否则,当前2个都返回了true,自定义onTouch方法也返回true,条件成立,就直接返回了,不再执行下面的程序。接下来,if (onTouchEvent(event)) 这个判断很重要,能否回调OnClickListener接口的onClick函数,关键在于此,可以肯定的是,如果上面if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&mOnTouchListener.onTouch(this, event))返回true,那么就不会执行并回调OnClickListener接口的onClick函数。接下来,我们看onTouchEvent这个函数,看它是如何响应点击事件的。

主要代码如下:


  public boolean onTouchEvent(MotionEvent event) {
 
         final int viewFlags = mViewFlags;
 
 
 
          if ((viewFlags & ENABLED_MASK) == DISABLED) {
 
             if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {

                 mPrivateFlags &= ~PRESSED;
 
                  refreshDrawableState();
 
            }
 
           // 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 (prepressed) {

                              // The button is being released before we actually
 
                              // showed it as pressed.  Make it show the pressed
 
                             // state now (before scheduling the click) to ensure
 
                           // the user sees it.
 
                           mPrivateFlags |= PRESSED;

                             refreshDrawableState();
 
                         }
 
  
 
                          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) {
 
                             postDelayed(mUnsetPressedState,

                                    ViewConfiguration.getPressedStateDuration());

                        } else if (!post(mUnsetPressedState)) {

                            // If the post failed, unpress right now

                            mUnsetPressedState.run();
 
                        }
 
                         removeTapCallback();
 
                     }
 
                     break;

 

                case MotionEvent.ACTION_DOWN:
 
                     mHasPerformedLongPress = false;
 
 
 
                     if (performButtonActionOnTouchDown(event)) {

                        break;

                     }
 

                     // Walk up the hierarchy to determine if we're inside a scrolling container.
 
                     boolean isInScrollingContainer = isInScrollingContainer();

 

                     // For views inside a scrolling container, delay the pressed feedback for
 
                     // a short period in case this is a scroll.
 
                     if (isInScrollingContainer) {
 
                         mPrivateFlags |= PREPRESSED;
                         if (mPendingCheckForTap == null) {
 
                            mPendingCheckForTap = new CheckForTap();
 
                        }

                         postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
 
                    } else {
 
                         // Not inside a scrolling container, so show the feedback right away
 
                         mPrivateFlags |= PRESSED;

                        refreshDrawableState();
 
                         checkForLongClick(0);
 
                     }
 
                     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
 
                     if (!pointInView(x, y, mTouchSlop)) {
 
                         // 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;
 
 }

     public boolean performClick() {
 
         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
 

        if (mOnClickListener != null) {
 
             playSoundEffect(SoundEffectConstants.CLICK);
 
             mOnClickListener.onClick(this);
 
             return true;
 
         }
 
 
 
         return false;
 
     }

复制代码

 

      代码量太大了,不过不要紧,我们通过主要代码分析一下。

 
复制代码

   public boolean onTouchEvent(MotionEvent event) {
  
       
  
          //控件不能被点击
 
          if ((viewFlags & ENABLED_MASK) == DISABLED) {
  
                …
 
          }
 
  //委托代理别的View去实现
 
          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:
 
                            …...
 
                              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)) {
 
                         //这里就是去执行回调注册的onClick函数,实现点击
 
                                      performClick();
 
                                  }
 
                              }
 
                              ……
 
                      break;
 
             //按下事件
 
                  case MotionEvent.ACTION_DOWN:
 
                      
 
                      ……
 
                      break;
 
  
 
                 ……
 
            //移动事件
 
                  case MotionEvent.ACTION_MOVE:
 
                       ……
 
                     break;
 
             }
 
       
 
              return true;

          }
         return false;
 
 }

 

复制代码

 

 

 

      从上面主要代码可以看出onTouchEvent传参MotionEvent类型,它封装了触摸的活动事件,其中就有MotionEvent.ACTION_DOWN、MotionEvent.ACTION_MOVE、MotionEvent.ACTION_UP三个事件。我们在来看看onTouchEvent的返回值,因为onTouchEvent是在dispatchTouchEvent事件分发处理中调用的,
复制代码

  public boolean dispatchTouchEvent(MotionEvent event) {
 
           ……
 
              if (onTouchEvent(event)) {
 
                  return true;
 
              }
 
 return fasle;
 
         }

复制代码

 

      如果onTouchEvent返回true,dispatchTouchEvent就返回true,表明事件被处理了,反之,事件未被处理。

 

程序的关键在  if (((viewFlags & CLICKABLE) == CLICKABLE ||

                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE))的判断里,我们发现无论switch的分支在什么地方跳出,返回都是true。这就表明,无论是三个事件中的哪一个,都会返回true。

      参照下图,结合上述,不难理解View的分发机制了。

 

                                      (图④)

四、      ViewGroup事件分发机制:

      ViewGroup事件分发机制较View的稍微复杂一些,不过对View的机制只要精确的理解后,仔细看过这一节,睡几觉起来,估计也就悟出来了,学习就是这么奇怪,当下理解不了或模糊的地方,只要脑子有印象,忽然一夜好像就懂了。

      先来看下面的一个简单布局,我们将通过例子,了解ViewGroup+View的android事件处理机制。

                  

                                           (图⑤)

      上图由:黑色为线性布局LinearLayout,紫色为相对布局RelativeLayout,按钮Button三部分组成。RelativeLayout为LinearLayout的子布局,Button为RelativeLayout的子布局。以下RelativeLayout简称(R),LinearLayout简称(L),Button简称(B)。

      经过前面讲解,我们首先知道这样两件事情。

1、(R)和(L)的父类是ViewGroup,(B)的父类是View。

2、dispatchTouchEvent这个函数很重要,不论是ViewGroup还是View,都由它来处理事件的消费和传递。

      下面,我们通过横向和纵向两个维度,通过源码和图解的方式,充分理解事件的传递机制。

      先来看整体的事件传递过程:

                                                             (图⑥)

      当手指点击按钮B时,事件传递的顺序是从底向上传递的,也就是按照L->R->B的顺序由下往上逐层传递,响应正好相反,是自上而下。

      L首先接收到点击事件,L的父类是ViewGroup类,并将事件传递给dispatchTouchEvent方法,dispatchTouchEvent函数中判断该控件L是否重载了onInterceptTouchEvent方法进行事件拦截,onInterceptTouchEvent默认返回false不拦截,那么dispatchTouchEvent方法将事件传递给R去处理(进入第2流程处理),如果返回true表示当前L控件拦截了事件向其它控件的传递,交给它自己父类View的dispatchTouchEvent去处理,在父方法的dispatchTouchEvent中,将会按照前面讲的View的事件处理机制去判断,比如判断L是否重载了onTouch方法,是否可点击,是否做了监听等事件。

      R也是ViewGroup的子类,因此与第1流程基本相似,如果onInterceptTouchEvent返回了false,表示事件将不拦截继续传递给B。

      B是View的子类,它没有onInterceptTouchEvent方法,直接交给自己父类View的dispatchTouchEvent去处理,流程同不再敷述。

      总结:

      onInterceptTouchEvent只有ViewGroup才有,当一个控件是继承自ViewGroup而来的,那么它就可能会有子控件,因此,才有可能传递给子控件,而继承自View的控件,不会有子控件,也就没有onInterceptTouchEvent函数了。

      通过dispatchTouchEvent分发的控件返回值True和false,表示当前控件是否消费了传递过来的事件,如果消费了,返回True,反之false。消费了,就不再继续传递了,没有消费,如果有子控件将继续传递。

      啰嗦点,如果想再深层次了解一下,再次从源码ViewGroup来分析一个L控件的事件传递过程,请看下图:

                                                      (图⑦)

      结合上面的图例,下面列出ViewGroup源码来分析一下,我们只需要分析ViewGroup的dispatchTouchEvent、onInterceptTouchEvent、dispatchTransformedTouchEvent三个方法即可。
复制代码

   public boolean dispatchTouchEvent(MotionEvent ev) {
  
           if (mInputEventConsistencyVerifier != null) {
  
               mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
  
           }
  
   
 
          boolean handled = false;
 
          if (onFilterTouchEventForSecurity(ev)) {
 
              final int action = ev.getAction();
 
              final int actionMasked = action & MotionEvent.ACTION_MASK;
 
  
 
              // Handle an initial down.
 
              if (actionMasked == MotionEvent.ACTION_DOWN) {
 
                 // Throw away all previous state when starting a new touch gesture.
 
                  // The framework may have dropped the up or cancel event for the previous gesture
 
                  // due to an app switch, ANR, or some other state change.
 
                 cancelAndClearTouchTargets(ev);
 
                  resetTouchState();
 
              }
 
  
 
              // Check for interception.
 
              final boolean intercepted;
 
              if (actionMasked == MotionEvent.ACTION_DOWN
 
                      || mFirstTouchTarget != null) {
 
                  final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
 
                  if (!disallowIntercept) {
 
                      intercepted = onInterceptTouchEvent(ev);
 
                      ev.setAction(action); // restore action in case it was changed
 
                  } else {
 
                      intercepted = false;
 
                  }
 
              } else {
 
                 // There are no touch targets and this action is not an initial down
 
                  // so this view group continues to intercept touches.
 
                  intercepted = true;
 
              }
 
 
 
              // Check for cancelation.
 
              final boolean canceled = resetCancelNextUpFlag(this)
 
                     || actionMasked == MotionEvent.ACTION_CANCEL;
 
  
 
              // Update list of touch targets for pointer down, if needed.
 
              final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
 
              TouchTarget newTouchTarget = null;
 
              boolean alreadyDispatchedToNewTouchTarget = false;
 
              if (!canceled && !intercepted) {
 
                  if (actionMasked == MotionEvent.ACTION_DOWN
 
                          || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
 
                          || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
 
                     final int actionIndex = ev.getActionIndex(); // always 0 for down
 
                      final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
 
                             : TouchTarget.ALL_POINTER_IDS;
 
 
 
                     // Clean up earlier touch targets for this pointer id in case they
 
                     // have become out of sync.
 
                     removePointersFromTouchTargets(idBitsToAssign);
 
 
 
                     final int childrenCount = mChildrenCount;
 
                     if (childrenCount != 0) {
 
                         // Find a child that can receive the event.
 
                         // Scan children from front to back.

                        final View[] children = mChildren;
 
                         final float x = ev.getX(actionIndex);
 
                         final float y = ev.getY(actionIndex);
 
 

                        for (int i = childrenCount - 1; i >= 0; i--) {

                             final View child = children[i];
 
                             if (!canViewReceivePointerEvents(child)

                                     || !isTransformedTouchPointInView(x, y, child, null)) {
 
                                 continue;
 
                             }
 
 

                             newTouchTarget = getTouchTarget(child);
 
                             if (newTouchTarget != null) {

                                // Child is already receiving touch within its bounds.
 
                                 // Give it the new pointer in addition to the ones it is handling.
 
                                 newTouchTarget.pointerIdBits |= idBitsToAssign;
 
                                 break;
 
                             }
 
 
 
                             resetCancelNextUpFlag(child);

                             if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
 
                                 // Child wants to receive touch within its bounds.
 
                                mLastTouchDownTime = ev.getDownTime();
 
                                 mLastTouchDownIndex = i;
 
                                 mLastTouchDownX = ev.getX();
 
                                 mLastTouchDownY = ev.getY();
 
                                 newTouchTarget = addTouchTarget(child, idBitsToAssign);
 
                                 alreadyDispatchedToNewTouchTarget = true;
 
                                 break;
 
                             }

                         }
 
                     }
 
 
 
                     if (newTouchTarget == null && mFirstTouchTarget != null) {
 
                         // Did not find a child to receive the event.
 
                        // Assign the pointer to the least recently added target.

                        newTouchTarget = mFirstTouchTarget;
 
                         while (newTouchTarget.next != null) {
 
                             newTouchTarget = newTouchTarget.next;
 
                         }

                         newTouchTarget.pointerIdBits |= idBitsToAssign;
 
                     }
 
                 }
 
             }
 
 

             // Dispatch to touch targets.
 
             if (mFirstTouchTarget == null) {
 
                // No touch targets so treat this as an ordinary view.
 
                 handled = dispatchTransformedTouchEvent(ev, canceled, null,
 
                         TouchTarget.ALL_POINTER_IDS);
 
             } else {
 
                 // Dispatch to touch targets, excluding the new touch target if we already
 
                 // dispatched to it.  Cancel touch targets if necessary.
 
                 TouchTarget predecessor = null;
 
                 TouchTarget target = mFirstTouchTarget;
                while (target != null) {

                     final TouchTarget next = target.next;
 
                     if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
 
                         handled = true;

                     } else {
 
                         final boolean cancelChild = resetCancelNextUpFlag(target.child)
 
                         || intercepted;
 
                         if (dispatchTransformedTouchEvent(ev, cancelChild,
 
                                 target.child, target.pointerIdBits)) {
 
                             handled = true;
 
                        }
 
                         if (cancelChild) {
 
                             if (predecessor == null) {
                                  mFirstTouchTarget = next;
 
                            } else {
 
                                 predecessor.next = next;

                             }
 
                             target.recycle();
 
                             target = next;
 
                             continue;
 
                         }
 
                     }
                     predecessor = target;
 
                     target = next;
 
                 }
 
             }
 
 
             // Update list of touch targets for pointer up or cancel, if needed.
 
             if (canceled
 
                     || actionMasked == MotionEvent.ACTION_UP

                     || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
 
                 resetTouchState();
 
            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {

                 final int actionIndex = ev.getActionIndex();
 
                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
 
                 removePointersFromTouchTargets(idBitsToRemove);
 
             }

         }
 
 
        if (!handled && mInputEventConsistencyVerifier != null) {
 
             mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
 
         }

         return handled;
 
 }
 
   public boolean onInterceptTouchEvent(MotionEvent ev) {
 
         return false;
 
     }
 
   private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,

             View child, int desiredPointerIdBits) {

         final boolean handled;
 
 
 
         // Canceling motions is a special case.  We don't need to perform any transformations

         // or filtering.  The important part is the action, not the contents.
 
         final int oldAction = event.getAction();
 
         if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
 
             event.setAction(MotionEvent.ACTION_CANCEL);
 
             if (child == null) {
                 handled = super.dispatchTouchEvent(event);
 
            } else {
 
                handled = child.dispatchTouchEvent(event);

            }
 
             event.setAction(oldAction);

            return handled;

         }
         // Calculate the number of pointers to deliver.
         final int oldPointerIdBits = event.getPointerIdBits();
         final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
         // If for some reason we ended up in an inconsistent state where it looks like we
        // might produce a motion event with no pointers in it, then drop the event.
         if (newPointerIdBits == 0) {
             return false;
         }
         // If the number of pointers is the same and we don't need to perform any fancy
         // irreversible transformations, then we can reuse the motion event for this
        // dispatch as long as we are careful to revert any changes we make.
         // Otherwise we need to make a copy.
         final MotionEvent transformedEvent;
         if (newPointerIdBits == oldPointerIdBits) {
             if (child == null || child.hasIdentityMatrix()) {
                 if (child == null) {
                     handled = super.dispatchTouchEvent(event);
                } else {
                     final float offsetX = mScrollX - child.mLeft;
                     final float offsetY = mScrollY - child.mTop;
                     event.offsetLocation(offsetX, offsetY);
                     handled = child.dispatchTouchEvent(event);
 
                   event.offsetLocation(-offsetX, -offsetY);
                }
                 return handled;
            }
             transformedEvent = MotionEvent.obtain(event);
         } else {
             transformedEvent = event.split(newPointerIdBits);
         }
         // Perform any necessary transformations and dispatch.
        if (child == null) {
 
            handled = super.dispatchTouchEvent(transformedEvent);
         } else {
            final float offsetX = mScrollX - child.mLeft;
             final float offsetY = mScrollY - child.mTop;
             transformedEvent.offsetLocation(offsetX, offsetY);
             if (! child.hasIdentityMatrix()) {
                 transformedEvent.transform(child.getInverseMatrix());
             }
             handled = child.dispatchTouchEvent(transformedEvent);
        }

         // Done.
 
         transformedEvent.recycle();
 
        return handled;
 
     }
 
 

复制代码

 

      代码量比较大,我们先概述一下各个函数的主要作用。

      dispatchTouchEvent主要用来分发事件,函数主要作用是来决定当前的事件是交由自己消费处理,还是交由子控件处理。

      onInterceptTouchEvent主要来决定当前控件是否需要拦截传递给子控件,如果返回True表示该控件拦截,并交由自己父类的dispatchTouchEvent处理消费,如果返回false表示不拦截,允许传递给子控件处理。

      dispatchTransformedTouchEvent主要根据传来的子控件,决定是自身处理消费,还是交由子控件处理消费。

我们主要来分析一下dispatchTouchEvent函数:

 
复制代码

     if (actionMasked == MotionEvent.ACTION_DOWN
 
                    || mFirstTouchTarget != null) {
 
                 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
 
                if (!disallowIntercept) {
 
                      intercepted = onInterceptTouchEvent(ev);
 
                     ev.setAction(action); // restore action in case it was changed
 
                 } else {
 
                   intercepted = false;
 
                 }
 
             } else {
 
                 // There are no touch targets and this action is not an initial down

                 // so this view group continues to intercept touches.
 
                 intercepted = true;
 
             }

复制代码

 

 

 

      这段代码,如果当前传递的事件是Down(按下)或者当前触摸链表不为空,那么它调用onInterceptTouchEvent函数,判断是否进行事件拦截处理,通过返回值来决定intercepted变量的值。

接下来if (!canceled && !intercepted){} 这个括号内的代码需要注意了,只有当intercepted返回值为false的时候,才满足这个条件进入代码段。因此,我们结合onInterceptTouchEvent源码,发现它默认值返回的是false,也就说如果你不重载onInterceptTouchEvent方法并令其返回True,它一定是返回false,并能够执行花括号内的代码。

      我们分析一下花括号中的代码,if (actionMasked == MotionEvent.ACTION_DOWN

                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)

                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {}判断当前的事件是否是ACTION_DOWN、ACTION_POINTER_DOWN(多点触摸)、ACTION_HOVER_MOVE(悬停),如果是,执行花括号内代码,

final int childrenCount = mChildrenCount;

if (childrenCount != 0) {}判断当前控件是否有子控件,如果大于0,执行花括号内代码,

  for (int i = childrenCount - 1; i >= 0; i--)遍历子控件,

if (!canViewReceivePointerEvents(child)

      判断当前的down、POINTER_DOWN、HOVER_MOVE三个事件的坐标点是否落在了子控件上,如果落在子控件上,

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))

      通过dispatchTransformedTouchEvent传递事件,交由子控件判断是否传递或自己消费处理。如果dispatchTransformedTouchEvent返回true,表示子控件已消费处理,并添加此子控件View到触摸链表,并放置链表头,并结束遍历子控件。newTouchTarget = addTouchTarget(child, idBitsToAssign);false表示未处理。

      接着分析

 
复制代码

   if (mFirstTouchTarget == null) {
 
                  handled = dispatchTransformedTouchEvent(ev, canceled, null,
 
                         TouchTarget.ALL_POINTER_IDS);
 
    } else {
 
        ……
 
 }

复制代码

 

 

 

      mFirstTouchTarget什么时候为空呢?从前面的代码可以看到,如果onInterceptTouchEvent返回为false(也就是不拦截),mFirstTouchTarget就为空,直接交给自己父View执行dispatchTouchEvent去了。如果mFirstTouchTarget不为空,它就取出触摸链表,逐个遍历判断处理,如果前面比如Down事件处理过了,就不再处理了。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_37041645/article/details/74642989

智能推荐

while循环&CPU占用率高问题深入分析与解决方案_main函数使用while(1)循环cpu占用99-程序员宅基地

文章浏览阅读3.8k次,点赞9次,收藏28次。直接上一个工作中碰到的问题,另外一个系统开启多线程调用我这边的接口,然后我这边会开启多线程批量查询第三方接口并且返回给调用方。使用的是两三年前别人遗留下来的方法,放到线上后发现确实是可以正常取到结果,但是一旦调用,CPU占用就直接100%(部署环境是win server服务器)。因此查看了下相关的老代码并使用JProfiler查看发现是在某个while循环的时候有问题。具体项目代码就不贴了,类似于下面这段代码。​​​​​​while(flag) {//your code;}这里的flag._main函数使用while(1)循环cpu占用99

【无标题】jetbrains idea shift f6不生效_idea shift +f6快捷键不生效-程序员宅基地

文章浏览阅读347次。idea shift f6 快捷键无效_idea shift +f6快捷键不生效

node.js学习笔记之Node中的核心模块_node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是-程序员宅基地

文章浏览阅读135次。Ecmacript 中没有DOM 和 BOM核心模块Node为JavaScript提供了很多服务器级别,这些API绝大多数都被包装到了一个具名和核心模块中了,例如文件操作的 fs 核心模块 ,http服务构建的http 模块 path 路径操作模块 os 操作系统信息模块// 用来获取机器信息的var os = require('os')// 用来操作路径的var path = require('path')// 获取当前机器的 CPU 信息console.log(os.cpus._node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是

数学建模【SPSS 下载-安装、方差分析与回归分析的SPSS实现(软件概述、方差分析、回归分析)】_化工数学模型数据回归软件-程序员宅基地

文章浏览阅读10w+次,点赞435次,收藏3.4k次。SPSS 22 下载安装过程7.6 方差分析与回归分析的SPSS实现7.6.1 SPSS软件概述1 SPSS版本与安装2 SPSS界面3 SPSS特点4 SPSS数据7.6.2 SPSS与方差分析1 单因素方差分析2 双因素方差分析7.6.3 SPSS与回归分析SPSS回归分析过程牙膏价格问题的回归分析_化工数学模型数据回归软件

利用hutool实现邮件发送功能_hutool发送邮件-程序员宅基地

文章浏览阅读7.5k次。如何利用hutool工具包实现邮件发送功能呢?1、首先引入hutool依赖<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.19</version></dependency>2、编写邮件发送工具类package com.pc.c..._hutool发送邮件

docker安装elasticsearch,elasticsearch-head,kibana,ik分词器_docker安装kibana连接elasticsearch并且elasticsearch有密码-程序员宅基地

文章浏览阅读867次,点赞2次,收藏2次。docker安装elasticsearch,elasticsearch-head,kibana,ik分词器安装方式基本有两种,一种是pull的方式,一种是Dockerfile的方式,由于pull的方式pull下来后还需配置许多东西且不便于复用,个人比较喜欢使用Dockerfile的方式所有docker支持的镜像基本都在https://hub.docker.com/docker的官网上能找到合..._docker安装kibana连接elasticsearch并且elasticsearch有密码

随便推点

Python 攻克移动开发失败!_beeware-程序员宅基地

文章浏览阅读1.3w次,点赞57次,收藏92次。整理 | 郑丽媛出品 | CSDN(ID:CSDNnews)近年来,随着机器学习的兴起,有一门编程语言逐渐变得火热——Python。得益于其针对机器学习提供了大量开源框架和第三方模块,内置..._beeware

Swift4.0_Timer 的基本使用_swift timer 暂停-程序员宅基地

文章浏览阅读7.9k次。//// ViewController.swift// Day_10_Timer//// Created by dongqiangfei on 2018/10/15.// Copyright 2018年 飞飞. All rights reserved.//import UIKitclass ViewController: UIViewController { ..._swift timer 暂停

元素三大等待-程序员宅基地

文章浏览阅读986次,点赞2次,收藏2次。1.硬性等待让当前线程暂停执行,应用场景:代码执行速度太快了,但是UI元素没有立马加载出来,造成两者不同步,这时候就可以让代码等待一下,再去执行找元素的动作线程休眠,强制等待 Thread.sleep(long mills)package com.example.demo;import org.junit.jupiter.api.Test;import org.openqa.selenium.By;import org.openqa.selenium.firefox.Firefox.._元素三大等待

Java软件工程师职位分析_java岗位分析-程序员宅基地

文章浏览阅读3k次,点赞4次,收藏14次。Java软件工程师职位分析_java岗位分析

Java:Unreachable code的解决方法_java unreachable code-程序员宅基地

文章浏览阅读2k次。Java:Unreachable code的解决方法_java unreachable code

标签data-*自定义属性值和根据data属性值查找对应标签_如何根据data-*属性获取对应的标签对象-程序员宅基地

文章浏览阅读1w次。1、html中设置标签data-*的值 标题 11111 222222、点击获取当前标签的data-url的值$('dd').on('click', function() { var urlVal = $(this).data('ur_如何根据data-*属性获取对应的标签对象

推荐文章

热门文章

相关标签