なか日記

一度きりの人生、楽しく生きよう。

Viewのフリックを実装する(一部問題あり)

カレンダーアプリなどで画面を左右にフリックすると、画面がスライドして次のデータが表示されたりします。
これを簡単に実装するクラスを作ってみました*1
ただし、ListViewでOnItemLongClickを実装するとフリックとOnItemLongClickのイベントが両方動いてしまうという問題があるのでどうしたら良いのか悩み中。
ソースはGitHub「nakaji/FlickSample · GitHub」に置いてます。

フリック処理の実装概要

ざっくり以下の様な感じです。

  1. ViewFlipperの子に複数のViewを持たせる
    • スライドさせた時、子のViewがローテーションされて表示されます
  2. ViewFlipperで使用するアニメーションを4つ用意する
    • 右から左に
      • 画面の外に出て行く、画面に入ってくる
    • 左から右に
      • 画面の外に出て行く、画面に入ってくる
  3. OnGestureListenerのonFlingにフリックしたときの処理を書く
  4. 上で作成したOnGestureListenerを与えてGestureDetectorを作成する
  5. 上で作成したGestureDetectorのonTouchEventを使用したOnTouchListenerを作成する
    • onTouchに実装します
  6. 上で作成したOnTouchListenerを各子Viewに割り当てる

フリック処理のヘルパークラス(FlickUtil)

コンストラクタにActivity自身とFiewFlipperのインスタンス、あと画面が左右にスライドするときの処理

package com.nakaji.android.flicksample;

import android.content.Context;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ViewFlipper;

public class FlickUtil {
    ViewFlipper viewFlipper;
    GestureDetector gestureDetecotr;
    Animation inFromLeft;
    Animation outToRight;
    Animation inFromRight;
    Animation outToLeft;
    FlickLogic setDataLogic;

    // ジェスチャーリスナー
    OnGestureListener gestureListener = new OnGestureListener() {
        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            return false;
        }

        @Override
        public void onShowPress(MotionEvent e) {
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            return false;
        }

        @Override
        public void onLongPress(MotionEvent e) {
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            float dx = Math.abs(velocityX);
            float dy = Math.abs(velocityY);

            if (dx > dy && dx > 150) {
                if (e1.getX() < e2.getX()) {
                    viewFlipper.setInAnimation(inFromLeft);
                    viewFlipper.setOutAnimation(outToRight);
                    viewFlipper.showPrevious();
                    setDataLogic.leftToRightLogic();
                } else {
                    viewFlipper.setInAnimation(inFromRight);
                    viewFlipper.setOutAnimation(outToLeft);
                    viewFlipper.showNext();
                    setDataLogic.rightToLeftLogic();
                }
                setDataLogic.setDataLogic();
                Log.d("BBT", "OnGestureListener.onFling");
                return true;
            }

            return false;
        }

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

    // タッチ処理リスナー
    OnTouchListener touchListener = new OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            return gestureDetecotr.onTouchEvent(event);
        }
    };

    /***
     * コンストラクタ
     * @param context
     * @param flipper
     * @param logic
     */
    public FlickUtil(Context context, ViewFlipper flipper, FlickLogic logic) {
        viewFlipper = flipper;
        gestureDetecotr = new GestureDetector(context, gestureListener);

        inFromLeft = AnimationUtils.loadAnimation(context, R.anim.in_from_left);
        outToRight = AnimationUtils.loadAnimation(context, R.anim.out_to_right);
        inFromRight = AnimationUtils.loadAnimation(context, R.anim.in_from_right);
        outToLeft = AnimationUtils.loadAnimation(context, R.anim.out_to_left);

        setOnTouchListener(context, flipper);
        setDataLogic(logic);
    }

    /***
     * ViewFlipperの子ViewにOnTouchListenerを設定する
     * @param context
     * @param flipper
     */
    private void setOnTouchListener(Context context, ViewFlipper flipper) {
        int child_count = viewFlipper.getChildCount();
        for (int i = 0; i < child_count; i++) {
            viewFlipper.getChildAt(i).setOnTouchListener(touchListener);
        }
    }

    /***
     * setDataLogicを設定する
     * @param logic
     */
    private void setDataLogic(FlickLogic logic) {
        setDataLogic = logic;
    }

    /***
     * フリックに関する処理に関するインタフェース
     *
     * @author nakaji
     *
     */
    public interface FlickLogic {
        public void setDataLogic();

        public void rightToLeftLogic();

        public void leftToRightLogic();
    }
}

アニメーション(左から右にフリックした場合)

左から画面に入ってくる
<?xml version="1.0" encoding="utf-8"?>
<set
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:interpolator="@android:anim/accelerate_interpolator">
	<translate
		android:duration="500"
		android:fromXDelta="-100%p" android:toXDelta="0%p"
		android:fromYDelta="0%p" android:toYDelta="0%p"
		android:fillAfter="true" android:fillEnabled="true"
	 />
</set>

右から左にフリックした場合は

		android:fromXDelta="100%p" android:toXDelta="0%p"
画面の右へ出て行く
<?xml version="1.0" encoding="utf-8"?>
<set
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:interpolator="@android:anim/accelerate_interpolator">
	<translate
		android:duration="500"
		android:fromXDelta="0%p" android:toXDelta="100%p"
		android:fromYDelta="0%p" android:toYDelta="0%p"
		android:fillAfter="true" android:fillEnabled="true"
	 />
</set>

右から左にフリックした場合は

		android:fromXDelta="0%p" android:toXDelta="-100%p"

サンプルAP

このクラスを使ったサンプルAPの一部を載せておきます。

レイアウト

ViewFlipperの子供に複数のViewを持たせます。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">

	<ViewFlipper
		android:id="@+id/ViewFlipper"
		android:layout_width="fill_parent"
		android:layout_height="fill_parent">
		<ListView
			android:id="@+id/ListView01"
			android:entries="@array/data01"
			android:layout_width="fill_parent"
			android:layout_height="fill_parent"
			android:text="@string/hello1" />
		<ListView
			android:id="@+id/ListView02"
			android:entries="@array/data02"
			android:layout_width="fill_parent"
			android:layout_height="fill_parent"
			android:text="@string/hello2" />
		<ListView
			android:id="@+id/ListView03"
			android:entries="@array/data03"
			android:layout_width="fill_parent"
			android:layout_height="fill_parent"
			android:text="@string/hello3"
		/>
	</ViewFlipper>
</LinearLayout>
FlickSampleActivity

リストの項目をクリック・ロングクリックしたときの処理については、View固有のイベントになるのでヘルパークラスには含めず、個別に設定させてます。
それにしても、newするだけ(変数に代入しない)って気持ち悪いな…

package com.nakaji.android.flicksample;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ListView;
import android.widget.ViewFlipper;

import com.nakaji.android.flicksample.FlickUtil.FlickLogic;

public class FlickSampleActivity extends Activity {

    ViewFlipper viewFlipper;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        ((ListView) findViewById(R.id.ListView01)).setOnItemClickListener(onItemClickListener);
        ((ListView) findViewById(R.id.ListView02)).setOnItemClickListener(onItemClickListener);
        ((ListView) findViewById(R.id.ListView03)).setOnItemClickListener(onItemClickListener);

        ((ListView) findViewById(R.id.ListView01)).setOnItemLongClickListener(onItemLongClickListener);
        ((ListView) findViewById(R.id.ListView02)).setOnItemLongClickListener(onItemLongClickListener);
        ((ListView) findViewById(R.id.ListView03)).setOnItemLongClickListener(onItemLongClickListener);

        viewFlipper = (ViewFlipper) findViewById(R.id.ViewFlipper);

        new FlickUtil(this, viewFlipper, new FlickLogic() {
            @Override
            public void setDataLogic() {
                View v = viewFlipper.getCurrentView();
                Log.d("FlickSample", "SetDataLogic.setDataLogic : " + v.toString());
            }

            @Override
            public void rightToLeftLogic() {
                View v = viewFlipper.getCurrentView();
                Log.d("FlickSample", "SetDataLogic.rightToLeftLogic : " + v.toString());
            }

            @Override
            public void leftToRightLogic() {
                View v = viewFlipper.getCurrentView();
                Log.d("FlickSample", "SetDataLogic.leftToRightLogic : " + v.toString());
            }
        });
    }

    private OnItemClickListener onItemClickListener = new OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView, View view, int arg2, long arg3) {
            Log.d("FlickSample", "onItemClick : " + view.toString());
        }
    };

    private OnItemLongClickListener onItemLongClickListener = new OnItemLongClickListener() {
        @Override
        public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
            Log.d("FlickSample", "onItemLongClick : " + arg1.toString());
            return false;
        }
    };
}

まとめ

まとめというか、わからないところ、地震がないところ。

  • ListViewでOnItemLongClickを実装して、リスト項目部分をフリックすると、フリックとOnItemLongClickのイベントが両方動くのはどうするのがいいんだろう
    • フリックとOnItemLongClickのイベントが両方動く
      f:id:nakaji999:20110509230614p:image:w180
    • フリックのイベントだけ動く
      f:id:nakaji999:20110509230613p:image:w180
  • newするだけ(変数に代入しない)ってあり?

*1:「[asin:4798122955:title]」のソースを参考にしました」