duyojiぶろぐ

技術系ときどき日常系

Paing ListView (1ページずつスクロールするListView)

iPhoneアプリNaver画像検索のような画象を縦に1ページずつ
めくって見るようなUIをAndroidでも実装してみた。
(Android版のNaver画象検索もiPhoneと同じような動きをすれば良いのにと
個人的に思う。)

ソースコード

とりあえずgithubソースコードをアップした。
https://github.com/duyoji/PagingListView


PagingListView.java

package test.duyoji.listview;

import test.duyoji.util.SizeUtil;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
import android.widget.AbsListView;
import android.widget.ListView;

@SuppressLint({ "NewApi", "NewApi" })
public class PagingListView extends ListView {
	
	private Context context;
	private int screenHeight;	
	private int nowPage = 0;
	private int totalPage = 0;	
	
	//constructor----------------------------------------------------------------	
	public PagingListView(Context context) {
		super(context);
		this.context = context;
		init();
	}

	public PagingListView(Context context, AttributeSet attrs) {
		super(context, attrs);
		this.context = context;
		init();
	}

	public PagingListView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		this.context = context;
		init();		
	}
	
	//interface------------------------------------------------------------------			
	public static interface OnPagingListener {
		public void onScrollStart(int _nowPage);
		public void onScrollFinish(int _nowPage);
		public void onNextListLoad(int _nowPage);
	}
		
	private OnPagingListener listener = null;
	
	public void setPagingListener(OnPagingListener listener) {
		this.listener = listener;
	}
	
	//setter, getter --------------------------------------------------------------	
	public void setNowPage(int _nowPage){
		this.nowPage = _nowPage;		
	}
			
	public void setTotalPage(int totalPage){
		this.totalPage = totalPage;
	}

	public int getNowPage(){
		return nowPage;
	}
		
	//paging scroll method---------------------------------------------------------------------------	
	public void scrollNextPage(){
		View firstVisibleView = getChildAt(0);
		nowPage += 1;
		smoothScrollBy(screenHeight-SizeUtil.getStatusBarHeight(context)-Math.abs(firstVisibleView.getTop())-1, 400);
	}
		
	public void scrollPrevPage(){
		View firstVisibleView = getChildAt(0);
		nowPage -= 1;
		smoothScrollBy(firstVisibleView.getTop(), 400); // Stops the listview from overshooting.
	}
	
	//initialize ---------------------------------------------------------------
	private void init(){
		WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();        
        this.screenHeight = display.getHeight();                  
        setScrollEvent();
	}

	
	//setOnScrollListener event-----------------------------------------------------	
	private void setScrollEvent(){
		setOnScrollListener(new OnScrollListener() {
			private boolean flingFlag = false;
			
			@Override
			public void onScrollStateChanged(AbsListView view, int scrollState) {
				switch (scrollState) {
				 
			    // スクロールしていない
			    case OnScrollListener.SCROLL_STATE_IDLE:			    				    				    	
			    	if (flingFlag) {
			    		flingFlag = false;
			    		if (listener != null) {
							listener.onScrollFinish(nowPage);
						}
					}else{
						postDelayed(new Runnable() {
				    	    public void run() {
				    	    	int savedPosition = getFirstVisiblePosition();
				    	    	View firstVisibleView = getChildAt(0);
				    	    	
				    	    	if (firstVisibleView.getHeight()/2.0 < Math.abs(firstVisibleView.getTop())) { 
				    	    		nowPage = savedPosition;
						    		smoothScrollBy(screenHeight-SizeUtil.getStatusBarHeight(context)-Math.abs(firstVisibleView.getTop()-1), 400); 
						    	}else{			    		
						    		nowPage = savedPosition;
						    		smoothScrollBy(firstVisibleView.getTop(), 400); // Stops the listview from overshooting.
						    	}
				    	    		
				    	    	if (listener != null) {
									listener.onScrollFinish(nowPage);
								}
				    	    } 
				    	}, 50);
					}
			    				    	
			        break;
			 
			    // スクロール中
//			    case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:			    	
//			        break;
			 
			    // はじいたとき
			    case OnScrollListener.SCROLL_STATE_FLING:			    				    				    
			    	flingFlag = true;
			    	postDelayed(new Runnable() {
			    	    public void run() {
			    	    	int savedPosition = getFirstVisiblePosition();
			    	    	View firstVisibleView = getChildAt(0);			    	    				    	    	

			    	    	if (savedPosition < nowPage){			    	    		
			    	    		if (nowPage+1 == totalPage ) {
			    	    			int firstVisibleItem = nowPage-getFirstVisiblePosition();
									View lastView = getChildAt(firstVisibleItem);
			    	    			if (lastView.getTop() > 0) {
			    	    				scrollPrevPage();
			    	    			}
								}else{
									scrollPrevPage();
								}
					    	}else if (0 < savedPosition || ( 0 == savedPosition && 0 > firstVisibleView.getTop() ) ){					    		
					    		if (nowPage != totalPage-1) {
									scrollNextPage();
								} 		
					    	}
			    	    	
			    	    	listener.onScrollStart(nowPage);
			    	    } 			    	    
			    	}, 50);
			    				    	
			        break;
			    }
			}
			
			@Override
			public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
				if (listener != null) {
					listener.onNextListLoad(nowPage);
				}				
			}
		});
	}			
}

main.xml

<LinearLayout 
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent" >
    <test.duyoji.listview.PagingListView 
	    android:id="@+id/main_list"
	    android:layout_width="match_parent"
	    android:layout_height="match_parent"
	    android:divider="#00000000"
		android:dividerHeight="0dip"
		android:scrollbars="none"
		android:fadingEdge="none"
		android:fadingEdgeLength="0dip"                
		android:scrollingCache="false"
        android:cacheColorHint="#00000000"/>

</LinearLayout>

item_paging_list.xml

<LinearLayout 
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent" >
    <ImageView
        android:id="@+id/item_paging_list_image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/ic_launcher"/>

</LinearLayout>

MainActivity.java

package test.duyoji.paginglist;

import java.util.ArrayList;
import test.duyoji.adapter.MenuAdapter;
import test.duyoji.listview.PagingListView;
import test.duyoji.listview.PagingListView.OnPagingListener;
import android.os.Bundle;
import android.app.Activity;

public class MainActivity extends Activity {	
	private PagingListView listView;
	private MenuAdapter adapter;	
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        init();
        setListItem();
    }
    
    private void init(){
    	this.listView = (PagingListView) findViewById(R.id.main_list);    	
    	this.listView.setPagingListener(new OnPagingListener() {			
			@Override
			public void onScrollStart(int _nowPage) {
				//ここにスクロールが開始したときの処理を書く
			}
			
			@Override
			public void onScrollFinish(int _nowPage) {
				//ここにスクロールが終了したときの処理を書く
			}
			
			@Override
			public void onNextListLoad(int _nowPage) {
				//ここにまだ読み込んでいない画象を追加したい時に処理を書く
			}
		});
    }
    
    //set listview items
  	private void setListItem(){
  		ArrayList<String> tmpArray = new ArrayList<String>();
  		for (int i = 0; i < 10; i++) 
  			tmpArray.add("tmp");
  		
  		listView.setTotalPage(tmpArray.size());
  		this.listView.setNowPage(0);
  		
  		adapter = new MenuAdapter(this, tmpArray);
  		listView.setAdapter(adapter);
  	}
    
}

MainAdapter.java

package test.duyoji.adapter;

import java.util.ArrayList;
import test.duyoji.paginglist.R;
import test.duyoji.util.SizeUtil;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;

@SuppressLint("NewApi")
public class MainAdapter extends BaseAdapter {	
	private ArrayList<String> items;	
	private LinearLayout.LayoutParams llp;
	private LayoutInflater inflater;	
	
	public MainAdapter(Context context, ArrayList<String> items) {
		this.items = items;		
		this.inflater = LayoutInflater.from(context);
		WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();                
        this.llp = new LinearLayout.LayoutParams(display.getWidth(), display.getHeight()-SizeUtil.getStatusBarHeight(context));        
	}
	
	public void addItems(ArrayList<String> _items){
		items.addAll(items);
		notifyDataSetChanged();
	}
	
	//View使い回し用
	private static class ViewHolder{
		private ImageView imageView;
	}	

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		final ViewHolder holder;						
		if (convertView == null) {	
			convertView = inflater.inflate(R.layout.item_paging_list, null);
			ImageView imageView = (ImageView) convertView.findViewById(R.id.item_paging_list_image);						
			imageView.setLayoutParams(llp);
			holder = new ViewHolder();
			holder.imageView = imageView;						
			convertView.setTag(holder);
		}else{		
			holder = (ViewHolder) convertView.getTag();
		}													
		holder.imageView.setImageResource(R.drawable.ic_launcher);		
		return convertView;
	}

	@Override
	public int getCount() {
		return items.size();
	}

	@Override
	public String getItem(int position) {
		return items.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

}

SizeUtil.java

package test.duyoji.util;

import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;

public class SizeUtil {
	private static final int LOW_DPI_STATUS_BAR_HEIGHT = 19;
	private static final int MEDIUM_DPI_STATUS_BAR_HEIGHT = 25;
	private static final int HIGH_DPI_STATUS_BAR_HEIGHT = 38;
	
	//ステータスバーの高さを取得する
	public static final int getStatusBarHeight(Context context) {
		DisplayMetrics displayMetrics = new DisplayMetrics();
		((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(displayMetrics);
		int statusBarHeight;
		switch (displayMetrics.densityDpi) {
		case DisplayMetrics.DENSITY_HIGH:
			statusBarHeight = HIGH_DPI_STATUS_BAR_HEIGHT;
			break;
		case DisplayMetrics.DENSITY_MEDIUM:
			statusBarHeight = MEDIUM_DPI_STATUS_BAR_HEIGHT;
			break;
		case DisplayMetrics.DENSITY_LOW:
			statusBarHeight = LOW_DPI_STATUS_BAR_HEIGHT;
			break;
		default:
			statusBarHeight = MEDIUM_DPI_STATUS_BAR_HEIGHT;
		}
		return statusBarHeight;
	}
}

説明

PagingListView

Interfaceを実装しており、スクロールがスタートしたとき,終わったとき,次の画像を読み込みたい時用に
三つ用意している。(OnPagingListener)

setter,getter部分ではListViewのトータルのアイテム数と現在のページをセットする。
getNowpage()を使うことで外部からインスタンスを経由して現在のページを取得できる。

スクロールようのメソッドがあり外部からでもページング出来るようにしている。
「次へ」ボタン「前へ」ボタンを用意して、scrollNextPage(),scrollPrevPage()を呼べば
1ページ分スクロールするようにしている。

init()ではPagingListViewの初期化を行っていて、1ページ分スクロールするため画面から
ステータスバーを引いた画面高さを取得している。
setScrollEvent()でOnScrollListenerを利用してフリックやフリックでないときの1ページスクロール処理
を記述している。

main.xml

main.xmlは単純な構造で先ほど説明したPaginListViewをセットしているだけ。

item_paging_list.xml

PagingListViewにセットするコンテンツ。
あとで説明する、MainAdapter.javaでgetViewメソッドのconvertViewで使う。
内容はLinearLayoutにImageViewを入れただけの単純な構造。

MainActivity.java

main.xmlをsetContentViewしていて,init()で初期化している。
listView.setPagingListener()内でここでは処理を記述していないが、
スクロール開始時,スクロール終了時,新しい画象を読み込む時の処理を書く。

setListItem()で気をつけて欲しいのはPagingListViewに現在のページとトータルのアイテム数を
記述すること。これが無いと挙動がおかしくなる。

listView.setTotalPage(tmpArray.size());
this.listView.setNowPage(0);

MainAdapter.java

ViewHolderを使って大量の画象を使っても落ちないように
ImageViewを使い回している。
LinearLayout.LayoutParamsで画象の横幅,高さのセットをして
setLayoutParamsでそのインスタンスをセットする。

SizeUtil.java

これは端末の解像度によってステータスバーの高さを返しているだけのクラス。
PagingListViewとMainAdapter.javaでスクロールする距離と,画像の高さをセットする
ときに使っている。