view 클릭 이벤트 처리 과정

16142 단어

view 프로세스


My TextView 계승 TextView를 사용자 정의하고 dispatch Touch Event on Touch Event 인쇄 로그

코드는 다음과 같습니다.

public class MyTextView extends TextView {

    public MyTextView(Context context) {
        super(context);
    }

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

    public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
//        Log.e("myTextView", "myTextView dispatchTouchEvent");

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.e("myTextView", "myTextView dispatchTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e("myTextView", "myTextView dispatchTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e("myTextView", "myTextView dispatchTouchEvent ACTION_UP");
                break;
        }

        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.e("myTextView", "myTextView onTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e("myTextView", "myTextView onTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e("myTextView", "myTextView onTouchEvent ACTION_UP");
                break;
        }

        return super.onTouchEvent(event);
    }
}

마지막으로 Activity 코드는 다음과 같습니다.

public class MainActivity extends AppCompatActivity {

    private MyTextView myTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myTextView = (MyTextView) findViewById(R.id.tv_show);
        myTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.e("activity","activity onclick");
            }
        });
        myTextView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {

                switch (motionEvent.getAction()){

                    case MotionEvent.ACTION_DOWN:
                        Log.e("activity","activity onTouch ACTION_DOWN");
                        break;

                    case MotionEvent.ACTION_MOVE:
                        Log.e("activity","activity onTouch ACTION_MOVE");
                        break;

                    case MotionEvent.ACTION_UP:
                        Log.e("activity","activity onTouch ACTION_UP");
                        break;
                }

                return false;
            }
        });
    }

}

주의 사항


뷰 그룹이 포함되어 있지 않으므로 절차가 간단합니다.view는 단독 요소로 하위 요소가 없기 때문에 이벤트를 아래로 전달할 수 없기 때문에 이벤트를 스스로 처리할 수 있습니다.
MainActivity에서 우리는 My TextView에 On Touch Listener, set On Click Listener라는 감청을 설정해 주었다. 자, View 사건과 관련된 곳은 보통 이 세 군데다. 하나는 온터치 이벤트, 하나는 디스패치 터치 이벤트, 하나는 set On Touch Listener와 set On Click Listener이다.

다음은 를 실행하고 버튼을 클릭하여 로그 출력을 확인합니다.


내가 일부러 눌렀을 때 문질러 놓았는데, 그렇지 않았다면 MOVE를 건드리지 않았을 것이다. 손이 떨리면 MOVE의 일지를 한 무더기 출력할 수 있었다
       
25372-25372/com.lu.viewdistributionevent E/myTextView: myTextView dispatchTouchEvent ACTION_DOWN
25372-25372/com.lu.viewdistributionevent E/activity: activity onTouch ACTION_DOWN
25372-25372/com.lu.viewdistributionevent E/myTextView: myTextView onTouchEvent ACTION_DOWN
25372-25372/com.lu.viewdistributionevent E/myTextView: myTextView dispatchTouchEvent ACTION_UP
25372-25372/com.lu.viewdistributionevent E/activity: activity onTouch ACTION_UP
25372-25372/com.lu.viewdistributionevent E/myTextView: myTextView onTouchEvent ACTION_UP
25372-25372/com.lu.viewdistributionevent E/activity: activity onclick

       

25372-25372/com.lu.viewdistributionevent E/myTextView: myTextView dispatchTouchEvent ACTION_DOWN
25372-25372/com.lu.viewdistributionevent E/activity: activity onTouch ACTION_DOWN
25372-25372/com.lu.viewdistributionevent E/myTextView: myTextView onTouchEvent ACTION_DOWN
25372-25372/com.lu.viewdistributionevent E/myTextView: myTextView dispatchTouchEvent ACTION_MOVE
25372-25372/com.lu.viewdistributionevent E/activity: activity onTouch ACTION_MOVE
25372-25372/com.lu.viewdistributionevent E/myTextView: myTextView onTouchEvent ACTION_MOVE
25372-25372/com.lu.viewdistributionevent E/myTextView: myTextView dispatchTouchEvent ACTION_UP
25372-25372/com.lu.viewdistributionevent E/activity: activity onTouch ACTION_UP
25372-25372/com.lu.viewdistributionevent E/myTextView: myTextView onTouchEvent ACTION_UP
25372-25372/com.lu.viewdistributionevent E/activity: activity onclick

보시다시피 DOWN, MOVE, UP은 다음 순서대로 수행됩니다.
  • dispatchTouchEvent
  • setOnTouchListener의onTouch
  • onTouchEvent
  • setOnClickListener의 onClick
  • 다음은 로그의 발자취를 따라 원본 탐색을 시작합니다

  • 디스패치 터치 이벤트에 먼저 들어가는 방법
  • /**
     * 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 the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }
    
        boolean result = false;
    
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }
    
        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }
    
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;//              listener,  clickListener,touchListener 
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED// view        
                    && li.mOnTouchListener.onTouch(this, event)) {//   TouchListener.onTouch  ,           
                result = true;
            }
    
            if (!result && onTouchEvent(event)) {//   onTouchEvent,     onClick/onLongClick  
                result = true;
            }
        }
    
        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
    
        // Clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
        // of the gesture.
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }
    
        return result;
    }
    

    Touch Lister 중의 onTouch 방법은true로 되돌아갑니다 제가 의도적으로 눌렀을 때 문질렀습니다. 그렇지 않으면 MOVE를 터치하지 않습니다. 손이 떨리면 MOVE 로그를 출력할 수 있습니다.
    17776-17776/com.lu.viewdistributionevent E/myTextView: myTextView dispatchTouchEvent ACTION_DOWN
    17776-17776/com.lu.viewdistributionevent E/activity: activity onTouch ACTION_DOWN
    17776-17776/com.lu.viewdistributionevent E/myTextView: myTextView dispatchTouchEvent ACTION_MOVE
    17776-17776/com.lu.viewdistributionevent E/activity: activity onTouch ACTION_MOVE
    17776-17776/com.lu.viewdistributionevent E/myTextView: myTextView dispatchTouchEvent ACTION_MOVE
    17776-17776/com.lu.viewdistributionevent E/activity: activity onTouch ACTION_MOVE
    17776-17776/com.lu.viewdistributionevent E/myTextView: myTextView dispatchTouchEvent ACTION_UP
    17776-17776/com.lu.viewdistributionevent E/activity: activity onTouch ACTION_UP
    

    원본에서 알 수 있듯이 먼저 OnTouch Listener를 설정했는지 판단하고 OnTouch Lister 중의 onTouch 방법이true로 되돌아오면 onTouch Event가 호출되지 않는다는 결론을 내렸다. OnTouch Listener의 우선순위가 onTouch Event보다 높다는 결론을 내렸다.이렇게 하면 외부에서 클릭 사건을 처리하는 데 편리하다는 장점이 있다.동시에 set On Click Listener의 onClick도 호출되지 않은 것을 발견할 수 있습니다.왜 On Touch Lister의 On Touch 방법을 설정해서true로 되돌려 주었는지, set On Click Listener의 On Click도 호출되지 않았습니다.
  • 이어서 온터치 이벤트의 실현을 분석한다.먼저 View가 사용 불가능한 상태에서 이벤트 처리 과정을 클릭하는 것을 보십시오.아래 코드를 보면 사용할 수 없는 상태의 View는 클릭 이벤트를 소모합니다. 비록 사용할 수 없을 것 같지만.
  • /**
     * Implement this method to handle touch screen motion events.
     * 

    * If this method is used to detect click actions, it is recommended that * the actions be performed by implementing and calling * {@link #performClick()}. This will ensure consistent system behavior, * including: *

      *
    • obeying click sound preferences *
    • dispatching OnClickListener calls *
    • handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when * accessibility features are enabled *
    * * @param event The motion event. * @return True if the event was handled, false otherwise. */ public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; final int action = event.getAction(); // View , 。 if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // 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) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch (action) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_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. setPressed(true, x, y); } if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { // 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(); } mIgnoreNextUpEvent = false; 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 |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPendingCheckForTap.x = event.getX(); mPendingCheckForTap.y = event.getY(); postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the feedback right away setPressed(true, x, y); checkForLongClick(0, x, y); } break; case MotionEvent.ACTION_CANCEL: setPressed(false); removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; break; case MotionEvent.ACTION_MOVE: drawableHotspotChanged(x, y); // Be lenient about moving outside of buttons if (!pointInView(x, y, mTouchSlop)) { // Outside button removeTapCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); setPressed(false); } } break; } return true; } return false; }

    그래서 결론을 얻었다.


    View의 LONGCLICKABLE 속성은 기본값false이고 CLICKABLE 속성이false인지 여부는 구체적인view와 관련이 있습니다.정확히 말하면 클릭 가능한view의 CLICKABLE는true이고 클릭 가능한view의 CLICKABLE는false이다.예를 들어 Button은 클릭할 수 있고 TextView는 클릭할 수 없습니다.setClickable 및 setLongClickable을 사용하여 View의 CLICKABLE와 LONG 를 각각 변경할 수 있습니다.CLICKABLE의 속성입니다.또한 setOnClickListener는 자동으로 CLICKABLE를true로 설정하고 setOnLongClickListener는 자동으로 LONGCLICKABLE를 true로 설정합니다.

    좋은 웹페이지 즐겨찾기