『Android開発芸術探索』ノート4--Viewイベント配信とスライド衝突

10711 ワード

  • 思惟導図
  • Viewイベント配信メカニズム
  • クリックイベント伝達規則
  • イベント配信のソースコード解析
  • Viewのスライド衝突
  • スライド衝突の解決方法
  • 参照記事
  • 思考ガイド
    Viewイベント配信メカニズム
    イベント転送ルールをクリック
    クリックイベントのイベント配信とは、MotionEventイベントの配信プロセスを、あるViewに渡すことである.
    イベント伝達には、常に3つの方法があります.
  • dispatchTouchEvent():イベントの配信を行うイベントがViewに伝達する場合、このメソッドは必ず呼び出され、戻り値は現在のViewのonTouchEvent()と下位ViewのdispatchTouchEvent()の影響を受ける.現在のイベントを消費するか否かを示す.
  • onInterceptTouchEvent():イベントをブロックするかどうかを判断し、現在のViewがイベントをブロックする場合、同じイベントシーケンスでこのメソッドは再び呼び出されず、結果は現在のイベントをブロックするかどうかを示す.
  • onTouchEvent():dispatchTouchEventで呼び出す.クリックイベントの処理に用いる、結果を返す現在のイベントを消費するか否かを示し、消費しなければ、同じイベントシーケンスにおいて、現在のViewは再びイベントを受信ことができない.

  • ソースコードの様々な判断を除いて、最も核心的なコードだけを残しておくと、次のようになります.
    public boolean dispatchTouchEvent(MotionEvent event) {
    
       boolean consume = false;   //             
    
       if (onInterceptTouchEvent(event)){
    
            //         
    
           consume = onTouchEvent(event);
    
       }else{
    
            //       view dispatchTouchEvent() 
    
           consume = child.dispatchTouchEvent(event);
    
       }
    
       return consume;
    
    }
    

    OnTouchListener()、onTouchEvent()、OnClickListener()の優先度
    一般的なイベントの配布について述べた.このビューにOnTouchListenerOnClickListenerが同時に追加するとする.この場合の優先順位は次のとおりです.
    OnTouchListener –> onTouchEvent –> OnClickListener
    一方、onTouchEvent()が最終的に呼び出すことができるかどうかは、OnTouchListener()のonTouch()の戻り値が設定されていることに依存し、onTouch()が返す結果がfalseであれば、onTouchEvent()が呼び出される.trueが戻るとonTouchEvent()は呼び出されない.
    最後に呼び出すOnClickListener()の方法はonTouchEvent()で呼び出される.したがって、onTouchEvent()の方法が実行すると、対応する追加のonClickLisener()が呼び出される.OnTouchListener() onTouch() trueであればonTouchEvent() , OnClickListener である.
    1つのイベントの伝達プロセスは次のとおりです.
    Activity -> Window ->decor view->ViewGroup-> View
    イベントがずっとブロックされず、最下層のViewに伝達する、最下層のViewのonTouchEvent()もfalseに戻って消費しないと、イベントは上位のonTouchEvent()に伝達され、falseに戻ると順次伝達される.
    イベント・メカニズムのルール:
  • のイベントシーケンスとは、押下から持ち上げるまでの間に発生する一連のイベントを指す.
  • のデフォルトのイベントシーケンスは、1つのViewによってのみブロックされ、消費することができる.(例外:onTouchEventで他のView.に強制的に渡す非常規を採用)
  • ビューがブロックを決定すると、このイベントシーケンスはそれ自身でしか処理できない.onInterceptTouchEvent()
  • に呼び出されません
  • ビューがACTION_DOWNイベント時にtrueを返さない場合、同じイベントシーケンスは処理されない.また、イベントは親要素のonTouchEvent()に再伝達され、メソッドが再呼び出される.
  • ビューがACTION_DOWN以外のイベントを消費する場合、このクリックイベントは消失し、親要素のonTouchEvent()は呼び出されず、現在ビューは後続のイベントを受信し続けることができ、最終的にこれらの消失イベントはactivity処理に伝達する.
  • ViewGroupデフォルトはイベントをブロックせず、ソースコードのViewGroupのonInterceptTouchEvent()デフォルトはfalse
  • を返します.
  • ViewonInterceptTouchEvent()ではなく、サブViewがないため、onTouchEvent()
  • を直接呼び出す.
  • ViewonTouchEventは、デフォルトでは消費イベントがtrueに戻る.クリックできない限り(clickablelongClickableがfalseである必要がある).ViewのlongClickableのデフォルトはfalseです.clickableは、Buttonのデフォルトがtrue、TextViewのデフォルトがfalseのようなコントロールを区別する必要がある.
  • Viewenableの属性は、1つのViewがonTouchEventの状態であるもdisableのデフォルトの戻り値に影響しない.clickableまたはlongClickableがtrueである限り.onTouchEvent()はtrueに戻ります
  • onClickが発生する前提は、現在のViewがクリック可能であり、downおよびupイベントを受信することである.
  • イベントが伝達する過程は外向内である.requestDisallowInterceptTouchEvent()により、親要素のイベント配信プロセスに子要素に関与することができるが、ACTION_DOWNのイベントを除く.

  • イベント配信のソース解析
    1.Activityによるクリックイベントの配布プロセス
    イベントの開始ActivitydispatchTouchEvent()が配信する、具体的な作業は内部のWindowに任せる.Windowsはイベントをdecor viewに渡します.一方、decor viewは、一般に、現在のインタフェースの下部容器(通常setContentViewで伝達するレイアウト)である、Activity.getWindow.getDecorView()によって得ることができる.
    WindowsはどのようにしてViewGroupにイベントを伝えますか?まずWindowクラスは抽象クラスであり、クラスの呼び出しの配布方法も抽象方法である.実装クラスを見つける必要がある.Windowの唯一の実装クラスPhoneWindow.このクラスはインスタンス化されると再構築されます.PhoneWindow#superDispatchTouchEvent(ev)メソッドは、イベントをDecorViewに渡す.
    decorViewは私たちのlayoutレイアウトをマウントしたトップレベルのViewで、FrameLayoutを継承しています.((ViewGroup)getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0);
    この方法はActivityが設定したViewを取得することができる.だから関係がはっきりした.イベントは最上位のDecorViewに渡され、私たちが設定したViewに渡されます.
    2.ViewGroupによるイベントの処理
    親要素において、子要素がクリックイベントを受信できるか否かを判断する要因は、主に、子要素がアニメーションを再生するか否かとクリックイベントの座標が子要素の領域内に落ちるか否かという2つの要因によって測定される.dispatchTransformedTouchEvent()は、実際には呼び出し子ViewのdispatchTouchEventである.一方、イベントでは、子供から親要素(子ViewはonTouchEventでfalseを返す).実はdispatchTransformedTouchEvent()が呼び出されたのです.違いは、伝達パラメータ3が伝達する空の値ではなく、外伝がnullであることである.次のコードを見てください.
     private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
    
                View child, int desiredPointerIdBits) {
    
        if (child == null) {
    
         handled = super.dispatchTouchEvent(event);
    
        } else {
    
         handled = child.dispatchTouchEvent(event);
    
        }
    
    }
    

    3.Viewによるイベントの処理
  • Viewのイベントに対する判断は、まず、onTouchListenerがあるか否かを検出することであり、あればその中のonTouch()を呼び出す方法である.
  • は、onTouchEvent()が実行するここで判断条件があり、onTouchListener()でtrueが戻るとする.ifにおける条件判断は1つ目が成立する、onTouchEvent()メソッドが呼び出されることもない.
  • if (li != null && li.mOnTouchListener != null
    
                   && (mViewFlags & ENABLED_MASK) == ENABLED
    
                   && li.mOnTouchListener.onTouch(this, event)) {
    
               result = true;
    
    }
    
    if (!result && onTouchEvent(event)) {
    
     result = true;
    
    }
    

    次に、
  • は、onTouchEvent()において、まず、View処理が利用できない状態を確認する.ここで注意が必要である、ビューは利用できないが、クリックマークまたは長押ししクリックマークがtrueである.事件も消費される.以下の
  • |
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
    
           .....
    
               // 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);
    
           }
    
    
  • ビューにエージェントが設定されている場合、TouchDelegate onTouchEvent()
  • も実行する.
  • は、クリック状態を処理する.まずクリックと長押しがtrueであれば消費イベントとなる.すなわちonTouchEvent()はtrueを返す.そしてACTION_UPにおいてperformClick()がトリガーされ、onClickListener()が設けるとここで判断するonClick()が呼び出される.
  • でずっと言ってたLONG_CLICKABLEとCLICKABLE.長押ししマークのデフォルトはfalse.クリックマークはViewがクリック可能かどうかと関係がある.buttonがクリックできるデフォルトはtrueです.否者逆ビューの2つのタグは、使用時にsetClickableおよびsetLongClickableによって変更することができる.ここではもう一つの付与方法に注意しなければならない.setOnClickListener()またはsetOnLongClickListener()の傍受が設定と自動的に対応する属性がtrueとなる.

  • Viewのスライド衝突
    スライド衝突の解決方法
    がいぶブロックほう
    クリックイベントとは、親コンテナのブロック処理を先に通過する必要があり、親コンテナがこのイベントを必要とする場合はブロックし、必要でない場合はドロップすることを指す.外部ブロック親コンテナを書き換える必要があるonInterceptTouchEventメソッド
    このようなブロック法を用いると、簡単に説明する.ではまず
  • ACTION_DOWNこのイベントは、親コンテナがこのイベントをブロックすると、後続のACTION_DOWNACTION_MOVEのイベントが親コンテナによって処理されるため、親コンテナはfalseに戻る必要がある.この時点でこのイベントシーケンスの残りの部分はサブ要素に伝達できない.
  • ACTION_UPというイベントは、実際のニーズに応じてブロックする必要があるか否かを決定することができる.ブロックする必要がある場合はtrueに戻る.そうでなければfalse.
  • ACTION_MOVEこのイベントはfalseに戻らなければならない.ACTION_UPイベント自体はあまり意味がないからだ.
  •     @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            boolean intercepted = false;
            int x = (int) ev.getX();
            int y = (int) ev.getY();
            switch (ev.getAction()){
                case MotionEvent.ACTION_DOWN:
                    intercepted = false;
                    break;
                case MotionEvent.ACTION_MOVE:
                    if("        "){
                        intercepted = true;
                    }else {
                        intercepted = false;
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    intercepted = false;
                    break;
            }
            mLastXIntercept = x;
            mLastYIntercept = x;
            return intercepted;
        }
    
    

    ないぶブロックほう
    親コンテナがイベントをブロックすることなく、すべてのイベントがサブエレメントに渡され、サブエレメントがこのイベントを必要とする場合に直接消費されることを意味します.そうでない場合は親容器で処理するが、この方法はAndroidにおけるイベント配信メカニズムと一致しないため、r e q u s t D e a l l o w InterceptTouchEvent()方式に合わせる必要がある.サブエレメントを書き換える必要があるdispatchTouchEvent
    このブロック法の使用規則:
    サブビューのACTION_UPが複写する.
  • dispatchTouchEvent()イベント:親コンテナにすべてのイベントのブロックを拒否させ、ACTION_DOWN
  • を呼び出す.
  • parent.requestDisallowInterceptTouchEvent(true)イベントでは、あるシーンでブロックする必要がある場合、呼び出し方法により親コンテナがイベントをブロックすることを許可する条件のブロック判定が行われる.
  • ACTION_MOVEの場合、return
  • が呼び出される.
    親容器のsuper.dispatchTouchEvent(event)onInterceptTouchEvent()を行いfalseを返し、残りはtrueを返す複写である.
    説明すると、なぜ親容器はACTION_DOWNと一緒にtrueで複写しないのか.Action_downというイベントはACTION_DOWNというマークの影響を受けないため、ブロックマークがどの値であるかにかかわらず、押下イベントは必ず実行されるので、ここでtrueを返すと、このイベントシーケンスの後続部分は親コンテナによって処理され、サブコンテナはこのイベントを受信できないことを意味する.
        @Override
        public boolean dispatchTouchEvent(MotionEvent event) {
            int x = (int) event.getX();
            int y = (int) event.getY();
            switch (event.getAction()){
                case MotionEvent.ACTION_DOWN:
                    getParent().requestDisallowInterceptTouchEvent(true);
                    break;
                case MotionEvent.ACTION_MOVE:
                    int deltaX =  x - mLastX;
                    int deltaY =  x - mLastY;
                    if("        "){
                        getParent().requestDisallowInterceptTouchEvent(false);
                    }
                    break;
                case MotionEvent.ACTION_UP:
    
                    break;
            }
            mLastX = x;
            mLastY = y;
            return super.dispatchTouchEvent(event);
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            int action = ev.getAction();
            if(action == MotionEvent.ACTION_DOWN){
                return false;
            }else {
                return true;
            }
        }
    
    

    ケースリファレンス:SwipeRefreshLayout+RecyclerViewスライド競合解決
    文章を参観する.
    『Android開発芸術探索』書籍集『Android開発芸術探索』03-Viewのイベント体系