備忘録 〜プログラミング〜

プログラミングに関する事をつらつらと、、

ListViewと、ScrollView使用時にスクロールの移動距離を取得する方法

下にスクロール時にヘッダーメニューを隠して、上にスクロール時にヘッダーメニューを表示する機能を実装する必要があったのですが、ListViewで移動距離を取得する方法が分からなくて調べてみました。
ScrollViewのほうも作ってみたので、まずはScrollViewのほうから、
【ScrollViewを継承したクラスを作成する】

public class ObservableScrollView extends ScrollView{
    public interface ScrollViewListener {
        void onScrollChanged(int x, int y, int oldX, int oldY);
    }
    private ScrollViewListener scrollViewListener = null;
    public ObservableScrollView(Context context) {
        super(context);
    }
    public ObservableScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public ObservableScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    public void setOnScrollViewListener(ScrollViewListener scrollViewListener) {
        this.scrollViewListener = scrollViewListener;
    }
    // onScrollChanged が複数回呼び出されるのを防止するために利用
    private int lastt = 0;
    @Override
    protected void onScrollChanged(int x, int y, int oldX, int oldY) {
        super.onScrollChanged(x, y, oldx, oldy);
        // 高速にスクロールした場合、画面上部で t, oldt がマイナスとなる場合は処理をスキップ
        if (scrollViewListener == null || lastt == y || y < 0 || oldy < 0) {
            return;
        }
        lastt = y;
        scrollViewListener.onScrollChanged(x, y, oldX, oldY);
    }
}

【使用方法】

final ObservableScrollView obScrollView = (ObservableScrollView) view.findViewById(R.id.contents_list_scroll_view);
  obScrollView.setOnScrollViewListener(new ObservableScrollView.ScrollViewListener() {
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    @Override
    public void onScrollChanged(int x, int y, int oldX, int oldY) {
      int diff = y - oldY; // 縦軸の移動量
      //以下は、スクロールでヘッダーを表示、非表示にする処理
      int translationY = 0 < diff
        ? Math.max((int) mHeader.getY() - diff, -mHeader.getHeight())
        : Math.min((int) mHeader.getY() - diff, 0);
      mHeader.setTranslationY(translationY);
    }
  });

続いて、ListView、
【移動距離を計算するヘルパークラスを用意する】

public class ListViewScrollTracker {
    private AbsListView mListView;
    private SparseArray<Integer> mPositions;
    public ListViewScrollTracker(final AbsListView listView){
        mListView = listView;
    }
    public int calculateIncrementalOffset(final int firstVisiblePosition, final int visibleItemCount){
        SparseArray<Integer> previousPositions = mPositions;
        mPositions = new SparseArray<Integer>();
        for(int i = 0; i < visibleItemCount; i++){
            mPositions.put(firstVisiblePosition + i, mListView.getChildAt(i).getTop());
        }
        if(previousPositions != null){
            for(int i = 0; i < previousPositions.size(); i++){
                int position = previousPositions.keyAt(i);
                int previousTop = previousPositions.get(position);
                Integer newTop = mPositions.get(position);
                if(newTop != null){
                    return newTop - previousTop;
                }
            }
        }
        return 0;
    }
    public void clear(){
        mPositions = null;
    }
}

【使用方法】

//スクロール値の相対置を調べるために使用
private ListViewScrollTracker mScrollTracker;
//ListViewの相対的なY位置
private int mOffsetY;
//ジャンルから探す/並び替えをスクロールに応じて表示、非表示にするために使用
private int mOldOffsetY;
private ListView mListView;
private LinearLayout mHeader;

@Override
protected void onCreate(Bundle savedInstanceState) {
  //使用するListView
  mListView = (ListView)view.findViewById(R.id.contents_list);
  mHeader = (LinearLayout)view.findViewById(R.id. store_top_header);
  mScrollTracker = new ListViewScrollTracker(mListView);
  mListView.setOnScrollListener(new AbsListView.OnScrollListener() {  
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
      mOffsetY += mScrollTracker.calculateIncrementalOffset(firstVisibleItem, visibleItemCount);
      int diff = mOldOffsetY - mOffsetY; // 縦軸の移動量      
      //以下は、スクロールでヘッダーを表示、非表示にする処理
      int translationY = 0 < diff
        ? Math.max((int) mHeader.getY() - diff, -mHeader.getHeight())
        : Math.min((int) mHeader.getY() - diff, 0);
      mHeader.setTranslationY(translationY);
    }
      mOldOffsetY = mOffsetY;
    }
  });
}

簡単にですが、レイアウトのxmlのサンプルも

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_height="match_parent"
  android:layout_width="match_parent"
  android:orientation="vertical">
  <ListView
        android:id="@+id/contents_list"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
  </ListView>
  <LinearLayout
    android:id="@+id/store_top_header"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:layout_height="wrap_content">
  </LineaLayout>
</FrameLayout>

長くなりましたが、このような感じでスクロールの移動距離を取得する事が出来ましたー。