ひつじのにっき

mhidakaのにっきです。たまに長文、気が向いたとき更新。

OverScrollでListViewをビョーンってする方法

あけましておめでとうございます。今年もAndroidネタです。

Android 2.3で増えたリストをぐーっと引っ張ってびよーんってなるOverScroll機能をつかってみた。案外ややこしかったのと誰もまとめてくれてなくて涙目になったので、まとめておきます。

読むだけではわかりにくいと思うので、サンプルをあげておきました。
http://mtnk.org/down/Android_OverScrollSample.src.zip
Android 2.3のエミュレータをお手元で起動しながら確認してみてください。

OverScrollの結論。全然簡単じゃないです。Googleさん、On/Offで動くぐらいの超簡単なWrapperぐらいつくってくれてもいいのに

→ (2010/1/4追記)この記事書いた後、簡単に実装する方法をおしえてもらいました!
http://y-anz-m.blogspot.com/2011/01/androidoverscroll.html

関連API

いくつかOverScrollを使うのに必要なAPI

  • android.view.View.overScrollBy
    • OverScrollの大事な関数。現在位置やら開始、終了するメソッド
  • android.view.View.onOverScrolled
    • OverScrollした結果を受け取るメソッド。OnTouchEventのあとも自動で呼ばれてる。
  • android.widget.AbsListView.setOverScrollMode
    • スクロールモードの設定。OverScroll開始のトリガを決める。

詳細は続きで。


OverScrollを使うには、protectedなメソッドをOverrideする必要があります。

android.view.View

protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent)

オーバースクロールさせるためのメソッド。overSrollByをつかってオーバースクロールの仕方を指定します。詳しくは http://developer.android.com/intl/ja/reference/android/view/View.html です。
スクロールした結果は

protected void onOverScrolled (int scrollX, int scrollY, boolean clampedX, boolean clampedY)

onOverScrolledで受け取る。
ふたつとも protected なのでViewを継承して、自分でOverScrollListViewとかを作ってあげないとダメです。結構めんどくさい。

OverScrollモードの設定

void setOverScrollMode(int mode)
int mode:OVER_SCROLL_ALWAYS (default), OVER_SCROLL_IF_CONTENT_SCROLLS or OVER_SCROLL_NEVER

このAPIAndroid SDKを探してたらすぐ出てくる。重要そうだけどOverScrollでは脇役。僕もネットで調べてたら一番最初にsetOverScrollModeが引っかかりました。
くわしくはAbsListViewを参照。

OverScrollするためのソースコード

OverScrollを開始する、終了する、その間の移動量を確定する、の3処理ぐらいに分かれます。

まずは独自Viewが必要です。

public class OverScrollListView extends ListView {
	private final String TAG ="ListView";
	public OverScrollListView(Context context, AttributeSet attrs) {
		super(context, attrs);

		setOverScrollMode(OVER_SCROLL_ALWAYS);
	}

こんなかんじ。例ではListViewです。

OverScrollの開始

一番簡単なのはタッチイベントを取得して、MotionEvent.ACTION_DOWNのときにOverScrollする移動量を設定してあげればいいです。

@Override
public boolean onTouchEvent(MotionEvent ev) {
	switch (ev.getAction()) {
	//タップの開始
	case MotionEvent.ACTION_DOWN:

		Log.v(TAG, "ACTION_DOWN (Overscroll開始処理)");
		if(!mIsMotion){
			mIsMotion = true;
			mMotionValue = 0;
			mOldMotionEvent = ev.getY();

			//通常のTouchEvent処理
			return super.onTouchEvent(ev);
		}
		break;

トリガは何でもいいけど、ここではMotionEvent.ACTION_DOWNで、OverScrollするための変数を初期化。mMotionValueがスクロールの移動量の総和(SUM)、mOldMotionEventで一つ前のMotionEventを覚えてます。※手抜きしてYの値だけ。

OverScrollの終了

サンプルではMotionEvent.ACTION_UPで終了します。OverScrollByも初期化して元の位置に戻してあげましょう。

//タップの終了(離すタイミング)
case MotionEvent.ACTION_UP:
	Log.v(TAG, "ACTION_UP  (初期化処理)");
	if(mIsMotion){
		/* 初期化 */
		mIsMotion = false;
		mMotionValue = 0;
		mOldMotionEvent = 0;

		/* OverScrollの状態もキーが離れたので戻す */
		overScrollBy(0, 0, (int) 0, (int) 0 , 0, 10, 0, 200, true);

		/* キーイベントの消化 */
		return true;
	}
	break;

overScrollBy(0, 0, (int) 0, (int) 0 , 0, 10, 0, 200, true);の最後のtrueはonTouchEventではtrueらしいです。理由は割愛。気になる人はSDKソースコード読めばわかりますよ。

OverScrollを実際にしているところ

実にややこしい。OverScrollByに移動量の増分値(delta)と総量(Sum of Scroll)、移動上限などを与えます。

if(mIsMotion){
	/* OverScrollなら */
	Log.v(TAG, "OverScroll");

	int dy = (int) (mOldMotionEvent - ev.getY());	//移動量の算出

	//OverScrollByの値はx,yの移動量、x,yの移動量の総和、??、x,yの最大値です。
	//移動した結果がbOverScrollでかえってくる。trueなら端っこに到達。
	boolean bOverScroll = overScrollBy(0, dy, (int) 0, mMotionValue , 0, 10, 0, 200, true);

	//for Debug
	Log.v(TAG, "ev("+ev.getX()+","+ev.getY()+"), mOldMotionEvent:"+mOldMotionEvent );
	Log.v(TAG, "dy:" + dy + " mMotionValue:" + mMotionValue);
	Log.v(TAG, "overScrollBy:" +bOverScroll);

	/* 更新 */
	mOldMotionEvent = ev.getY();//現在値
	mMotionValue += dy;			//移動量

	/* キーイベントの消化 */
	return true;
}

移動量を一定ではなくて加速度をつけて与えるとイージングもかけられます*1

*1:月曜ぐらいにTechbooster.jpn.orgで解説できたらいいけど