グリッドレイアウトでのジェスチャーの検出


1106

flingAndroidアプリケーションでジェスチャー検出を機能させたいのですが。

私が持ってGridLayoutいるのは9 ImageView秒を含むです。ソースはここにあります:Romain Guysのグリッドレイアウト

私が取ったそのファイルは、Romain GuyのPhotostreamアプリケーションからのものであり、ほんの少し変更されています。

単純なクリックの状況では、追加するonClickListenerfor をを実装ImageViewするメインに設定するだけです。を認識するものを実装することは、無限に複雑になります。これはスパンする可能性があるためだと思いますか?activityView.OnClickListenerflingviews

  • 私のアクティビティが実装されている場合、 OnGestureListenerそれをのジェスチャーリスナーGridまたはImage追加したビューとして設定する方法がわかりません。

    public class SelectFilterActivity extends Activity implements
       View.OnClickListener, OnGestureListener { ...
    
  • 私の活動が実装する場合、私 OnTouchListenerにはonFling方法がありません override(これには、フリングが注目に値するかどうかを判断できるパラメーターとして2つのイベントがあります)。

    public class SelectFilterActivity extends Activity implements
        View.OnClickListener, OnTouchListener { ...
    
  • 私がカスタムを作成する場合ViewGestureImageViewそのように拡張して、ビューから発生したImageViewアクティビティをアクティビティに通知する方法がわかりませんfling。いずれにせよ、私はこれを試してみましたが、画面に触れたときにメソッドが呼び出されませんでした。

私は本当にこれがビュー全体で機能する具体的な例が必要です。何を、いつ、どのように添付すればよいlistenerですか?シングルクリックも検出できる必要があります。

// Gesture detection
mGestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {

    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        int dx = (int) (e2.getX() - e1.getX());
        // don't accept the fling if it's too short
        // as it may conflict with a button push
        if (Math.abs(dx) > MAJOR_MOVE && Math.abs(velocityX) > Math.absvelocityY)) {
            if (velocityX > 0) {
                moveRight();
            } else {
                moveLeft();
            }
            return true;
        } else {
            return false;
        }
    }
});

画面の上部に透明なビューを配置して、フリングをキャプチャすることは可能ですか?

inflate子イメージビューをXMLから選択しない場合GestureDetector、作成した新しいサブクラスにコンストラクタパラメータとしてを渡すことができImageViewますか?

これは、fling検出を機能させるための非常に単純なアクティビティであるSelectFilterActivity(フォトストリームから適応)です。

私はこれらの情報源を見てきました:

今のところ何もうまくいかず、いくつかの指針を期待していました。


回答:


818

Code Shogunに感謝します。そのコードは自分の状況に適応しました。

OnClickListener通常どおりアクティビティを実装します。

public class SelectFilterActivity extends Activity implements OnClickListener {

  private static final int SWIPE_MIN_DISTANCE = 120;
  private static final int SWIPE_MAX_OFF_PATH = 250;
  private static final int SWIPE_THRESHOLD_VELOCITY = 200;
  private GestureDetector gestureDetector;
  View.OnTouchListener gestureListener;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    /* ... */

    // Gesture detection
    gestureDetector = new GestureDetector(this, new MyGestureDetector());
    gestureListener = new View.OnTouchListener() {
      public boolean onTouch(View v, MotionEvent event) {
        return gestureDetector.onTouchEvent(event);
      }
    };

  }

  class MyGestureDetector extends SimpleOnGestureListener {
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
      try {
        if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
          return false;
        // right to left swipe
        if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
          Toast.makeText(SelectFilterActivity.this, "Left Swipe", Toast.LENGTH_SHORT).show();
        } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
          Toast.makeText(SelectFilterActivity.this, "Right Swipe", Toast.LENGTH_SHORT).show();
        }
      } catch (Exception e) {
         // nothing
      }
      return false;
    }

    @Override
    public boolean onDown(MotionEvent e) {
      return true;
    }
  }
}

メインレイアウトに追加するすべてのビューにジェスチャーリスナーをアタッチします。

// Do this for each view added to the grid
imageView.setOnClickListener(SelectFilterActivity.this); 
imageView.setOnTouchListener(gestureListener);

onClick(View v)アクティビティとonFlingジェスチャーリスナーの両方のオーバーライドされたメソッドがヒットするのを畏敬の念で見てください。

public void onClick(View v) {
  Filter f = (Filter) v.getTag();
  FilterFullscreenActivity.show(this, input, f);
}

ポスト「フリング」ダンスはオプションですが、推奨されます。


109
このコードをありがとう!とても役に立ちました。しかし、ジェスチャーを機能させようとしているときに、非常にイライラする問題に遭遇しました。SimpleOnGestureListenerで、ジェスチャを登録するには、onDownをオーバーライドする必要があります。それは真を返すことができますが、私は定義する必要があります。PS:私のAPIリビジョンかハードウェアかはわかりませんが、HTC Droid Erisで1.5を使用しています。
Cdsboy、2009

私はあなたのコードを試してみましたが、スワイプしたりクリックしたりしても問題ありません(マウスを使って、エミュレーターで作業しているためです)。onClickメソッドで定義したトーストを常に取得しているため、エミュレーターはスワイプせずにクリックだけを検出します。なぜそうなのですか?
lomza

私はこのコードを試しましたが、うまくいきませんでした。私のギャラリービュー内の子ビューの1つにonClickリスナーを適用すると、まだスクロールできませんでした
Jonathan

Iomza:breakステートメントを入れて、コードをステップ実行してみましたか?
IgorGanapolsky 2011

内部クラスを使用するための称賛!非常にクリーンなアプローチ。
IgorGanapolsky 2011

211

上記の回答の1つは、さまざまなピクセル密度の処理について言及していますが、スワイプパラメータを手動で計算することを推奨しています。ViewConfigurationクラスを使用して、システムから実際にスケーリングされた適切な値を取得できることは注目に値します。

final ViewConfiguration vc = ViewConfiguration.get(getContext());
final int swipeMinDistance = vc.getScaledPagingTouchSlop();
final int swipeThresholdVelocity = vc.getScaledMinimumFlingVelocity();
final int swipeMaxOffPath = vc.getScaledTouchSlop();
// (there is also vc.getScaledMaximumFlingVelocity() one could check against)

これらの値を使用すると、アプリケーションとシステムの残りの部分の間でフリングの「感触」がより一貫することに気づきました。


11
私は使用swipeMinDistance = vc.getScaledPagingTouchSlop()swipeMaxOffPath = vc.getScaledTouchSlop()ます。
Thomas Ahle

8
getScaledTouchSlopぎこちなく、オフセット結果がほとんどありません。たとえば、高さ540の画面で24ピクセルしかありません。指で範囲内に維持するのは非常に困難です。:S
WonderCsabo 2013年

148

私はそれを少し異なり、実装する追加の検出器クラスを書きました View.onTouchListener

onCreate次のように、それを最下位のレイアウトに追加するだけです。

ActivitySwipeDetector activitySwipeDetector = new ActivitySwipeDetector(this);
lowestLayout = (RelativeLayout)this.findViewById(R.id.lowestLayout);
lowestLayout.setOnTouchListener(activitySwipeDetector);

ここで、id.lowestLayoutはレイアウト階層で最も低いビューのid.xxxであり、lowestLayoutはRelativeLayoutとして宣言されています

そして、実際のアクティビティスワイプ検出器クラスがあります。

public class ActivitySwipeDetector implements View.OnTouchListener {

static final String logTag = "ActivitySwipeDetector";
private Activity activity;
static final int MIN_DISTANCE = 100;
private float downX, downY, upX, upY;

public ActivitySwipeDetector(Activity activity){
    this.activity = activity;
}

public void onRightSwipe(){
    Log.i(logTag, "RightToLeftSwipe!");
    activity.doSomething();
}

public void onLeftSwipe(){
    Log.i(logTag, "LeftToRightSwipe!");
    activity.doSomething();
}

public void onDownSwipe(){
    Log.i(logTag, "onTopToBottomSwipe!");
    activity.doSomething();
}

public void onUpSwipe(){
    Log.i(logTag, "onBottomToTopSwipe!");
    activity.doSomething();
}

public boolean onTouch(View v, MotionEvent event) {
    switch(event.getAction()){
        case MotionEvent.ACTION_DOWN: {
            downX = event.getX();
            downY = event.getY();
            return true;
        }
        case MotionEvent.ACTION_UP: {
            upX = event.getX();
            upY = event.getY();

            float deltaX = downX - upX;
            float deltaY = downY - upY;

       // swipe horizontal?
        if(Math.abs(deltaX) > Math.abs(deltaY))
        {
            if(Math.abs(deltaX) > MIN_DISTANCE){
                // left or right
                if(deltaX > 0) { this.onRightSwipe(); return true; }
                if(deltaX < 0) { this.onLeftSwipe(); return true; }
            }
            else {
                    Log.i(logTag, "Horizontal Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
                    return false; // We don't consume the event
            }
        }
        // swipe vertical?
        else 
        {
            if(Math.abs(deltaY) > MIN_DISTANCE){
                // top or down
                if(deltaY < 0) { this.onDownSwipe(); return true; }
                if(deltaY > 0) { this.onUpSwipe(); return true; }
            }
            else {
                    Log.i(logTag, "Vertical Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
                    return false; // We don't consume the event
            }
        }

            return true;
        }
    }
    return false;
}

}

私にとって本当に良い作品です!


1
これにより、実際にはジェスチャー機能を適用するのがはるかに簡単になり、必要な「少ない」配線が必要になりました。Dありがとう@Thomas
nemesisfixx

5
これはきちんとしたユーティリティクラスのように見えますが、私はあなたの4つのon ... swipe()メソッドがインターフェイスであるべきだと思います
Someone Somewhere

2
これらのリターンがあってはならない(「イベントを消費しない」行)、そうではありませんか?垂直スクロール機能を無効にします。
Marek Sebera

5
具体的には、onTouch()メソッド。まず、デルタXが十分に大きくない場合、デルタYをチェックせずに戻ります。その結果、左右のスワイプが検出されません。次に、スワイプが見つからない場合でもtrueを返しません。3番目に、アクションがダウンしてもtrueを返すべきではありません。これにより、onClickなどの他のリスナーが機能しなくなります。
Jeffrey Blattman、2012

1
@Piotr参照を保持するオブジェクトがアクティビティ自体と同じスコープである限り、問題にはなりません。この問題は、静的メンバーなどから、アクティビティよりもスコープが大きい場所にアクティビティへの参照を保持すると発生します。
Jeffrey Blattman、2012

94

Thomas Fankhauserのソリューションを少し修正して修復しました

システム全体は、SwipeInterfaceActivitySwipeDetectorの 2つのファイルで構成されています


SwipeInterface.java

import android.view.View;

public interface SwipeInterface {

    public void bottom2top(View v);

    public void left2right(View v);

    public void right2left(View v);

    public void top2bottom(View v);

}

検出器

import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class ActivitySwipeDetector implements View.OnTouchListener {

    static final String logTag = "ActivitySwipeDetector";
    private SwipeInterface activity;
    static final int MIN_DISTANCE = 100;
    private float downX, downY, upX, upY;

    public ActivitySwipeDetector(SwipeInterface activity){
        this.activity = activity;
    }

    public void onRightToLeftSwipe(View v){
        Log.i(logTag, "RightToLeftSwipe!");
        activity.right2left(v);
    }

    public void onLeftToRightSwipe(View v){
        Log.i(logTag, "LeftToRightSwipe!");
        activity.left2right(v);
    }

    public void onTopToBottomSwipe(View v){
        Log.i(logTag, "onTopToBottomSwipe!");
        activity.top2bottom(v);
    }

    public void onBottomToTopSwipe(View v){
        Log.i(logTag, "onBottomToTopSwipe!");
        activity.bottom2top(v);
    }

    public boolean onTouch(View v, MotionEvent event) {
        switch(event.getAction()){
        case MotionEvent.ACTION_DOWN: {
            downX = event.getX();
            downY = event.getY();
            return true;
        }
        case MotionEvent.ACTION_UP: {
            upX = event.getX();
            upY = event.getY();

            float deltaX = downX - upX;
            float deltaY = downY - upY;

            // swipe horizontal?
            if(Math.abs(deltaX) > MIN_DISTANCE){
                // left or right
                if(deltaX < 0) { this.onLeftToRightSwipe(v); return true; }
                if(deltaX > 0) { this.onRightToLeftSwipe(v); return true; }
            }
            else {
                Log.i(logTag, "Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
            }

            // swipe vertical?
            if(Math.abs(deltaY) > MIN_DISTANCE){
                // top or down
                if(deltaY < 0) { this.onTopToBottomSwipe(v); return true; }
                if(deltaY > 0) { this.onBottomToTopSwipe(v); return true; }
            }
            else {
                Log.i(logTag, "Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
                v.performClick();
            }
        }
        }
        return false;
    }

}

これは次のように使用されます:

ActivitySwipeDetector swipe = new ActivitySwipeDetector(this);
LinearLayout swipe_layout = (LinearLayout) findViewById(R.id.swipe_layout);
swipe_layout.setOnTouchListener(swipe);

そして、実装する際にActivity、あなたからのメソッドを実装する必要がSwipeInterface、あなたが表示されに見つけることができスワイプイベントと呼ばれていました。

@Override
public void left2right(View v) {
    switch(v.getId()){
        case R.id.swipe_layout:
            // do your stuff here
        break;
    }       
}

もう一度少し変更しv.performClick();ました。同じビューに設定されている場合、OnClickListenerへのイベントを消費しないために使用されるを参照してください
Marek Sebera

こんにちは、私は完全に初心者なので、この質問は非常に明白または簡単なものかもしれませんが、答えてください。あなたが書いた部分は、次のように使用されます:ActivitySwipeDetector swipe = new ActivitySwipeDetector(this); このステートメントはMainActivityの一部になりますよね?そして、「これ」がMainActivityのアクティビティになります。一方、コンストラクターはSwipeInterfaceのインスタンスを取ります。ここで私を助けてください。どうもありがとう。
Chocolava、2013年

@Chocolavaは新しい質問を作成します。コメントは、このように尋ねるには適切な場所ではありません。
Marek Sebera 2013年

@MarekSeberaこれはScrollViewとListViewでは機能しませんか?それらを処理する方法?
Duc Tran

@silentbang再び、これはそのような質問をする場所ではありません。新しい質問スレッドを作成してください。
Marek Sebera 2013年

65

上記のスワイプジェスチャー検出コードは非常に便利です。ただし(REL_SWIPE)、絶対値ではなく次の相対値を使用して、このソリューション密度にとらわれないようにすることもできます。(SWIPE_)

DisplayMetrics dm = getResources().getDisplayMetrics();

int REL_SWIPE_MIN_DISTANCE = (int)(SWIPE_MIN_DISTANCE * dm.densityDpi / 160.0f);
int REL_SWIPE_MAX_OFF_PATH = (int)(SWIPE_MAX_OFF_PATH * dm.densityDpi / 160.0f);
int REL_SWIPE_THRESHOLD_VELOCITY = (int)(SWIPE_THRESHOLD_VELOCITY * dm.densityDpi / 160.0f);

8
これを呼び出すための+1。DensityMetrics.densityDpiはAPI 4で導入されたことに注意してください。API1との下位互換性のために、代わりにDensityMetrics.densityを使用してください。これにより、計算がSWIPE_MIN_DISTANCE * dm.densityになるように変更されます。
Thane Anthem

どこで160.0fという数字を得ましたか?
IgorGanapolsky 2011

developer.android.com/guide/practices/screens_support.html密度非依存ピクセル(dp)dp単位からスクリーンピクセルへの変換は簡単です:px = dp *(dpi / 160)
paiego

あちこち探していました。インターネット上のonFling()の例にはこれがなく、UXの低下につながります。ありがとう!
サンディ

160.0fは、DP(密度非依存ピクセル)のベースとなる標準密度である160 DPIに由来します。public static final int DENSITY_MEDIUM APIレベル4で追加中密度画面用の標準の量子化DPI。
定数値

35

Thomas FankhauserMarek Seberaによって提案された私のバージョンのソリューション(垂直スワイプは処理しません):

SwipeInterface.java

import android.view.View;

public interface SwipeInterface {

    public void onLeftToRight(View v);

    public void onRightToLeft(View v);
}

ActivitySwipeDetector.java

import android.content.Context;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

public class ActivitySwipeDetector implements View.OnTouchListener {

    static final String logTag = "ActivitySwipeDetector";
    private SwipeInterface activity;
    private float downX, downY;
    private long timeDown;
    private final float MIN_DISTANCE;
    private final int VELOCITY;
    private final float MAX_OFF_PATH;

    public ActivitySwipeDetector(Context context, SwipeInterface activity){
        this.activity = activity;
        final ViewConfiguration vc = ViewConfiguration.get(context);
        DisplayMetrics dm = context.getResources().getDisplayMetrics();
        MIN_DISTANCE = vc.getScaledPagingTouchSlop() * dm.density;
        VELOCITY = vc.getScaledMinimumFlingVelocity();
        MAX_OFF_PATH = MIN_DISTANCE * 2;            
    }

    public void onRightToLeftSwipe(View v){
        Log.i(logTag, "RightToLeftSwipe!");
        activity.onRightToLeft(v);
    }

    public void onLeftToRightSwipe(View v){
        Log.i(logTag, "LeftToRightSwipe!");
        activity.onLeftToRight(v);
    }

    public boolean onTouch(View v, MotionEvent event) {
        switch(event.getAction()){
        case MotionEvent.ACTION_DOWN: {
            Log.d("onTouch", "ACTION_DOWN");
            timeDown = System.currentTimeMillis();
            downX = event.getX();
            downY = event.getY();
            return true;
        }
        case MotionEvent.ACTION_UP: {
            Log.d("onTouch", "ACTION_UP");
            long timeUp = System.currentTimeMillis();
            float upX = event.getX();
            float upY = event.getY();

            float deltaX = downX - upX;
            float absDeltaX = Math.abs(deltaX); 
            float deltaY = downY - upY;
            float absDeltaY = Math.abs(deltaY);

            long time = timeUp - timeDown;

            if (absDeltaY > MAX_OFF_PATH) {
                Log.i(logTag, String.format("absDeltaY=%.2f, MAX_OFF_PATH=%.2f", absDeltaY, MAX_OFF_PATH));
                return v.performClick();
            }

            final long M_SEC = 1000;
            if (absDeltaX > MIN_DISTANCE && absDeltaX > time * VELOCITY / M_SEC) {
                if(deltaX < 0) { this.onLeftToRightSwipe(v); return true; }
                if(deltaX > 0) { this.onRightToLeftSwipe(v); return true; }
            } else {
                Log.i(logTag, String.format("absDeltaX=%.2f, MIN_DISTANCE=%.2f, absDeltaX > MIN_DISTANCE=%b", absDeltaX, MIN_DISTANCE, (absDeltaX > MIN_DISTANCE)));
                Log.i(logTag, String.format("absDeltaX=%.2f, time=%d, VELOCITY=%d, time*VELOCITY/M_SEC=%d, absDeltaX > time * VELOCITY / M_SEC=%b", absDeltaX, time, VELOCITY, time * VELOCITY / M_SEC, (absDeltaX > time * VELOCITY / M_SEC)));
            }

        }
        }
        return false;
    }

}

誰でもクラスの呼び出し方法を教えてください。ActivitySwipeDetector swipe = new ActivitySwipeDetector(this); そのようなコンストラクタがないため、明らかにエラーが発生しています。ActivitySwipeDetectorを指定する必要がありますswipe = new ActivitySwipeDetector(this、null);
abdfahim 2013年

@AbdullahFahim ActivitySwipeDetector(this、YourActivity.this);
Anton Kashpor 14

25

この質問は古いものであり、Googleは2011年7月に互換パッケージ、リビジョン3)をリリースしました。これには、ViewPagerAndroid 1.6以降で動作するものが含まれています。GestureListenerこの質問のために掲載の答えは、Android上で非常にエレガント感じることはありません。Androidギャラリーでの写真の切り替えや、新しいPlayマーケットアプリでのビューの切り替えに使用するコードを探しているのであれば、間違いありませんViewPager

詳細については、次のリンクをご覧ください。


ViewPagerの1つの問題は、投げるジェスチャーの距離と速度のパラメーターを制御できないことです。
アルマルカウイ

ViewPagerはギャラリーでは使用されません。
アンソニー

18

すべてのジェスチャーに直接使用できる組み込みのインターフェースがあります。
以下は、基本レベルのユーザー向けの説明です。 ここに画像の説明を入力してください 2つの輸入品があり、両方が異なるものを選択する場合は注意が必要です。 ここに画像の説明を入力してください ここに画像の説明を入力してください


1
そして次のステップは何ですか?そのリスナーを特定のビューに設定する方法は?そして、このビューがフラグメントの一部である場合はどうなりますか?
2013年

16

Web(およびこのページ)では、ViewConfigurationを使用するという提案があります。getScaledTouchSlop()を使用して、のデバイススケール値を取得しSWIPE_MIN_DISTANCEます。

getScaledTouchSlop()スワイプではなく、「スクロールしきい値」の距離を対象としています。スクロールしきい値距離は、「ページ間のスイング」しきい値距離よりも小さくなければなりません。たとえば、この関数は私のSamsung GS2で12ピクセルを返し、このページで引用されている例は約100ピクセルです。

APIレベル8(Android 2.2、Froyo)を使用するgetScaledPagingTouchSlop()と、ページスワイプ用に設計されています。私のデバイスでは、24(ピクセル)を返します。したがって、APIレベルが8未満の場合、「2 * getScaledTouchSlop()」が「標準」のスワイプしきい値であると思います。しかし、小さな画面で私のアプリケーションのユーザーはそれが少なすぎると私に言った...私のアプリケーションのように、あなたは垂直にスクロールし、水平にページを変更することができます。提案された値では、スクロールする代わりにページを変更することがあります。


13

また、マイナーな機能強化として。

try / catchブロックの主な理由は、最初の移動ではe1がnullになる可能性があるためです。try / catchに加えて、nullとreturnのテストを含めます。次のように

if (e1 == null || e2 == null) return false;
try {
...
} catch (Exception e) {}
return false;

12

ここには多くの優れた情報があります。残念ながら、これは多くのアプリケーションに不可欠であると思われるかもしれませんが、このfling-processingコードの多くは、さまざまな完了状態のさまざまなサイトに散在しています。

時間をかけて、適切な条件が満たされていることを確認するflingリスナーを作成しました。フリングページフリングのしきい値を満たしていることを確認するチェックを追加するページフリングリスナーを追加しました。これらのリスナーの両方を使用すると、フリングを水平軸または垂直軸に簡単に制限できます。画像をスライドするビューでどのように使用されるかを確認できます。私はここの人々がほとんどの研究をしたことを認めます---私はそれを使用可能なライブラリにまとめました。

ここ数日は、Androidでのコーディングにおける私の最初の試みです。来てはるかに期待しています。


2本の指でスワイプジェスチャーを実装したい。私を助けてください!
Gaurav Arora

12

これは、実用的な実装が必要な場合は、2つの回答を組み合わせた回答です。

package com.yourapplication;

import android.content.Context;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

public abstract class OnSwipeListener implements View.OnTouchListener {

    private final GestureDetector gestureDetector;

    public OnSwipeListener(Context context){
        gestureDetector = new GestureDetector(context, new OnSwipeGestureListener(context));
        gestureDetector.setIsLongpressEnabled(false);
    }

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        return gestureDetector.onTouchEvent(event);
    }

    private final class OnSwipeGestureListener extends GestureDetector.SimpleOnGestureListener {

        private final int minSwipeDelta;
        private final int minSwipeVelocity;
        private final int maxSwipeVelocity;

        private OnSwipeGestureListener(Context context) {
            ViewConfiguration configuration = ViewConfiguration.get(context);
            // We think a swipe scrolls a full page.
            //minSwipeDelta = configuration.getScaledTouchSlop();
            minSwipeDelta = configuration.getScaledPagingTouchSlop();
            minSwipeVelocity = configuration.getScaledMinimumFlingVelocity();
            maxSwipeVelocity = configuration.getScaledMaximumFlingVelocity();
        }

        @Override
        public boolean onDown(MotionEvent event) {
            // Return true because we want system to report subsequent events to us.
            return true;
        }

        // NOTE: see http://stackoverflow.com/questions/937313/android-basic-gesture-detection
        @Override
        public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX,
                               float velocityY) {

            boolean result = false;
            try {
                float deltaX = event2.getX() - event1.getX();
                float deltaY = event2.getY() - event1.getY();
                float absVelocityX = Math.abs(velocityX);
                float absVelocityY = Math.abs(velocityY);
                float absDeltaX = Math.abs(deltaX);
                float absDeltaY = Math.abs(deltaY);
                if (absDeltaX > absDeltaY) {
                    if (absDeltaX > minSwipeDelta && absVelocityX > minSwipeVelocity
                            && absVelocityX < maxSwipeVelocity) {
                        if (deltaX < 0) {
                            onSwipeLeft();
                        } else {
                            onSwipeRight();
                        }
                    }
                    result = true;
                } else if (absDeltaY > minSwipeDelta && absVelocityY > minSwipeVelocity
                        && absVelocityY < maxSwipeVelocity) {
                    if (deltaY < 0) {
                        onSwipeTop();
                    } else {
                        onSwipeBottom();
                    }
                }
                result = true;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return result;
        }
    }

    public void onSwipeLeft() {}

    public void onSwipeRight() {}

    public void onSwipeTop() {}

    public void onSwipeBottom() {}
}

本当に良い実装をありがとう。さらに、私がチェックすることをお勧めabsDeltaY > minSwipeDeltaabsVelocityY > minSwipeVelocityabsVelocityY < maxSwipeVelocity場合にのみ場合はminSwipeDelta != getScaledTouchSlopminSwipeVelocity != getScaledMinimumFlingVelocitymaxSwipeVelocity != getScaledMaximumFlingVelocity、すなわちこれらのいわゆる「デフォルト」(I平均getScaledTouchSlop、getScaledMinimumFlingVelocity、getScaledMaximumFlingVelocity)の値は、あなたに応じて拡大縮小または変更された場合にのみチェックするために自分の願い。
Elia12345

ポイントは、ソースコードによると、言及された「デフォルト」値はGestureDetectorによってすでにチェックされており、OnFlingはそれらが確認された場合にのみトリガーされます(トリガーが行われるのは、、またはでACTION_UPない場合のみ、つまり、完全に実現されたジェスチャーの結果)。(私は他のAPIバージョンをチェックしていませんので、コメントはありがたいです)。ACTION_MOVEACTION_POINTER_UP
Elia12345

11

droidQueryライブラリを使用して、フリング、クリック、ロングクリック、カスタムイベントを処理できます。実装は以下の私の以前の回答に基づいて構築されていますが、droidQuery洗練された単純な構文を提供します。

//global variables    private boolean isSwiping = false;
private SwipeDetector.Direction swipeDirection = null;
private View v;//must be instantiated before next call.

//swipe-handling code
$.with(v).swipe(new Function() {
    @Override
    public void invoke($ droidQuery, Object... params) {
        if (params[0] == SwipeDetector.Direction.START)
            isSwiping = true;
        else if (params[0] == SwipeDetector.Direction.STOP) {
            if (isSwiping) {                    isSwiping = false;
                if (swipeDirection != null) {
                    switch(swipeDirection) {
                        case DOWN :                                //TODO: Down swipe complete, so do something
                            break;
                        case UP :
                            //TODO: Up swipe complete, so do something
                            break;
                        case LEFT :
                            //TODO: Left swipe complete, so do something
                            break;
                        case RIGHT :
                            //TODO: Right swipe complete, so do something
                            break;
                        default :                                break;
                    }
                }                }
        }
        else {
            swipeDirection = (SwipeDetector.Direction) params[0];
        }
    }
});

元の回答

この回答は、他の回答からのコンポーネントの組み合わせを使用しています。SwipeDetectorイベントをリスニングするための内部インターフェイスを持つクラスで構成されます。また、のメソッドRelativeLayoutをオーバーライドして、スワイプイベントと他の検出されたイベント(クリックやロングクリックなど)の両方を許可する方法を示すためにも提供 します。ViewonTouch

SwipeDetector

package self.philbrown;

import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

/**
 * Detect Swipes on a per-view basis. Based on original code by Thomas Fankhauser on StackOverflow.com,
 * with adaptations by other authors (see link).
 * @author Phil Brown
 * @see <a href="http://stackoverflow.com/questions/937313/android-basic-gesture-detection">android-basic-gesture-detection</a>
 */
public class SwipeDetector implements View.OnTouchListener
{
    /**
     * The minimum distance a finger must travel in order to register a swipe event.
     */
    private int minSwipeDistance;

    /** Maintains a reference to the first detected down touch event. */
    private float downX, downY;

    /** Maintains a reference to the first detected up touch event. */
    private float upX, upY;

    /** provides access to size and dimension contants */
    private ViewConfiguration config;

    /**
     * provides callbacks to a listener class for various swipe gestures.
     */
    private SwipeListener listener;

    public SwipeDetector(SwipeListener listener)
    {
        this.listener = listener;
    }


    /**
     * {@inheritDoc}
     */
    public boolean onTouch(View v, MotionEvent event)
    {
        if (config == null)
        {
                config = ViewConfiguration.get(v.getContext());
                minSwipeDistance = config.getScaledTouchSlop();
        }

        switch(event.getAction())
        {
        case MotionEvent.ACTION_DOWN:
            downX = event.getX();
            downY = event.getY();
            return true;
        case MotionEvent.ACTION_UP:
            upX = event.getX();
            upY = event.getY();

            float deltaX = downX - upX;
            float deltaY = downY - upY;

            // swipe horizontal?
            if(Math.abs(deltaX) > minSwipeDistance)
            {
                // left or right
                if (deltaX < 0)
                {
                        if (listener != null)
                        {
                                listener.onRightSwipe(v);
                                return true;
                        }
                }
                if (deltaX > 0)
                {
                        if (listener != null)
                        {
                                listener.onLeftSwipe(v);
                                return true;
                        }
                }
            }

            // swipe vertical?
            if(Math.abs(deltaY) > minSwipeDistance)
            {
                // top or down
                if (deltaY < 0)
                {
                        if (listener != null)
                        {
                                listener.onDownSwipe(v);
                                return true;
                        }
                }
                if (deltaY > 0)
                {
                        if (listener != null)
                        {
                                listener.onUpSwipe(v);
                                return true;
                        }
                }
            }
        }
        return false;
    }

    /**
     * Provides callbacks to a registered listener for swipe events in {@link SwipeDetector}
     * @author Phil Brown
     */
    public interface SwipeListener
    {
        /** Callback for registering a new swipe motion from the bottom of the view toward its top. */
        public void onUpSwipe(View v);
        /** Callback for registering a new swipe motion from the left of the view toward its right. */
        public void onRightSwipe(View v);
        /** Callback for registering a new swipe motion from the right of the view toward its left. */
        public void onLeftSwipe(View v);
        /** Callback for registering a new swipe motion from the top of the view toward its bottom. */
        public void onDownSwipe(View v);
    }
}

スワイプインターセプタービュー

package self.philbrown;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.RelativeLayout;

import com.npeinc.module_NPECore.model.SwipeDetector;
import com.npeinc.module_NPECore.model.SwipeDetector.SwipeListener;

/**
 * View subclass used for handling all touches (swipes and others)
 * @author Phil Brown
 */
public class SwipeInterceptorView extends RelativeLayout
{
    private SwipeDetector swiper = null;

    public void setSwipeListener(SwipeListener listener)
    {
        if (swiper == null)
            swiper = new SwipeDetector(listener);
    }

    public SwipeInterceptorView(Context context) {
        super(context);
    }

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

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

    @Override
    public boolean onTouchEvent(MotionEvent e)
    {
        boolean swipe = false, touch = false;
        if (swiper != null)
            swipe = swiper.onTouch(this, e);
        touch = super.onTouchEvent(e);
        return swipe || touch;
    }
}

1
クリック可能な要素を含むビューにこれを実装してみました。スワイプがクリック可能な要素(たとえば、onItemClickリスナーが登録されているリストビュー)で開始すると、onTouchEventは呼び出されません。したがって、ユーザーはクリック可能な要素の上でスワイプを開始できません。これは私にとって不幸なことです。クリック可能な要素はかなりのビュースペースを占め、スワイプのサポートが必要なため、これを回避する方法を理解しようとしています。ビュー全体。スワイプがクリック可能な要素で始まらない場合は、完全に機能します。
Lo-Tan

@ Lo-Tan、これは、クリック可能なアイテムが子ビューであり、したがってのにあるSwipeInterceptorViewために発生し、クリックが最初に処理されます。これを修正するには、を実装して独自のクリックメカニズムを実装onTouchListenerするか、回避策として、クリックの代わりに長いクリックをリッスンします(を参照View.setOnLongClickListener)。
Phil

私は今まさにそれを試みています。または、ドラッグを開始した場合にクリックイベントをキャンセルすることもできます:)ありがとうございます。
Lo-Tan

1つの解決策は、アプリのすべてのビューにスワイプ検出器をアタッチすることです。もう1つは、SwipeInterceptorViewにonInterceptTouchEventを実装することです。
Edward Falk 2015

7

私は答えるのが遅すぎることを知っていますが、それでもListViewのSwipe Detectionを投稿しています。ListViewアイテムでSwipe Touch Listenerを使用する方法

屈折:Exterminator13(このページの回答の1つ)

1つのActivitySwipeDetector.classを作成します

package com.example.wocketapp;

import android.content.Context;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

public class ActivitySwipeDetector implements View.OnTouchListener 
{
    static final String logTag = "SwipeDetector";
    private SwipeInterface activity;
    private float downX, downY;
    private long timeDown;
    private final float MIN_DISTANCE;
    private final int VELOCITY;
    private final float MAX_OFF_PATH;

    public ActivitySwipeDetector(Context context, SwipeInterface activity)
    {
        this.activity = activity;
        final ViewConfiguration vc = ViewConfiguration.get(context);
        DisplayMetrics dm = context.getResources().getDisplayMetrics();
        MIN_DISTANCE = vc.getScaledPagingTouchSlop() * dm.density;
        VELOCITY = vc.getScaledMinimumFlingVelocity();
        MAX_OFF_PATH = MIN_DISTANCE * 2;
    }

    public void onRightToLeftSwipe(View v) 
    {
        Log.i(logTag, "RightToLeftSwipe!");
        activity.onRightToLeft(v);
    }

    public void onLeftToRightSwipe(View v) 
    {
        Log.i(logTag, "LeftToRightSwipe!");
        activity.onLeftToRight(v);
    }

    public boolean onTouch(View v, MotionEvent event) 
    {
        switch (event.getAction()) 
        {
            case MotionEvent.ACTION_DOWN:
            {
                Log.d("onTouch", "ACTION_DOWN");
                timeDown = System.currentTimeMillis();
                downX = event.getX();
                downY = event.getY();
                v.getParent().requestDisallowInterceptTouchEvent(false);
                return true;
            }

        case MotionEvent.ACTION_MOVE:
            {
                float y_up = event.getY();
                float deltaY = y_up - downY;
                float absDeltaYMove = Math.abs(deltaY);

                if (absDeltaYMove > 60) 
                {
                    v.getParent().requestDisallowInterceptTouchEvent(false);
                } 
                else
                {
                    v.getParent().requestDisallowInterceptTouchEvent(true);
                }
            }

            break;

            case MotionEvent.ACTION_UP: 
            {
                Log.d("onTouch", "ACTION_UP");
                long timeUp = System.currentTimeMillis();
                float upX = event.getX();
                float upY = event.getY();

                float deltaX = downX - upX;
                float absDeltaX = Math.abs(deltaX);
                float deltaY = downY - upY;
                float absDeltaY = Math.abs(deltaY);

                long time = timeUp - timeDown;

                if (absDeltaY > MAX_OFF_PATH) 
                {
                    Log.e(logTag, String.format(
                            "absDeltaY=%.2f, MAX_OFF_PATH=%.2f", absDeltaY,
                            MAX_OFF_PATH));
                    return v.performClick();
                }

                final long M_SEC = 1000;
                if (absDeltaX > MIN_DISTANCE && absDeltaX > time * VELOCITY / M_SEC) 
                {
                     v.getParent().requestDisallowInterceptTouchEvent(true);
                    if (deltaX < 0) 
                    {
                        this.onLeftToRightSwipe(v);
                        return true;
                    }
                    if (deltaX > 0) 
                    {
                        this.onRightToLeftSwipe(v);
                        return true;
                    }
                }
                else 
                {
                    Log.i(logTag,
                            String.format(
                                    "absDeltaX=%.2f, MIN_DISTANCE=%.2f, absDeltaX > MIN_DISTANCE=%b",
                                    absDeltaX, MIN_DISTANCE,
                                    (absDeltaX > MIN_DISTANCE)));
                    Log.i(logTag,
                            String.format(
                                    "absDeltaX=%.2f, time=%d, VELOCITY=%d, time*VELOCITY/M_SEC=%d, absDeltaX > time * VELOCITY / M_SEC=%b",
                                    absDeltaX, time, VELOCITY, time * VELOCITY
                                            / M_SEC, (absDeltaX > time * VELOCITY
                                            / M_SEC)));
                }

                 v.getParent().requestDisallowInterceptTouchEvent(false);

            }
        }
        return false;
    }
    public interface SwipeInterface 
    {

        public void onLeftToRight(View v);

        public void onRightToLeft(View v);
    }

}

次のように、アクティビティクラスから呼び出します。

yourLayout.setOnTouchListener(new ActivitySwipeDetector(this, your_activity.this));

また、2つの@overrideメソッドを提供するSwipeInterface実装 することを忘れないでください。

    @Override
    public void onLeftToRight(View v) 
    {
        Log.e("TAG", "L to R");
    }

    @Override
    public void onRightToLeft(View v) 
    {
        Log.e("TAG", "R to L");
    }

私はそれを見つけるMAX_OFF_PATH = 5 * vc.getScaledPagingTouchSlop()自然にわずかに弧を旅行親指スワイプにとってより快適です。
qix

4

ジェスチャーとは、タッチスクリーンとユーザー間の相互作用をトリガーするための微妙な動きです。これは、画面上の最初のタッチから最後の指が表面を離れるまでの期間続きます。

Androidには、GestureDetectorと呼ばれるクラスが用意されています。このクラスを使用して、上下にタップしたり、垂直および水平にスワイプ(フリング)、長押しおよび短押し、ダブルタップなどの一般的なジェスチャーを検出できます。リスナーを接続します。

Activity クラスにGestureDetector.OnDoubleTapListener(ダブルタップジェスチャー検出用)およびGestureDetector.OnGestureListenerインターフェイスを実装し、すべての抽象メソッドを実装します。詳細 https://developer.android.com/training/gestures/detector.htmlにアクセスできます礼儀

デモテスト用。GestureDetectorDemo


4

別のクラスを作成したり、コードを複雑にし
たくない場合は、OnTouchListener内にGestureDetector変数を作成して、コードをより簡単にすることができます

namVyuVarは、listnerを設定する必要があるビューの任意の名前にすることができます

namVyuVar.setOnTouchListener(new View.OnTouchListener()
{
    @Override
    public boolean onTouch(View view, MotionEvent MsnEvtPsgVal)
    {
        flingActionVar.onTouchEvent(MsnEvtPsgVal);
        return true;
    }

    GestureDetector flingActionVar = new GestureDetector(getApplicationContext(), new GestureDetector.SimpleOnGestureListener()
    {
        private static final int flingActionMinDstVac = 120;
        private static final int flingActionMinSpdVac = 200;

        @Override
        public boolean onFling(MotionEvent fstMsnEvtPsgVal, MotionEvent lstMsnEvtPsgVal, float flingActionXcoSpdPsgVal, float flingActionYcoSpdPsgVal)
        {
            if(fstMsnEvtPsgVal.getX() - lstMsnEvtPsgVal.getX() > flingActionMinDstVac && Math.abs(flingActionXcoSpdPsgVal) > flingActionMinSpdVac)
            {
                // TskTdo :=> On Right to Left fling

                return false;
            }
            else if (lstMsnEvtPsgVal.getX() - fstMsnEvtPsgVal.getX() > flingActionMinDstVac && Math.abs(flingActionXcoSpdPsgVal) > flingActionMinSpdVac)
            {
                // TskTdo :=> On Left to Right fling

                return false;
            }

            if(fstMsnEvtPsgVal.getY() - lstMsnEvtPsgVal.getY() > flingActionMinDstVac && Math.abs(flingActionYcoSpdPsgVal) > flingActionMinSpdVac)
            {
                // TskTdo :=> On Bottom to Top fling

                return false;
            }
            else if (lstMsnEvtPsgVal.getY() - fstMsnEvtPsgVal.getY() > flingActionMinDstVac && Math.abs(flingActionYcoSpdPsgVal) > flingActionMinSpdVac)
            {
                // TskTdo :=> On Top to Bottom fling

                return false;
            }
            return false;
        }
    });
});

3

すべての人に:MotionEvent.ACTION_CANCELのケースを忘れないでください

ACTION_UPなしで30%スワイプで呼び出す

この場合、ACTION_UPに等しい


2

より一般的なClassを取得し、Tomasのクラスを取得して、アクティビティまたはフラグメントにイベントを送信するインターフェイスを追加しました。コンストラクタにリスナーを登録するので、インターフェースを実装するか、ClassCastExceptionがスローされるようにしてください。インターフェイスは、クラスで定義された4つの最後のintの1つを返し、それがアクティブ化されたビューを返します。

import android.app.Activity;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class SwipeDetector implements View.OnTouchListener{

    static final int MIN_DISTANCE = 100;
    private float downX, downY, upX, upY;
    public final static int RIGHT_TO_LEFT=1;
    public final static int LEFT_TO_RIGHT=2;
    public final static int TOP_TO_BOTTOM=3;
    public final static int BOTTOM_TO_TOP=4;
    private View v;

    private onSwipeEvent swipeEventListener;


    public SwipeDetector(Activity activity,View v){
        try{
            swipeEventListener=(onSwipeEvent)activity;
        }
        catch(ClassCastException e)
        {
            Log.e("ClassCastException",activity.toString()+" must implement SwipeDetector.onSwipeEvent");
        } 
        this.v=v;
    }
    public SwipeDetector(Fragment fragment,View v){
        try{
            swipeEventListener=(onSwipeEvent)fragment;
        }
        catch(ClassCastException e)
        {
            Log.e("ClassCastException",fragment.toString()+" must implement SwipeDetector.onSwipeEvent");
        } 
        this.v=v;
    }


    public void onRightToLeftSwipe(){   
        swipeEventListener.SwipeEventDetected(v,RIGHT_TO_LEFT);
    }

    public void onLeftToRightSwipe(){   
        swipeEventListener.SwipeEventDetected(v,LEFT_TO_RIGHT);
    }

    public void onTopToBottomSwipe(){   
        swipeEventListener.SwipeEventDetected(v,TOP_TO_BOTTOM);
    }

    public void onBottomToTopSwipe(){
        swipeEventListener.SwipeEventDetected(v,BOTTOM_TO_TOP);
    }

    public boolean onTouch(View v, MotionEvent event) {
        switch(event.getAction()){
        case MotionEvent.ACTION_DOWN: {
            downX = event.getX();
            downY = event.getY();
            return true;
        }
        case MotionEvent.ACTION_UP: {
            upX = event.getX();
            upY = event.getY();

            float deltaX = downX - upX;
            float deltaY = downY - upY;

            //HORIZONTAL SCROLL
            if(Math.abs(deltaX) > Math.abs(deltaY))
            {
                if(Math.abs(deltaX) > MIN_DISTANCE){
                    // left or right
                    if(deltaX < 0) 
                    {
                        this.onLeftToRightSwipe();
                        return true;
                    }
                    if(deltaX > 0) {
                        this.onRightToLeftSwipe();
                        return true; 
                    }
                }
                else {
                    //not long enough swipe...
                    return false; 
                }
            }
            //VERTICAL SCROLL
            else 
            {
                if(Math.abs(deltaY) > MIN_DISTANCE){
                    // top or down
                    if(deltaY < 0) 
                    { this.onTopToBottomSwipe();
                    return true; 
                    }
                    if(deltaY > 0)
                    { this.onBottomToTopSwipe(); 
                    return true;
                    }
                }
                else {
                    //not long enough swipe...
                    return false;
                }
            }

            return true;
        }
        }
        return false;
    }
    public interface onSwipeEvent
    {
        public void SwipeEventDetected(View v , int SwipeType);
    }

}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.