web-dev-qa-db-ja.com

RecyclerViewがタッチイベントを消費しないようにする

問題

Google Playアプリケーションの正確な動作をシミュレートしようとしています。

現在、「トップゲーム」などのカテゴリをスクロールしているときにリストをタッチすると、そのセルとRecyclerViewがタッチイベントを処理し、波紋が表示されているため、両方がタッチイベントを処理していることを確認できます指を押したままにすると、スクロールが止まります。または、リストがまだ移動しているときにセルをクリックすると、そのコンテンツはすぐに開かれます。

デフォルトの動作では、移動中に指を下に置くとスクロールが即座に停止し、そのタッチイベントを消費します。つまり、セルはそのタッチについて通知されません。アプリケーションのメインコンテンツがRecyclerViewにある場合、これは非常に苛立たしく、ユーザーが連続してスクロールしてからセルをクリックすると、最初のクリックがスクロールを停止して消費されたため、2回クリックする必要があります。リストがほとんど動いていない場合でも。

これまでに試したこと

最初は、RecyclerView内にタッチイベントを消費しないように指示する何らかのフラグがあるはずだと思いました。私はAndroidソースコードを調べ、Androidのドキュメントを介してルーティングしましたが、役に立ちませんでした。

次に、onInterceptTouchEventを使用してタッチイベントをキャプチャし、タッチされていることを自分のビューに手動で伝えようとしました。

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    View myCell = recyclerView.findChildViewUnder(event.getX(), event.getY());
    myCell.dispatchTouchEvent(event);
    return super.onInterceptTouchEvent(event);
}

ここで、dispatchTouchEventは、アダプターで設定した対応するタグがビューにあったとしても、何もしなかったようです。

私はコードマシンガンを使ってこの問題をあらゆる角度から撮影してみました。 requestDisallowInterceptTouchEventで遊んで、手動でonTouchを呼び出し、RecyclerView以降のリスナーのすべての方法をオーバーライドします。

何も動作していないようです。これにはエレガントな解決策があるはずだと私は確信しています、そして私は狂気への道に沿ったどこかで重要な詳細をすくい取りましたか?特に、Google独自のアプリはこのように動作するためです。

助けてください! :)ありがとう。

ソリューション

Jagoan Neonのおかげで解決策が見つかりました。使いやすさのために、私は彼の答えをカスタムRecyclerViewで囲みました。

public class MultiClickRecyclerView extends RecyclerView {
   public MultiClickRecyclerView(Context context) {
        super(context);
   }

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

   public MultiClickRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
   }

   @Override
   public boolean onInterceptTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN && this.getScrollState() == RecyclerView.SCROLL_STATE_SETTLING) {
            this.stopScroll();
        }
        return super.onInterceptTouchEvent(event);
   }
}
20
vguzzi

まず、OnItemTouchListenerを、おそらく次のようなグローバル変数として定義する必要があります。

RecyclerView.OnItemTouchListener mOnItemTouchListener = new RecyclerView.OnItemTouchListener() {
    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        if (e.getAction() == MotionEvent.ACTION_DOWN && rv.getScrollState() == RecyclerView.SCROLL_STATE_SETTLING) {
            Log.d(TAG, "onInterceptTouchEvent: click performed");
            rv.findChildViewUnder(e.getX(), e.getY()).performClick();
            return true;
        }
        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {}

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
};

なぜACTION_DOWNとSCROLL_STATE_SETTLINGなのですか?

ACTION_DOWNモーションイベントは、RecyclerViewでタッチを実行したときにトリガーされ、その正確な時点で、RecyclerViewはスクロールを停止しようとするため、スクロール状態をSCROLL_STATE_SETTLINGに変更します。そして、それはまさにあなたがクリックを実行したいときです。

そして、MotionEventを消費したことをRecyclerViewに伝えるために、trueを返すことを忘れないでください。そうしないと、performClick()が複数回呼び出される可能性があります。

それをonResumeでRecyclerViewに追加し、onPauseで削除すれば、すべて完了です;)

更新

FindChildViewUnderを実行しているときにnullチェッカーを追加します。特定のセルに触れていないときはnullを返します。

View childView = rv.findChildViewUnder(e.getX(), e.getY());
if (childView != null) childView.performClick();

アップデート2

RecyclerViewのスクロールを停止するだけで十分であることがわかります。代わりにこれを行ってください:

@Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        if (e.getAction() == MotionEvent.ACTION_DOWN && rv.getScrollState() == RecyclerView.SCROLL_STATE_SETTLING)
            rv.stopScroll();
        return false;
    }
25
Jagoan Neon