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を配置してみる。
  1. <ScrollView  
  2.     android:id="@+id/scroll_view"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="fill_parent">  
  5.     
  6.     <LinearLayout  
  7.       android:id="@+id/content"  
  8.       android:layout_width="fill_parent"  
  9.       android:layout_height="fill_parent"  
  10.       android:orientation="vertical"  
  11.       android:paddingTop="200dip"  
  12.       android:paddingBottom="200dip">  
  13.         
  14.       <TextView  
  15.         android:layout_width="fill_parent"  
  16.         android:layout_height="300dip"  
  17.         android:text="First Content."  
  18.         android:textSize="50dip"/>  
  19.         
  20.       <TextView  
  21.         android:layout_width="fill_parent"  
  22.         android:layout_height="300dip"  
  23.         android:text="Second Content."  
  24.         android:textSize="50dip"/>  
  25.         
  26.       <TextView  
  27.         android:layout_width="fill_parent"  
  28.         android:layout_height="300dip"  
  29.         android:text="Third Content."  
  30.         android:textSize="50dip"/>  
  31.   
  32.     </LinearLayout>  
  33.   
  34. </ScrollView>  

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

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

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

多分、コードのほうがわかり易い。
  1. @Override  
  2. public boolean onTouch(View v, MotionEvent event)  
  3. {  
  4.     scroller.forceFinished(true);  
  5.     mScrollView.removeCallbacks(task);  
  6.       
  7.     if (event.getAction() == MotionEvent.ACTION_UP)  
  8.     {  
  9.         int displayHeight = mScrollView.getHeight();  
  10.         int contentTop = mContentView.getPaddingTop();  
  11.         int contentBottom = mContentView.getHeight() - mContentView.getPaddingBottom();  
  12.         int lastPageTop = contentBottom - displayHeight;  
  13.           
  14.         int currScrollY = mScrollView.getScrollY();  
  15.           
  16.         scroller.startScroll(0, currScrollY, 0, lastPageTop - currScrollY, 500);  
  17.           
  18.         mScrollView.post(task);  
  19.           
  20.         return true;  
  21.     }  
  22.       
  23.     return false;  
  24. }  

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

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

結果はこれ。

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

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

0 件のコメント:

コメントを投稿