『Android開発芸術探索』ノート4--Viewイベント配信とスライド衝突
10711 ワード
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()の優先度
一般的なイベントの配布について述べた.このビューに
OnTouchListener
とOnClickListener
が同時に追加するとする.この場合の優先順位は次のとおりです.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に戻ると順次伝達される.イベント・メカニズムのルール:
onInterceptTouchEvent()
はACTION_DOWN
イベント時にtrueを返さない場合、同じイベントシーケンスは処理されない.また、イベントは親要素のonTouchEvent()
に再伝達され、メソッドが再呼び出される.ACTION_DOWN
以外のイベントを消費する場合、このクリックイベントは消失し、親要素のonTouchEvent()
は呼び出されず、現在ビューは後続のイベントを受信し続けることができ、最終的にこれらの消失イベントはactivity処理に伝達する.ViewGroup
デフォルトはイベントをブロックせず、ソースコードのViewGroupのonInterceptTouchEvent()
デフォルトはfalse View
はonInterceptTouchEvent()
ではなく、サブViewがないため、onTouchEvent()
View
のonTouchEvent
は、デフォルトでは消費イベントがtrueに戻る.クリックできない限り(clickable
、longClickable
がfalseである必要がある).ViewのlongClickable
のデフォルトはfalseです.clickable
は、Button
のデフォルトがtrue、TextView
のデフォルトがfalseのようなコントロールを区別する必要がある.View
のenable
の属性は、1つのViewがonTouchEvent
の状態であるもdisable
のデフォルトの戻り値に影響しない.clickable
またはlongClickable
がtrueである限り.onTouchEvent()
はtrueに戻りますonClick
が発生する前提は、現在のViewがクリック可能であり、downおよびupイベントを受信することである.requestDisallowInterceptTouchEvent()
により、親要素のイベント配信プロセスに子要素に関与することができるが、ACTION_DOWN
のイベントを除く.イベント配信のソース解析
1.Activityによるクリックイベントの配布プロセス
イベントの開始
Activity
のdispatchTouchEvent()
が配信する、具体的な作業は内部の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によるイベントの処理
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()
onTouchEvent()
はtrueを返す.そしてACTION_UP
においてperformClick()
がトリガーされ、onClickListener()
が設けるとここで判断するonClick()
が呼び出される.setClickable
およびsetLongClickable
によって変更することができる.ここではもう一つの付与方法に注意しなければならない.setOnClickListener()
またはsetOnLongClickListener()
の傍受が設定と自動的に対応する属性がtrueとなる.Viewのスライド衝突
スライド衝突の解決方法
がいぶブロックほう
クリックイベントとは、親コンテナのブロック処理を先に通過する必要があり、親コンテナがこのイベントを必要とする場合はブロックし、必要でない場合はドロップすることを指す.外部ブロック親コンテナを書き換える必要があるonInterceptTouchEventメソッド
このようなブロック法を用いると、簡単に説明する.ではまず
ACTION_DOWN
このイベントは、親コンテナがこのイベントをブロックすると、後続のACTION_DOWN
、ACTION_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のイベント体系