Android Touchイベント配信応答メカニズム

16704 ワード

概要
Androidでは、ポイント押し、ロング押し、ドラッグ、スライドなどが含まれており、これらのイベントがあればAndroidがユーザーのさまざまな操作に応答することができます.しかし、結局、これらのすべての事件は以下の3つの部分に基づいています.
  • ACTION_DOWN(押下)
  • ACTION_MOVE(移動)
  • ACTION_UP(リフト)
  • すべての操作イベントは、まずACTION_を実行する必要があります.DOWN(押下)操作は、その後すべての操作が押下操作を前提としており、押下操作が完了すると、次はACTION_MOVEそしてACTION_UP、またはダイレクトACTION_UP.
    すべての操作イベントの実行順序は、ACTION_である必要があります.DOWN -> ACTION_MOVE -> ACTION_UP.(ACTION_MOVEが含まれているか否かは、ユーザジェスチャーに移動操作が含まれているか否かによる)
    本文はAndroid 2に基づく.2.3バージョンの分析(主に:2.2.3バージョンのコードがはっきりしていて簡単で、原理が通用するため)
    Activityイベント転送
    Touchイベントの発生はハードウェアとLinuxコアの部分に関連しており、私はハードウェアグループにいますが、私もこの内容を深く研究したくありません.現在、Touchイベントが発生した後、最初に応答したのはActivityのdispatchTouchEventメソッドであることを知る必要があります.
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

    ここで、onUserInteraction()は空のメソッドであり、開発者は自分のニーズに応じてoverrideというメソッドをoverrideすることができ、このメソッドはTouchイベントの周期で最初に呼び出されるに違いない.
    次にgetWindow()を解析する.superDispatchTouchEvent(ev)は、最終的にはViewGroupのdispatchTouchEventメソッドを呼び出します.
    まとめ:
    以上の解析から,少なくとも1つのTouchイベントはまずActivityのdispatchTouchEvent法で処理され,次いでViewGroupのdispatchTouchEvent法に割り当てられることが分かった.
    ViewGroup応答Touchイベント
    ポイントはdispatchTouchEvent()メソッドを分析することです.2.2.3バージョンのこのメソッドは簡単です.中国語の注釈を追加するソースコードは以下の通りです.
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        final float xf = ev.getX();
        final float yf = ev.getY();
        // mScrollX        x      
        // mScrollY        y      
        final float scrolledXFloat = xf + mScrollX;
        final float scrolledYFloat = yf + mScrollY;
        final Rect frame = mTempRect;
    
        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    
        //      ACTION_DOWN  
        if (action == MotionEvent.ACTION_DOWN) {
            //     ACTION_DOWN ,   mMotionTarget  null
            //   ,ACTION_DOWN      Touch  
            if (mMotionTarget != null) {
                mMotionTarget = null;
            }
    
            //     ViewGroup   touch      
            if (disallowIntercept || !onInterceptTouchEvent(ev)) {
                ev.setAction(MotionEvent.ACTION_DOWN);
                //  ViewGroup  View        touch     
                final int scrolledXInt = (int) scrolledXFloat;
                final int scrolledYInt = (int) scrolledYFloat;
                final View[] children = mChildren;
                final int count = mChildrenCount;
                for (int i = count - 1; i >= 0; i --) {
                    final View child = children[i];
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                        //               
                        child.getHitRect(frame);
                        //      Touch      child      
                        if (frame.contains(scrolledXInt, scrolledYInt)) {
                            //   Touch     View     
                            final float xc = scrolledXFloat - child.mLeft;
                            final float yc = scrolledYFloat - child.mTop;
                            ev.setLocation(xc, yc);
                            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
                            //   child view dispatchTouchEvent   Touch      
                            if (child.dispatchTouchEvent(ev)) {
                                //   touch       View
                                mMotionTarget = child;
                                return true;
                            }
                        }
                    }
                }
            }
        }
    
        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL);
        if (isUpOrCancel) {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }
    
        //     ACTION_DOWN   ,           touch    View
        final View target = mMotionTarget;
    
        //         ViewGroup     ,   Touch      ViewGroup      
        if (target == null) {
            ev.setLocation(xf, yf);
            // ViewGroup    View,           View dispatchTouchEvent  
            return super.dispatchTouchEvent(ev);
        }
    
        if (!disallowIntercept && onInterceptTouchEvent(ev)) {
            final float xc = scrolledXFloat - (float) target.mLeft;
            final float yc = scrolledYFloat - (float) target.mTop;
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
            ev.setAction(MotionEvent.ACTION_CANCEL);
            ev.setLocation(xc, yc);
            //    View   Touch  
            if (!target.dispatchTouchEvent(ev)) {
            }
            mMotionTarget = null;
            return true;
        }
    
        if (isUpOrCancel) {
            mMotionTarget = null;
        }
    
        final float xc = scrolledXFloat - (float) target.mLeft;
        final float yc = scrolledYFloat - (float) target.mTop;
        ev.setLocation(xc, yc);
        if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
            ev.setAction(MotionEvent.ACTION_CANCEL);
            target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
            mMotionTarget = null;
        }
    
        return target.dispatchTouchEvent(ev);
    }
    

    ViewGroupのデフォルトのonInterceptTouchEventの戻り値はfalseであるため、ViewGroupはTouchEventをブロックしません.
    コードから、ViewGroup自体がTouchイベントをブロックしない場合、Touchイベントは最終的に対応するサブViewに渡されて処理されることがわかります.
    View応答Touchイベント
    ViewGroupがTouchイベントに応答するソースコード解析から,ViewGroupはTouchイベントをブロックしない前提で,TouchイベントをChild Viewに配布して実現した.ここのChild ViewがButtonであると仮定すると,TouchイベントのViewでの伝達規則を検討する.
    Buttonのソースコードを確認すると、dispatchTouchEventメソッドが実装されていません.しかし、android.widget.Buttonはandroidから受け継いだwidget.TextView->android.view.View,最終的にはdispatchTouchEvent法がViewクラスで実現されることを見出した.ソースコードは次のとおりです.
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
            mOnTouchListener.onTouch(this, event)) {
            return true;
        }
        return onTouchEvent(event);
    }

    Android2.2.3リリースでは、superDispatchTouchEventメソッドの実装は非常に簡単ですが、問題を説明するのに十分です.次に,if文のコード実装を1つずつ解析する.
    第一に、mOnTouchListenerはどのように値を割り当てますか?
    解答:Viewから次の方法を見つけることができます.
    public void setOnTouchListener(OnTouchListener l) {
        mOnTouchListener = l;
    }

    この方法は,開発者がコントロールにtouchイベント応答オブジェクトを登録すると,mOnTouchListenerが付与されることをよく知っているはずである.
    2つ目はonTouch()はコールバック方法ですか?
    解答:計算しなければなりません.コントロールにonTouchイベントオブジェクトを登録するとき、onTouchメソッドを書き換えます.ここでは、私たちの登録をトリガーするouTouchメソッドです.さらに,書き換えたonTouchメソッドでtrueを返すと,そのTouchイベントは既に処理済みであり,そうでなければViewのonTouchEvent()メソッドを呼び出し続けることが分かる.
    次に、ViewのonTouchEvent()メソッドを分析し続けます.ソースコードは次のとおりです.
    public boolean onTouchEvent(MotionEvent event) {
        final int viewFlags = mViewFlags;
    
        //  disable View   Touch  
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            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) {
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }
    
                    if (!mHasPerformedLongPress) {
                        removeLongPressCallback();
    
                        if (!focusTaken) {
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                        }
                    }
    
                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }
    
                    if (prepressed) {
                        mPrivateFlags |= PRESSED;
                        refreshDrawableState();
                        postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        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:
                break;
    
                case MotionEvent.ACTION_MOVE:
                final int x = (int) event.getX();
                final int y = (int) event.getY();
    
                //       
                int slop = mTouchSlop;
                if ((x < 0 - slop) || (x >= getWidth() + slop) ||
                    (y < 0 - slop) || (y >= getHeight() + slop)) {
                    removeTapCallback();
    
                }
                break;
            }
        }
    }

    onTouchEvent()メソッドは複雑に見えます.ここでは、特にACTION_に注目します.UPの処理.ただし、ACTION_UPの処理は最終的にPerformClickクラスのインスタンス化に呼び出される.次に、クラスがどのようにインスタンス化されているかを見てみましょう.
    private final class PerformClick implements Runnable {
        public void run() {
            performClick();
        }
    }
    
    public boolean performClick() {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    
        if (mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            mOnClickListener.onClick(this);
            return true;
        }
        return false;
    }

    以前、mOnTouchListenerはonTouchイベントで付与されていると分析しましたが、そのmOnClickListenerはonClickイベントで付与されているに違いありませんので、ここではACTION_UPイベントではonClickメソッドをコールバックした.
    “`
    参考資料
  • Android Deeper-Touchイベント配信応答メカニズム
  • Androidイベント配信メカニズムを完全に解析し、ソースコードの観点から
  • を徹底的に理解します.
  • Androidイベント配信メカニズム完全解析、ソースコードの観点から徹底的に理解(下)