Java java지도 시간 Android Touch 이벤트 배포 과정에 대한 자세한 설명

Android Touch 이벤트 배포 과정에 대한 자세한 설명

Jan 16, 2017 pm 04:55 PM

이 글에서는 Android Touch 이벤트 배포 과정을 예시 형식으로 설명하고 있는데, 이는 Android 프로그래밍을 심층적으로 이해하고 익히는 데 매우 도움이 됩니다. 구체적인 분석은 다음과 같습니다.

먼저 간단한 예부터 시작하세요.

먼저 아래 예를 살펴보세요.

Android Touch事件分发过程详解

레이아웃 파일:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/container"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:layout_gravity="center"
  tools:context="com.example.touch_event.MainActivity"
  tools:ignore="MergeRootFrame" > 
  
  <Button
    android:id="@+id/my_button"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/hello_world" /> 
  
</FrameLayout>
로그인 후 복사

MainActivity 파일:

public class MainActivity extends Activity { 
  
  @Override
  protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.activity_main); 
  
    Button mBtn = (Button) findViewById(R.id.my_button); 
    mBtn.setOnTouchListener(new OnTouchListener() { 
  
      @Override
      public boolean onTouch(View v, MotionEvent event) { 
        Log.d("", "### onTouch : " + event.getAction()); 
        return false; 
      } 
    }); 
    mBtn.setOnClickListener(new OnClickListener() { 
  
      @Override
      public void onClick(View v) { 
        Log.d("", "### onClick : " + v); 
      } 
    }); 
  
  } 
  
  @Override
  public boolean dispatchTouchEvent(MotionEvent ev) { 
    Log.d("", "### activity dispatchTouchEvent"); 
    return super.dispatchTouchEvent(ev); 
  } 
}
로그인 후 복사

사용자가 버튼을 클릭하면 다음 로그가 출력됩니다.

08-31 03:03:56.116: D/(1560): ### activity dispatchTouchEvent 
08-31 03:03:56.116: D/(1560): ### onTouch : 0 
08-31 03:03:56.196: D/(1560): ### activity dispatchTouchEvent 
08-31 03:03:56.196: D/(1560): ### onTouch : 1 
08-31 03:03:56.196: D/(1560): ### onClick : android.widget.Button{52860d98 VFED..C. ...PH... 0,0-1080,144 #7f05003d app:id/my_button}
로그인 후 복사

dispatchTouchEvent 메소드가 Activity가 먼저 실행된 후 onTouch 메서드가 실행되고, 다음으로 dispatchTouchEvent --> onTouch가 실행되고, 마지막으로 버튼의 클릭 이벤트가 실행됩니다. 여기서 질문이 있을 수 있습니다. 왜 dispatchTouchEvent와 onTouch는 두 번 실행되지만 onClick은 한 번만 실행됩니까? 두 터치 이벤트의 동작이 다른 이유는 무엇입니까? 동작 0과 동작 1은 무엇을 나타냅니까?

onTouchEvent를 재정의한 친구는 일반적으로 ACTION_DOWN, ACTION_MOVE, ACTION_UP 등을 포함하여 이 메소드 본문에서 중앙 집중식 터치 유형 이벤트를 처리한다는 것을 알고 있습니다. 그러나 위의 예에서는 움직임이 없습니다. 간단하게 누르고 들어 올리세요. 따라서 우리의 터치 이벤트는 누르기와 떼기뿐이므로 2개의 터치 이벤트가 있고 동작은 각각 0과 1입니다. MotionEvent의 몇 가지 변수 정의를 살펴보겠습니다.

public final class MotionEvent extends InputEvent implements Parcelable { 
// 代码省略 
  public static final int ACTION_DOWN       = 0;  // 按下事件 
  public static final int ACTION_UP        = 1;  // 抬起事件  
  public static final int ACTION_MOVE       = 2;  // 手势移动事件 
  public static final int ACTION_CANCEL      = 3;  // 取消 
 // 代码省略 
}
로그인 후 복사

눌려진 이벤트가 0이고 들어 올려진 이벤트가 1이라는 것을 알 수 있는데, 이는 위에서 말한 내용도 확인시켜 줍니다.

다른 두 장면을 보면:

1. 버튼 바깥쪽 영역을 클릭하면 출력 Log는 다음과 같습니다.

08-31 03:04:45.408: D/(1560): ### activity dispatchTouchEvent08-31  
03:04:45.512: D/(1560): ### activity dispatchTouchEvent
로그인 후 복사

2. onTouch 함수를 실행하고 출력하면 로그는 다음과 같습니다.

08-31 03:06:04.764: D/(1612): ### activity dispatchTouchEvent 
08-31 03:06:04.764: D/(1612): ### onTouch : 0 
08-31 03:06:04.868: D/(1612): ### activity dispatchTouchEvent 
08-31 03:06:04.868: D/(1612): ### onTouch : 1
로그인 후 복사

위 두 장면은 왜 이럴까요? 계속해서 읽어보자.

Android Touch 이벤트 배포

그럼 전체 이벤트 배포 프로세스는 어떻게 되나요?

간단히 말하면 사용자가 휴대폰 화면을 터치하면 터치 메시지가 생성됩니다. 마지막으로 이 터치 메시지가 ViewRoot의 InputHandler로 전송됩니다(4.2의 소스 코드를 보면 이 ViewRootImpl로 변경됨) ViewRoot는 GUI 관리 시스템으로 ViewRoot의 정의에 따르면 View 유형이 아닌 Handler임을 알 수 있습니다. InputHandler는 KeyEvent 및 TouchEvent 유형 이벤트를 처리하는 데 사용되는 인터페이스 유형입니다. 소스 코드를 살펴보겠습니다.

public final class ViewRoot extends Handler implements ViewParent, 
    View.AttachInfo.Callbacks { 
      // 代码省略 
  private final InputHandler mInputHandler = new InputHandler() { 
    public void handleKey(KeyEvent event, Runnable finishedCallback) { 
      startInputEvent(finishedCallback); 
      dispatchKey(event, true); 
    } 
    public void handleMotion(MotionEvent event, Runnable finishedCallback) { 
      startInputEvent(finishedCallback); 
      dispatchMotion(event, true);   // 1、handle 触摸消息 
    } 
  }; 
    // 代码省略 
  // 2、分发触摸消息 
  private void dispatchMotion(MotionEvent event, boolean sendDone) { 
    int source = event.getSource(); 
    if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { 
      dispatchPointer(event, sendDone);   // 分发触摸消息 
    } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { 
      dispatchTrackball(event, sendDone); 
    } else { 
      // TODO 
      Log.v(TAG, "Dropping unsupported motion event (unimplemented): " + event); 
      if (sendDone) { 
        finishInputEvent(); 
      } 
    } 
  } 
  // 3、通过Handler投递消息 
  private void dispatchPointer(MotionEvent event, boolean sendDone) { 
    Message msg = obtainMessage(DISPATCH_POINTER); 
    msg.obj = event; 
    msg.arg1 = sendDone ? 1 : 0; 
    sendMessageAtTime(msg, event.getEventTime()); 
  } 
  @Override
  public void handleMessage(Message msg) {      // ViewRoot覆写handlerMessage来处理各种消息 
    switch (msg.what) { 
      // 代码省略 
    case DO_TRAVERSAL: 
      if (mProfile) { 
        Debug.startMethodTracing("ViewRoot"); 
      } 
  
      performTraversals(); 
  
      if (mProfile) { 
        Debug.stopMethodTracing(); 
        mProfile = false; 
      } 
      break; 
  
    case DISPATCH_POINTER: {    // 4、处理DISPATCH_POINTER类型的消息,即触摸屏幕的消息 
      MotionEvent event = (MotionEvent) msg.obj; 
      try { 
        deliverPointerEvent(event); // 5、处理触摸消息 
      } finally { 
        event.recycle(); 
        if (msg.arg1 != 0) { 
          finishInputEvent(); 
        } 
        if (LOCAL_LOGV || WATCH_POINTER) Log.i(TAG, "Done dispatching!"); 
      } 
    } break; 
    // 代码省略 
  } 
  // 6、真正的处理事件 
  private void deliverPointerEvent(MotionEvent event) { 
    if (mTranslator != null) { 
      mTranslator.translateEventInScreenToAppWindow(event); 
    } 
    boolean handled; 
    if (mView != null && mAdded) { 
      // enter touch mode on the down 
      boolean isDown = event.getAction() == MotionEvent.ACTION_DOWN; 
      if (isDown) { 
        ensureTouchMode(true);  // 如果是ACTION_DOWN事件则进入触摸模式,否则为按键模式。 
      } 
      if(Config.LOGV) { 
        captureMotionLog("captureDispatchPointer", event); 
      } 
      if (mCurScrollY != 0) { 
        event.offsetLocation(0, mCurScrollY);  // 物理坐标向逻辑坐标的转换 
      } 
      if (MEASURE_LATENCY) { 
        lt.sample("A Dispatching TouchEvents", System.nanoTime() - event.getEventTimeNano()); 
      } 
      // 7、分发事件,如果是窗口类型,则这里的mView对应的就是PhonwWindow中的DecorView,否则为根视图的ViewGroup。 
      handled = mView.dispatchTouchEvent(event); 
      // 代码省略   
    } 
  } 
  // 代码省略 
}
로그인 후 복사

코드 7의 mView가 DecorView인지 루트 뷰인지에 관계없이 안개 레이어 이후입니다. 비창문 인터페이스, 본질은 ViewGroup입니다. 즉, 터치 이벤트는 결국 루트 뷰 ViewGroup에 의해 배포됩니다! ! !
이 프로세스를 분석하기 위해 Activity를 예로 들어 보겠습니다. 표시된 Activity에는 이 창의 구현 클래스가 PhoneWindow라는 것을 알 수 있습니다. 휴대폰에서 볼 수 있듯이 이 DecorView는 Activity의 dispatchTouchEvent가 실제로 PhoneWindow의 dispatchTouchEvent를 호출하는 하위 클래스입니다. 소스 코드를 살펴보고 Activity의 dispatchTouchEvent 함수(

public boolean dispatchTouchEvent(MotionEvent ev) { 
   if (ev.getAction() == MotionEvent.ACTION_DOWN) { 
     onUserInteraction(); 
   } 
   if (getWindow().superDispatchTouchEvent(ev)) {   // 1、调用的是PhoneWindow的superDispatchTouchEvent(ev) 
  
     return true; 
   } 
   return onTouchEvent(ev); 
 } 
  
 public void onUserInteraction() { 
 }
로그인 후 복사

)를 입력하여 이벤트가 발생하는지 살펴보겠습니다. 프레스 이벤트인 경우 onUserInteraction() 함수가 시작됩니다. 이 함수는 비어 있으므로 지금은 무시하겠습니다. 계속해서 읽으면 터치 이벤트 배포가 getWindow().superDispatchTouchEvent(ev) 함수를 호출하는 것을 알 수 있습니다. getWindow()에서 얻은 인스턴스 유형은 Activity 클래스에서 다음 메서드를 사용하여 getWindow가 무엇인지 확인할 수 있습니다. ()를 얻습니다. 유형:

Log.d("", "### Activiti中getWindow()获取的类型是 : " + this.getWindow()) ;
로그인 후 복사

출력:

08-31 03:40:17.036: D/(1688): ### Activiti中getWindow()获取的类型是 : com.android.internal.policy.impl.PhoneWindow@5287fe38
로그인 후 복사

좋습니다. 더 이상 고민하지 말고 PhoneWindow의 superDispatchTouchEvent 함수를 계속 살펴보겠습니다.

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
  return mDecor.superDispatchTouchEvent(event);
}
로그인 후 복사

음, mDecor의 superDispatchTouchEvent(event) 함수가 호출됩니다. 이 mDecor는 위에서 언급한 DecorView 유형으로, 우리가 보는 활동의 모든 콘텐츠에 대한 최상위 ViewGroup입니다. 전체 ViewTree 루트 노드. 그 진술을 살펴보십시오.

// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
로그인 후 복사

DecorView

그럼 DecorView가 무엇인지 계속해서 살펴보겠습니다.

    private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
    /* package */int mDefaultOpacity = PixelFormat.OPAQUE;
  
    /** The feature ID of the panel, or -1 if this is the application&#39;s DecorView */
    private final int mFeatureId;
  
    private final Rect mDrawingBounds = new Rect();
  
    private final Rect mBackgroundPadding = new Rect();
  
    private final Rect mFramePadding = new Rect();
  
    private final Rect mFrameOffsets = new Rect();
  
    private boolean mChanging;
  
    private Drawable mMenuBackground;
    private boolean mWatchingForMenu;
    private int mDownY;
  
    public DecorView(Context context, int featureId) {
      super(context);
      mFeatureId = featureId;
    }
  
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
      final int keyCode = event.getKeyCode();
      // 代码省略
      return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
          : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
    }
  
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
      final Callback cb = getCallback();
      return cb != null && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super
          .dispatchTouchEvent(ev);
    }
  
    @Override
    public boolean dispatchTrackballEvent(MotionEvent ev) {
      final Callback cb = getCallback();
      return cb != null && mFeatureId < 0 ? cb.dispatchTrackballEvent(ev) : super
          .dispatchTrackballEvent(ev);
    }
  
    public boolean superDispatchKeyEvent(KeyEvent event) {
      return super.dispatchKeyEvent(event);
    }
  
    public boolean superDispatchTouchEvent(MotionEvent event) {
      return super.dispatchTouchEvent(event);
    }
  
    public boolean superDispatchTrackballEvent(MotionEvent event) {
      return super.dispatchTrackballEvent(event);
    }
  
    @Override
    public boolean onTouchEvent(MotionEvent event) {
      return onInterceptTouchEvent(event);
    }
// 代码省略
}
로그인 후 복사

보시다시피 DecorView는 FrameLayout에서 상속됩니다. 터치 이벤트(dispatchTouchEvent)의 배포 및 처리는 FrameLayout에서 처리되는 슈퍼 클래스로 전달됩니다. 구현 후 FrameLayout의 상위 클래스인 ViewGroup을 계속 추적합니다. DispatchTouchEvent의 구현을 살펴보았습니다. 그런 다음 먼저 ViewGroup(Android 2.3 소스 코드)이 이벤트를 배포하는 방법을 살펴보겠습니다.

ViewGroup의 터치 이벤트 배포

rreee


这个函数代码比较长,我们只看上文中标注的几个关键点。首先在代码1处可以看到一个条件判断,如果disallowIntercept和!onInterceptTouchEvent(ev)两者有一个为true,就会进入到这个条件判断中。disallowIntercept是指是否禁用掉事件拦截的功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。那么当第一个值为false的时候就会完全依赖第二个值来决定是否可以进入到条件判断的内部,第二个值是什么呢?onInterceptTouchEvent就是ViewGroup对事件进行拦截的一个函数,返回该函数返回false则表示不拦截事件,反之则表示拦截。第二个条件是是对onInterceptTouchEvent方法的返回值取反,也就是说如果我们在onInterceptTouchEvent方法中返回false,就会让第二个值为true,从而进入到条件判断的内部,如果我们在onInterceptTouchEvent方法中返回true,就会让第二个值的整体变为false,从而跳出了这个条件判断。例如我们需要实现ListView滑动删除某一项的功能,那么可以通过在onInterceptTouchEvent返回true,并且在onTouchEvent中实现相关的判断逻辑,从而实现该功能。

进入代码1内部的if后,有一个for循环,遍历了当前ViewGroup下的所有子child view,如果触摸该事件的坐标在某个child view的坐标范围内,那么该child view来处理这个触摸事件,即调用该child view的dispatchTouchEvent。如果该child view是ViewGroup类型,那么继续执行上面的判断,并且遍历子view;如果该child view不是ViewGroup类型,那么直接调用的是View中的dispatchTouchEvent方法,除非这个child view的类型覆写了该方法。我们看看View中的dispatchTouchEvent函数:

View的Touch事件分发

/**
 * Pass the touch screen motion event down to the target view, or this
 * view if it is the target.
 *
 * @param event The motion event to be dispatched.
 * @return True if the event was handled by the view, false otherwise.
 */
public boolean dispatchTouchEvent(MotionEvent event) {
  if (!onFilterTouchEventForSecurity(event)) {
    return false;
  }
  
  if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
      mOnTouchListener.onTouch(this, event)) {
    return true;
  }
  return onTouchEvent(event);
}
로그인 후 복사


该函数中,首先判断该事件是否符合安全策略,然后判断该view是否是enable的 ,以及是否设置了Touch Listener,mOnTouchListener即我们通过setOnTouchListener设置的。

/**
 * Register a callback to be invoked when a touch event is sent to this view.
 * @param l the touch listener to attach to this view
 */
public void setOnTouchListener(OnTouchListener l) {
  mOnTouchListener = l;
}
로그인 후 복사

如果mOnTouchListener.onTouch(this, event)返回false则继续执行onTouchEvent(event);如果mOnTouchListener.onTouch(this, event)返回true,则表示该事件被消费了,不再传递,因此也不会执行onTouchEvent(event)。这也验证了我们上文中留下的场景2,当onTouch函数返回true时,点击按钮,但我们的点击事件没有执行。那么我们还是先来看看onTouchEvent(event)函数到底做了什么吧。

/**
 * Implement this method to handle touch screen motion events.
 *
 * @param event The motion event.
 * @return True if the event was handled, false otherwise.
 */
public boolean onTouchEvent(MotionEvent event) {
  final int viewFlags = mViewFlags;
  
  if ((viewFlags & ENABLED_MASK) == DISABLED)    // 1、判断该view是否enable
    // A disabled view that is clickable still consumes the touch
    // events, it just doesn&#39;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)) // 2、是否是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&#39;t have it already and we should in
          // touch mode.
          boolean focusTaken = false;
          if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
            focusTaken = requestFocus();    // 获取焦点
          }
  
          if (!mHasPerformedLongPress) {
            // This is a tap, so remove the longpress check
            removeLongPressCallback();
  
            // Only perform take click actions if we were in the pressed state
            if (!focusTaken) {
              // Use a Runnable and post this rather than calling
              // performClick directly. This lets other visual state
              // of the view update before click actions start.
              if (mPerformClick == null) {
                mPerformClick = new PerformClick();
              }
              if (!post(mPerformClick))   // post
                performClick();     // 3、点击事件处理
              }
            }
          }
  
          if (mUnsetPressedState == null) {
            mUnsetPressedState = new UnsetPressedState();
          }
  
          if (prepressed) {
            mPrivateFlags |= PRESSED;
            refreshDrawableState();
            postDelayed(mUnsetPressedState,
                ViewConfiguration.getPressedStateDuration());
          } else if (!post(mUnsetPressedState)) {
            // If the post failed, unpress right now
            mUnsetPressedState.run();
          }
          removeTapCallback();
        }
        break;
  
      case MotionEvent.ACTION_DOWN:
        if (mPendingCheckForTap == null) {
          mPendingCheckForTap = new CheckForTap();
        }
        mPrivateFlags |= PREPRESSED;
        mHasPerformedLongPress = false;
        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
        break;
  
      case MotionEvent.ACTION_CANCEL:
        mPrivateFlags &= ~PRESSED;
        refreshDrawableState();
        removeTapCallback();
        break;
  
      case MotionEvent.ACTION_MOVE:
        final int x = (int) event.getX();
        final int y = (int) event.getY();
  
        // Be lenient about moving outside of buttons
        int slop = mTouchSlop;
        if ((x < 0 - slop) || (x >= getWidth() + slop) ||
            (y < 0 - slop) || (y >= getHeight() + slop)) {
          // Outside button
          removeTapCallback();
          if ((mPrivateFlags & PRESSED) != 0) {
            // Remove any future long press/tap checks
            removeLongPressCallback();
  
            // Need to switch from pressed to not pressed
            mPrivateFlags &= ~PRESSED;
            refreshDrawableState();
          }
        }
        break;
    }
    return true;
  }
  
  return false;
}
로그인 후 복사

我们看到,在onTouchEvent函数中就是对ACTION_UP、ACTION_DOWN、ACTION_MOVE等几个事件进行处理,而最重要的就是UP事件了,因为这个里面包含了对用户点击事件的处理,或者是说对于用户而言相对重要一点,因此放在了第一个case中。在ACTION_UP事件中会判断该view是否enable、是否clickable、是否获取到了焦点,然后我们看到会通过post方法将一个PerformClick对象投递给UI线程,如果投递失败则直接调用performClick函数执行点击事件。

/**
 * Causes the Runnable to be added to the message queue.
 * The runnable will be run on the user interface thread.
 *
 * @param action The Runnable that will be executed.
 *
 * @return Returns true if the Runnable was successfully placed in to the
 *     message queue. Returns false on failure, usually because the
 *     looper processing the message queue is exiting.
 */
public boolean post(Runnable action) {
  Handler handler;
  if (mAttachInfo != null) {
    handler = mAttachInfo.mHandler;
  } else {
    // Assume that post will succeed later
    ViewRoot.getRunQueue().post(action);
    return true;
  }
  
  return handler.post(action);
}
로그인 후 복사

我们看看PerformClick类吧。

private final class PerformClick implements Runnable {
  public void run() {
    performClick();
  }
}
로그인 후 복사

可以看到,其内部就是包装了View类中的performClick()方法。再看performClick()方法:

/**
 * 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&#39;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;
 }
로그인 후 복사

  

代码很简单,主要就是调用了mOnClickListener.onClick(this);方法,即执行用户通过setOnClickListener设置进来的点击事件处理Listener。
 
总结

用户触摸屏幕产生一个触摸消息,系统底层将该消息转发给ViewRoot ( ViewRootImpl ),ViewRoot产生一个DISPATCHE_POINTER的消息,并且在handleMessage中处理该消息,最终会通过deliverPointerEvent(MotionEvent event)来处理该消息。在该函数中会调用mView.dispatchTouchEvent(event)来分发消息,该mView是一个ViewGroup类型,因此是ViewGroup的dispatchTouchEvent(event),在该函数中会遍历所有的child view,找到该事件的触发的左边与每个child view的坐标进行对比,如果触摸的坐标在该child view的范围内,则由该child view进行处理。如果该child view是ViewGroup类型,则继续上一步的查找过程;否则执行View中的dispatchTouchEvent(event)函数。在View的dispatchTouchEvent(event)中首先判断该控件是否enale以及mOnTouchListent是否为空,如果mOnTouchListener不为空则执行mOnTouchListener.onTouch(event)方法,如果该方法返回false则再执行View中的onTouchEvent(event)方法,并且在该方法中执行mOnClickListener.onClick(this, event) ;方法; 如果mOnTouchListener.onTouch(event)返回true则不会执行onTouchEvent方法,因此点击事件也不会被执行。

相信本文所述对大家进一步深入掌握Android程序设计有一定的借鉴价值。

更多Android Touch事件分发过程详解相关文章请关注PHP中文网!

본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

2025 년 상위 4 개의 JavaScript 프레임 워크 : React, Angular, Vue, Svelte 2025 년 상위 4 개의 JavaScript 프레임 워크 : React, Angular, Vue, Svelte Mar 07, 2025 pm 06:09 PM

이 기사는 2025 년에 상위 4 개의 JavaScript 프레임 워크 (React, Angular, Vue, Svelte)를 분석하여 성능, 확장 성 및 향후 전망을 비교합니다. 강력한 공동체와 생태계로 인해 모두 지배적이지만 상대적으로 대중적으로

카페인 또는 구아바 캐시와 같은 라이브러리를 사용하여 자바 애플리케이션에서 다단계 캐싱을 구현하려면 어떻게해야합니까? 카페인 또는 구아바 캐시와 같은 라이브러리를 사용하여 자바 애플리케이션에서 다단계 캐싱을 구현하려면 어떻게해야합니까? Mar 17, 2025 pm 05:44 PM

이 기사는 카페인 및 구아바 캐시를 사용하여 자바에서 다단계 캐싱을 구현하여 응용 프로그램 성능을 향상시키는 것에 대해 설명합니다. 구성 및 퇴거 정책 관리 Best Pra와 함께 설정, 통합 및 성능 이점을 다룹니다.

Node.js 20 : 주요 성능 향상 및 새로운 기능 Node.js 20 : 주요 성능 향상 및 새로운 기능 Mar 07, 2025 pm 06:12 PM

Node.js 20은 V8 엔진 개선, 특히 더 빠른 쓰레기 수집 및 I/O를 통해 성능을 크게 향상시킵니다. 새로운 기능에는 더 나은 webAssembly 지원 및 정제 디버깅 도구, 개발자 생산성 및 응용 속도 향상이 포함됩니다.

Java의 클래스로드 메커니즘은 다른 클래스 로더 및 대표 모델을 포함하여 어떻게 작동합니까? Java의 클래스로드 메커니즘은 다른 클래스 로더 및 대표 모델을 포함하여 어떻게 작동합니까? Mar 17, 2025 pm 05:35 PM

Java의 클래스 로딩에는 부트 스트랩, 확장 및 응용 프로그램 클래스 로더가있는 계층 적 시스템을 사용하여 클래스로드, 링크 및 초기화 클래스가 포함됩니다. 학부모 위임 모델은 핵심 클래스가 먼저로드되어 사용자 정의 클래스 LOA에 영향을 미치도록합니다.

Spring Boot Snakeyaml 2.0 CVE-2022-1471 문제 고정 Spring Boot Snakeyaml 2.0 CVE-2022-1471 문제 고정 Mar 07, 2025 pm 05:52 PM

이 기사는 원격 코드 실행을 허용하는 중요한 결함 인 Snakeyaml의 CVE-2022-1471 취약점을 다룹니다. Snakeyaml 1.33 이상으로 Spring Boot 응용 프로그램을 업그레이드하는 방법에 대해 자세히 설명합니다.

빙산 : 데이터 호수 테이블의 미래 빙산 : 데이터 호수 테이블의 미래 Mar 07, 2025 pm 06:31 PM

대규모 분석 데이터 세트를위한 오픈 테이블 형식 인 Iceberg는 데이터 호수 성능 및 확장 성을 향상시킵니다. 내부 메타 데이터 관리를 통한 Parquet/Orc의 한계를 해결하여 효율적인 스키마 진화, 시간 여행, 동시 W를 가능하게합니다.

Java에서 기능 프로그래밍 기술을 어떻게 구현할 수 있습니까? Java에서 기능 프로그래밍 기술을 어떻게 구현할 수 있습니까? Mar 11, 2025 pm 05:51 PM

이 기사는 Lambda 표현식, 스트림 API, 메소드 참조 및 선택 사항을 사용하여 기능 프로그래밍을 Java에 통합합니다. 간결함과 불변성을 통한 개선 된 코드 가독성 및 유지 관리 가능성과 같은 이점을 강조합니다.

오이의 단계간에 데이터를 공유하는 방법 오이의 단계간에 데이터를 공유하는 방법 Mar 07, 2025 pm 05:55 PM

이 기사는 오이 단계간에 데이터를 공유하는 방법, 시나리오 컨텍스트, 글로벌 변수, 인수 통과 및 데이터 구조를 비교합니다. 간결한 컨텍스트 사용, 설명을 포함하여 유지 관리에 대한 모범 사례를 강조합니다.

See all articles