何を作ったか
Android でおなじみの ListView ですが、水平方向へのスクロールは出来ません。そこで、RecyclerView を継承して横方向へスクロールする ListView のようなウェジェットを作ります。
RecyclerView とは?
複数の View を良しなに表示するウェジェットです。かなり自由度が高いので、大抵のものはこれでできます。詳細に関しては、【Android】RecyclerViewの基本的な実装 が詳しいです。
完成品
import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
public class HorizontalListView extends RecyclerView{
public interface OnItemClickListener{
void onItemClick(View view, int position);
}
public HorizontalListView(Context context){
super(context);
initialize(context);
}
public HorizontalListView(Context context, AttributeSet set){
super(context, set);
initialize(context);
}
public HorizontalListView(Context context, AttributeSet set, int defaultAttr){
super(context, set, defaultAttr);
initialize(context);
}
private void initialize(Context context){
LinearLayoutManager manager = new LinearLayoutManager(context);
manager.setOrientation(LinearLayoutManager.HORIZONTAL);
setLayoutManager(manager);
}
private OnItemClickListener mListener;
public void setOnItemClickListener(OnItemClickListener listener){
mListener = listener;
Adapter adapter = getAdapter();
if ( adapter instanceof ArrayAdapter ){
ArrayAdapter a = (ArrayAdapter)adapter;
a.mListener = this.mListener;
}
}
@Override
public void setAdapter(RecyclerView.Adapter adapter){
super.setAdapter(adapter);
if ( adapter instanceof ArrayAdapter ){
ArrayAdapter a = (ArrayAdapter)adapter;
a.mListener = this.mListener;
}
}
@Override
protected void onDetachedFromWindow(){
super.onDetachedFromWindow();
setOnItemClickListener(null);
setAdapter(null);
setLayoutManager(null);
}
private static class SimpleViewHolder extends ViewHolder{
private SimpleViewHolder(View view){
super(view);
}
}
public static abstract class ArrayAdapter<E> extends RecyclerView.Adapter<ViewHolder> {
public ArrayAdapter(List<E> list){
mDataList = new ArrayList<>(list.size());
mViews = new ArrayList<>(list.size());
mDataList.addAll(list);
}
private List<E> mDataList;
private OnItemClickListener mListener;
private List<View> mViews;
/**
* リスト要素となるViewをインスタンス化する
* @param parent
* @return null not acceptable
*/
public abstract View getView(ViewGroup parent);
/**
* リストに表示するデータをViewへ反映する
* @param view 反映先のView
* @param data 反映させるデータ{@link #getItem(int)}でも取得可能
* @param position リスト上での位置
*/
public abstract void onBindView(View view, E data, int position);
@Override
public final ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
return new SimpleViewHolder(getView(parent));
}
@Override
public final void onBindViewHolder(final ViewHolder holder, int position){
final View view = holder.itemView;
E data = getItem(position);
onBindView(view, data, position);
mViews.add(holder.itemView);
holder.itemView.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v){
if ( mListener != null ){
mListener.onItemClick(view, holder.getAdapterPosition());
}
}
});
}
@Override
public void onDetachedFromRecyclerView(RecyclerView recyclerView){
super.onDetachedFromRecyclerView(recyclerView);
for ( View view : mViews ) view.setOnClickListener(null);
mViews = null;
mListener = null;
mDataList = null;
}
@Override
public final int getItemCount(){
return mDataList.size();
}
public E getItem(int position){
return mDataList.get(position);
}
}
}
実装の説明
横スクロールの実現
横方向を指定した LinearLayoutManager を RecyclerView に設定するだけでOK。
private void initialize(Context context){
LinearLayoutManager manager = new LinearLayoutManager(context);
manager.setOrientation(LinearLayoutManager.HORIZONTAL);
setLayoutManager(manager);
}
アダプターの用意
RecyclerView も ListView と同様に、データと表示する View を管理するアダプターが必要となります。そこで、RecyclerView.Adapter を継承して HorizontalListView.ArrayAdapter を用意。使用時に実装すべきは以下の2つ。
public abstract View getView(ViewGroup parent);
public abstract void onBindView(View view, E data, int position);
getView(ViewGroup) で表示に使う View をインフレートして、onBindView(View,E,int) でデータ E の内容を View へ反映させます。
ListView を使う時、android.widget.ArrayAdapter を継承したアダプターでレイアウトをカスタマイズするのと同じ感じで操作できるように、同様なAPIを持たせました。
コールバックの用意
ListView と同様にリストの要素がクリックされたら通知するようなコールバックが欲しいですね。作りましょう。
public interface OnItemClickListener{
void onItemClick(View view, int position);
}
クリックされた View と表示位置を通知します。実装としては、先ほど作った HorizontalListView.ArrayAdapter が getView(ViewGroup) で取得して表示する View に OnClickListener をセットして、受け取ったコールバックを中継します。ただし、注意として RecyclerView#setAdapter() で設定するアダプターが HorizontalListView.ArrayAdapter でないと機能しません。