カレンダーアプリなどで画面を左右にフリックすると、画面がスライドして次のデータが表示されたりします。
これを簡単に実装するクラスを作ってみました*1。
ただし、ListViewでOnItemLongClickを実装するとフリックとOnItemLongClickのイベントが両方動いてしまうという問題があるのでどうしたら良いのか悩み中。
ソースはGitHub「nakaji/FlickSample · GitHub」に置いてます。
フリック処理の実装概要
ざっくり以下の様な感じです。
- ViewFlipperの子に複数のViewを持たせる
- スライドさせた時、子のViewがローテーションされて表示されます
- ViewFlipperで使用するアニメーションを4つ用意する
- 右から左に
- 画面の外に出て行く、画面に入ってくる
- 左から右に
- 画面の外に出て行く、画面に入ってくる
- 右から左に
- OnGestureListenerのonFlingにフリックしたときの処理を書く
- 上で作成したOnGestureListenerを与えてGestureDetectorを作成する
- 上で作成したGestureDetectorのonTouchEventを使用したOnTouchListenerを作成する
- onTouchに実装します
- 上で作成した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; } }; }
*1:「[asin:4798122955:title]」のソースを参考にしました」