かなり正確な水平パスをトラバースすると、GestureDetectorがListItemsで正常に機能することがわかりました。ただし、少し外れると、リストがスクロールし、ジェスチャは完了しません。何が起こっているのかは次のとおりです。
- GestureDetectorは、setOnTouchListener()を介してListItemsにインストールしたonTouchからMotionEventsを取得することから始めます。
- その間、ListViewは、onInterceptTouchEvent()を介して子ビューに送信されたイベントをリッスンしています。
- ListViewは、スクロールを開始したことを検出し、onInterceptTouchEvent()からtrueを返します。
- それ以降、MotionEventsは元のターゲットではなくListViewに送信されます...ListViewはonTouchハンドラーでMotionEventsの受信を開始します。これは、最後のACTION_UPまで続きます。(ACTION_DOWNからすべてのACTION_MOVEからACTION_UPまでのMotionEventは単一のジェスチャと見なされ、シーケンスの最後のACTION_UPの後にすべてが再開されることに注意してください)
- 元のターゲット(ListItem)はACTION_CANCEL MotionEventを取得し、ListItemのGestureDetectorがベイルアウトします。これは、GestureDetectorのコードをアプリに貼り付けて、ステップスルーすると発生することがわかります。
タッチが水平から少し外れたとしても、水平スワイプが続くかのようにアプリが動作する必要がありました。
解決策:これには、ViewGroup.requestDisallowInterceptTouchEvent(boolean disallowIntercept)が含まれ、親がモーションイベントをのぞき見するのを防ぎます。この方法では、onTouchListenerを実装してわずかなスワイプ(10ピクセル程度)を検出し、親がモーションイベントをインターセプトするのを停止します。その後、親はスクロールせず、ジェスチャ検出器は完了し続けます。
コードは次のとおりです。
private boolean mFlingInProgress = false;
private float mStartX = 0;
private final int FLING_TRIGGER_DISTANCE = 10;
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
float currentX = event.getRawX();
switch (action) {
case MotionEvent.ACTION_DOWN:
mStartX = currentX;
break;
case MotionEvent.ACTION_MOVE:
if (false == mFlingInProgress) {
if (Math.abs(currentX - mStartX) > FLING_TRIGGER_DISTANCE) {
// stop the parent intercepting motion events
mLayout.getParent().requestDisallowInterceptTouchEvent(true);
mFlingInProgress = true;
}
}
break;
case MotionEvent.ACTION_UP:
mFlingInProgress = false;
break;
}
return mGestureDetector.onTouchEvent(event);
}