Top > Memo >

Android ViewFlipperのページ移動をフリックで行う

Tue Oct 18 13:23:43 UTC 2011 Tue Oct 18 13:23:43 UTC 2011

Androidには、ViewFlipperという複数のViewをページングしてくれるViewGroupがあるのだが、これを良くあるフリック入力でページ切り替え出来る様にしたときのメモ。

基本的には、

  • Activity(ViewFlipper)で発生したTouchイベントをGestureDetectorに渡し
  • GestureDetectorから呼び出されるOnGestureListenerのonFlingでページ切り替えを行う

だけ。

ちなみにGestureDetectorは、TouchイベントをOnGestureListenerで受け取れるイベントに変換してくれる役割をもつ。

しかし、ネット上でいくつか拝見した情報通りにコーディングしてはまったのが、

  • どのタイミングでGestureDetectorにTouchイベントを渡すか?

という所。

よくあるのは、Activity#onTouchや各ViewのonTouchListenerで渡すというパターン。

でも、これらでTouchイベントを受け取ろうとしても、子のViewでTouchイベントが処理されてしまうと最上位のViewFlipperまでイベントが届かないことになる。

自分の場合、ViewFlipperの子Viewとしてandroid:autoLinkを有効にしたTextViewを追加して、URLを含む文字列をsetTextしたらURLをクリックしていない場合でも、onTouchイベントがViewFlipperまで届かないといった現象が発生した。

っということで、おそらく

  • Activity(or ViewFlipper)のdispatchTouchEventメソッドをオーバーライドしたその中で渡す

のが正解っぽい。
dispatchTouchEventは、Activity(またはViewFlipper)で発生したTouchイベントを子のViewにディスパッチするメソッドなので、このメソッドで子Viewよりも先にTouchイベントを処理できる。
ここでページを切り替えるべきフリック入力なのかを判定し、そうであればページ切り替え、そうでなければ通常通りdispatchTouchEventを実行するという感じにすれば良い。

ってことで、コードはこんな感じになった。

public class Pager extends Activity implements OnGestureListener {
    private GestureDetector gestureDetector;
    private ViewFlipper flipper;
    private Animation slideInFromLeft;
    private Animation slideInFromRight;
    private Animation slideOutToLeft;
    private Animation slideOutToRight;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.pager);
        flipper = (ViewFlipper)findViewById(R.id.flipper);
        slideInFromLeft =
            AnimationUtils.loadAnimation(this, R.anim.slide_in_from_left);
        slideInFromRight =
            AnimationUtils.loadAnimation(this, R.anim.slide_in_from_right);
        slideOutToLeft =
            AnimationUtils.loadAnimation(this, R.anim.slide_out_to_left);
        slideOutToRight =
            AnimationUtils.loadAnimation(this, R.anim.slide_out_to_right);
        gestureDetector = new GestureDetector(this, this);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        return gestureDetector.onTouchEvent(event)
            || super.dispatchTouchEvent(event);
    }

    @Override
    public final boolean onFling(
        final MotionEvent e1,
        final MotionEvent e2,
        final float velocityX,
        final float velocityY) {
        float dx = Math.abs(e1.getX() - e2.getX());
        float dy = Math.abs(e1.getY() - e2.getY());
        if (dx > dy) {
            if (velocityX > 0) {
                flipper.setInAnimation(slideInFromLeft);
                flipper.setOutAnimation(slideOutToRight);
                flipper.showPrevious();
            } else {
                flipper.setInAnimation(slideInFromRight);
                flipper.setOutAnimation(slideOutToLeft);
                flipper.showNext();
            }
            return true;
        }
        return false;
    }
    
    // ...
}

※必要最低限のコードのみ。

ちなみにViewFlipperが最上位のコンテナではない場合やActivityがない場合は、ViewFlipperでオーバーライドする事になる。
っというか、ViewFlipperに限らずフリックによって何か処理したい場合は大抵この形になるね…

もちろん、これらとは別に各種リソース定義が必要。

res/anim/slide_in_from_left.xmlであればこんな感じ。

<?xml version="1.0" encoding="utf-8"?>
<translate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    android:fromXDelta="-100%p"
    android:toXDelta="0%p"
    android:fromYDelta="0%p"
    android:toYDelta="0%p">
</translate>