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

智能推荐

使用nginx解决浏览器跨域问题_nginx不停的xhr-程序员宅基地

文章浏览阅读1k次。通过使用ajax方法跨域请求是浏览器所不允许的,浏览器出于安全考虑是禁止的。警告信息如下:不过jQuery对跨域问题也有解决方案,使用jsonp的方式解决,方法如下:$.ajax({ async:false, url: 'http://www.mysite.com/demo.do', // 跨域URL ty..._nginx不停的xhr

在 Oracle 中配置 extproc 以访问 ST_Geometry-程序员宅基地

文章浏览阅读2k次。关于在 Oracle 中配置 extproc 以访问 ST_Geometry,也就是我们所说的 使用空间SQL 的方法,官方文档链接如下。http://desktop.arcgis.com/zh-cn/arcmap/latest/manage-data/gdbs-in-oracle/configure-oracle-extproc.htm其实简单总结一下,主要就分为以下几个步骤。..._extproc

Linux C++ gbk转为utf-8_linux c++ gbk->utf8-程序员宅基地

文章浏览阅读1.5w次。linux下没有上面的两个函数,需要使用函数 mbstowcs和wcstombsmbstowcs将多字节编码转换为宽字节编码wcstombs将宽字节编码转换为多字节编码这两个函数,转换过程中受到系统编码类型的影响,需要通过设置来设定转换前和转换后的编码类型。通过函数setlocale进行系统编码的设置。linux下输入命名locale -a查看系统支持的编码_linux c++ gbk->utf8

IMP-00009: 导出文件异常结束-程序员宅基地

文章浏览阅读750次。今天准备从生产库向测试库进行数据导入,结果在imp导入的时候遇到“ IMP-00009:导出文件异常结束” 错误,google一下,发现可能有如下原因导致imp的数据太大,没有写buffer和commit两个数据库字符集不同从低版本exp的dmp文件,向高版本imp导出的dmp文件出错传输dmp文件时,文件损坏解决办法:imp时指定..._imp-00009导出文件异常结束

python程序员需要深入掌握的技能_Python用数据说明程序员需要掌握的技能-程序员宅基地

文章浏览阅读143次。当下是一个大数据的时代,各个行业都离不开数据的支持。因此,网络爬虫就应运而生。网络爬虫当下最为火热的是Python,Python开发爬虫相对简单,而且功能库相当完善,力压众多开发语言。本次教程我们爬取前程无忧的招聘信息来分析Python程序员需要掌握那些编程技术。首先在谷歌浏览器打开前程无忧的首页,按F12打开浏览器的开发者工具。浏览器开发者工具是用于捕捉网站的请求信息,通过分析请求信息可以了解请..._初级python程序员能力要求

Spring @Service生成bean名称的规则(当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致)_@service beanname-程序员宅基地

文章浏览阅读7.6k次,点赞2次,收藏6次。@Service标注的bean,类名:ABDemoService查看源码后发现,原来是经过一个特殊处理:当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致public class AnnotationBeanNameGenerator implements BeanNameGenerator { private static final String C..._@service beanname

随便推点

二叉树的各种创建方法_二叉树的建立-程序员宅基地

文章浏览阅读6.9w次,点赞73次,收藏463次。1.前序创建#include&lt;stdio.h&gt;#include&lt;string.h&gt;#include&lt;stdlib.h&gt;#include&lt;malloc.h&gt;#include&lt;iostream&gt;#include&lt;stack&gt;#include&lt;queue&gt;using namespace std;typed_二叉树的建立

解决asp.net导出excel时中文文件名乱码_asp.net utf8 导出中文字符乱码-程序员宅基地

文章浏览阅读7.1k次。在Asp.net上使用Excel导出功能,如果文件名出现中文,便会以乱码视之。 解决方法: fileName = HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8);_asp.net utf8 导出中文字符乱码

笔记-编译原理-实验一-词法分析器设计_对pl/0作以下修改扩充。增加单词-程序员宅基地

文章浏览阅读2.1k次,点赞4次,收藏23次。第一次实验 词法分析实验报告设计思想词法分析的主要任务是根据文法的词汇表以及对应约定的编码进行一定的识别,找出文件中所有的合法的单词,并给出一定的信息作为最后的结果,用于后续语法分析程序的使用;本实验针对 PL/0 语言 的文法、词汇表编写一个词法分析程序,对于每个单词根据词汇表输出: (单词种类, 单词的值) 二元对。词汇表:种别编码单词符号助记符0beginb..._对pl/0作以下修改扩充。增加单词

android adb shell 权限,android adb shell权限被拒绝-程序员宅基地

文章浏览阅读773次。我在使用adb.exe时遇到了麻烦.我想使用与bash相同的adb.exe shell提示符,所以我决定更改默认的bash二进制文件(当然二进制文件是交叉编译的,一切都很完美)更改bash二进制文件遵循以下顺序> adb remount> adb push bash / system / bin /> adb shell> cd / system / bin> chm..._adb shell mv 权限

投影仪-相机标定_相机-投影仪标定-程序员宅基地

文章浏览阅读6.8k次,点赞12次,收藏125次。1. 单目相机标定引言相机标定已经研究多年,标定的算法可以分为基于摄影测量的标定和自标定。其中,应用最为广泛的还是张正友标定法。这是一种简单灵活、高鲁棒性、低成本的相机标定算法。仅需要一台相机和一块平面标定板构建相机标定系统,在标定过程中,相机拍摄多个角度下(至少两个角度,推荐10~20个角度)的标定板图像(相机和标定板都可以移动),即可对相机的内外参数进行标定。下面介绍张氏标定法(以下也这么称呼)的原理。原理相机模型和单应矩阵相机标定,就是对相机的内外参数进行计算的过程,从而得到物体到图像的投影_相机-投影仪标定

Wayland架构、渲染、硬件支持-程序员宅基地

文章浏览阅读2.2k次。文章目录Wayland 架构Wayland 渲染Wayland的 硬件支持简 述: 翻译一篇关于和 wayland 有关的技术文章, 其英文标题为Wayland Architecture .Wayland 架构若是想要更好的理解 Wayland 架构及其与 X (X11 or X Window System) 结构;一种很好的方法是将事件从输入设备就开始跟踪, 查看期间所有的屏幕上出现的变化。这就是我们现在对 X 的理解。 内核是从一个输入设备中获取一个事件,并通过 evdev 输入_wayland

推荐文章

热门文章

相关标签