2011年1月4日火曜日

ScrollViewを格好よくスクロールする

iPhoneとAndroidを並べて使っていると、スクロールする画面で感じるストレスに差を感じることがある。

2つを比べてみると、iPhoneでは最上部までスクロールしてもまだスクロールすることができるのに対し、Androidではコンテンツの最上部までスクロールしてしまうと、それ以上スクロールを試みてもうんともすんとも言わないといった違いがあり、これがストレスの原因の一つと思われる。

そこで、Androidでもびろーんってのを実装してみようと考えたわけだが、Androidでは簡単に実現する方法が見当たらない。※Android 2.3 (Gingerbread)ではOverscrollを使う簡単な方法があるらしい。

なので今回は、OverscrollをAndroid 2.2以下でも簡単に実装することができる方法を考えてみた。そんでもってついでに公開してみた。Google Codeにサンプルプロジェクトごと公開してみたので、興味があればご参照あれ。

まず、画面を構成する要素はScrollViewとその子要素(今回はLinearLayoutとした)である。ScrollViewは子要素を一つしか設定できない。子要素の中身には適当なViewを配置してみる。
<ScrollView
    android:id="@+id/scroll_view"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
  
    <LinearLayout
      android:id="@+id/content"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
      android:orientation="vertical"
      android:paddingTop="200dip"
      android:paddingBottom="200dip">
      
      <TextView
        android:layout_width="fill_parent"
        android:layout_height="300dip"
        android:text="First Content."
        android:textSize="50dip"/>
      
      <TextView
        android:layout_width="fill_parent"
        android:layout_height="300dip"
        android:text="Second Content."
        android:textSize="50dip"/>
      
      <TextView
        android:layout_width="fill_parent"
        android:layout_height="300dip"
        android:text="Third Content."
        android:textSize="50dip"/>

    </LinearLayout>

</ScrollView>

ScrollViewの子要素であるLinearLayoutには、OverScrollするための余白としてpaddingTopおよびpaddingBottomに200dipを設定しておく。

次にTouchEventListenerを作成し、ScrollViewへ登録する。
scrollView.setOnTouchListener(new MyOnTouchListener(scrollView, contentView));

TouchEventListenerではACTION_UPをフックし、ScrollViewのgetScrollY()メソッドで現在のスクロール位置を取得する。現在のスクロール位置がpadding領域であった場合は、画面の端にpadding領域を除いたContent部分がぴったり表示されるようにスクロールする。といった感じ。

多分、コードのほうがわかり易い。
@Override
public boolean onTouch(View v, MotionEvent event)
{
    scroller.forceFinished(true);
    mScrollView.removeCallbacks(task);
    
    if (event.getAction() == MotionEvent.ACTION_UP)
    {
        int displayHeight = mScrollView.getHeight();
        int contentTop = mContentView.getPaddingTop();
        int contentBottom = mContentView.getHeight() - mContentView.getPaddingBottom();
        int lastPageTop = contentBottom - displayHeight;
        
        int currScrollY = mScrollView.getScrollY();
        
        scroller.startScroll(0, currScrollY, 0, lastPageTop - currScrollY, 500);
        
        mScrollView.post(task);
        
        return true;
    }
    
    return false;
}

ちなみに、scrollerはアニメーション付きのスクロール量計算をカプセル化したクラスで、下記のようなコードでインスタンスを生成しておく。
scroller = new Scroller(mScrollView.getContext(), new OvershootInterpolator());

また、taskの実体はRunnableであり、scrollerの計算結果取得とScrollView#scrollTo()メソッドの呼び出しを行っている。
task = new Runnable()
{
    @Override
    public void run()
    {
        scroller.computeScrollOffset();
        mScrollView.scrollTo(0, scroller.getCurrY());
        
        if (!scroller.isFinished())
        {
            mScrollView.post(this);
        }
    }
};

結果はこれ。

画像だと伝わりにくいが、タッチして下へぐぐっと引っ張ることができる。

指をはなすとびろーんアニメーション(Overshoot)でもとに戻る。

0 件のコメント:

コメントを投稿