5

タッチ イベント (スワイプなど) の機能を持つカスタム ビューがあります。このカスタム ビューが 内で使用される可能性がありますScrollableLayout。問題は、ユーザーがカスタム ビュー内をスワイプすると、親 (ScrollableLayout) もスワイプ ジェスチャを処理するため、スクロールする必要がないことです。

event.preventDefaults()JavaScriptのようなものが必要です。

私は常にオーバーライドView#onTouchEventして返しますtrue。これからtrueを返すonTouchEventと、イベントが消費され、他のビューがonTouchEventを取得しないことを意味すると思いましたが、それは間違っています。

誰でも私を助けることができますか?

私のビューはそれをテストするのがとても簡単です:

public class PreventingTouchEventView extends View {

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

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.RED);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(200, 200);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return true;
    }
}

サンプル Android プロジェクトを github にプッシュしました: https://github.com/jjoe64/android-preventing-touch-test

赤いキャンバス内をタッチしてスクロールしても、ScrollableLayout はスクロールしませ

4

2 に答える 2

14

そのレイアウトからのタッチを防ぐには、いくつかのオプションがあります。

1) 常に true を返す OnTouchListener を設定します。

2) dispatchTouchEvent(MotionEvent イベント) をオーバーライドして、常に true を返すようにします。

UPD:オーケー、通常は OnTouchListener を設定する必要があります。これは、他のビューが呼び出されないようにするために前に述べたように、常に true を返しますonTouchが、ScrollView ではまったく別の話です。

まず、どのように機能するかを説明dispatchTouchEventします。ルートビューから子ビューへのイベントのディスパッチを開始するため、親が子dispatchTouchEventの前に呼び出されることを意味します。 dispatchTouchEvent

次に、ViewGroup.dispatchTouchEvent()コードの一部があります

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
    intercepted = onInterceptTouchEvent(ev);
    ev.setAction(action); // restore action in case it was changed
} else {
    intercepted = false;
}

これは、このタッチ イベントをインターセプトする必要があるかどうかを決定します。つまり、interceptedフラグが true の場合、このビューは後続のすべてのイベントをインターセプトします。また、ScrollView onInterceptTouchEvent垂直スクロールを実行している場合にアクションをインターセプトするために実装されています。

したがって、私の最初のアイデアはFLAG_DISALLOW_INTERCEPT、カスタム ビューの親に設定して、イベントをインターセプトするこの機会を禁止することでしたが、次のコード行のためにこのアイデアは失敗しますViewGroup

if (actionMasked == MotionEvent.ACTION_DOWN) {
     // Throw away all previous state when starting a new touch gesture.
     // The framework may have dropped the up or cancel event for the previous gesture
    // due to an app switch, ANR, or some other state change.
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}

つまり、MotionEvent.ACTION_DOWNすべての状態がクリアされた後 (およびFLAG_DISALLOW_INTERCEPT同様にクリアされます) ということです。

したがって、最終的な解決策は非常に簡単です

public class PreventingTouchEventView extends View {

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

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.RED);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (getParent() != null && event.getAction() == MotionEvent.ACTION_DOWN) {
            getParent().requestDisallowInterceptTouchEvent(true);
        }
        return super.dispatchTouchEvent(event);
    }
}

これはFLAG_DISALLOW_INTERCEPTfirst の直後にMotionEvent.ACTION_DOWN発生し、親による将来のアクションのさらなるインターセプトを禁止します。

ただし、親のビューがこのイベントでクレイジーなことを行ってインターセプトしている場合は、親のビューが の前に呼び出されるMotionEvent.ACTION_DOWNため、それについて何もできないことに注意してください。dispatchTouchEventdispatchTouchEvent

于 2013-07-24T15:03:57.250 に答える
1

親の onInterceptTouchEvent メソッドをオーバーライドする必要があります

于 2013-07-24T15:02:00.013 に答える