ListViewで作っていたアプリに並べ替え機能を追加したかったのですが、並べ替え機能を実現するためにはRecyclerViewを使わないといけないようでした。
なので、ListViewからRecyclerViewへ載せ替えた内容について書きたいと思います。
RecyclerViewの実装方法については様々な記事で見つかるのですが、この記事ではListViewで実現していた事をRecyclerViewでどのように実現したかに焦点を当てています。
以下のように行を長押しすると移動ができます。
ソースコードの全容が知りたい場合にはgitHubでコードを公開しています。
https://github.com/highcom/ComicMemo
まだ、動作を知りたい場合にはgoogle playでアプリを公開しています。
では、説明をしていきます。
1.build.gradleにサポートライブラリを追加
dependenciesにサポートライブラリを使う事を記述します。
その際、minSdkVersionが14以上である事が必須で、compileSdkVersionとバージョンと合わせる事が推奨されます。
android {
compileSdkVersion 26
defaultConfig {
...
minSdkVersion 14
}
...
}
dependencies {
implementation 'com.android.support:recyclerview-v7:26.1.0'
}
2.レイアウトxmlのListViewタグを置き換える
リスト画面のxmlでListViewタグで定義していたものをRecyclerViewに置き換えます。
<ListView
android:id="@+id/comicListView"
...
android:layout_marginBottom="50dp" />
↓↓↓
<android.support.v7.widget.RecyclerView
android:id="@+id/comicListView"
...
android:layout_marginBottom="50dp" />
また、カスタムViewを使っていたので、1行ずつのレイアウトファイルとしてrow.xmlを定義しているのですがそちらに変更はありません。
3.継承するAdapterを置き換えて実装
3.1.継承するAdapterクラスを変更
RecyclerViewのAdapterを利用する場合、ViewHolderを継承したジェネリクスクラスの実装が必要です。
public class ListViewAdapter extends SimpleAdapter {
public class ViewHolder {
TextView title;
....
}
....
}
↓↓↓
public class ListViewAdapter extends RecyclerView.Adapter<ListViewAdapter.ViewHolder> {
public class ViewHolder extends RecyclerView.ViewHolder {
TextView title;
....
}
....
}
3.2.メソッドを修正
①Overrideするメソッドを変更
ListViewの場合は、SimpleAdapterを継承していたので、
・getView
・getCount
の2つをOverrideする必要がありましたが、RecyclerViewの場合には、
・onCreateViewHolder
・onBindViewHolder
・getItemCount
の3つをOverrideする必要があります。
②メソッド内で実装していた処理の移動
getView内で実装していた処理は、ViewHolderクラスのコンストラクタ内に移す必要があります。
③リスナーメソッドの独自実装
行選択時のリスナーメソッドについて、
ListViewではActivityクラスでListViewをインスタンス化する時にsetOnItemClickListenerで実装します(後述)。
RecyclerViewではリスナーメソッドをAdapter側で実装する必要があるので、onBindViewHolderメソッド内でリスナーメソッドを実装しています。
public class ListViewAdapter extends SimpleAdapter {
public class ViewHolder {
TextView title;
....
}
@Override
public int getCount() {
....
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
holder = new ViewHolder();
holder.title = (TextView) view.findViewById(android.R.id.title);
Button addbtn = (Button) view.findViewById(R.id.addbutton);
addbtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
....
}
});
}
}
↓↓↓
public class ListViewAdapter extends RecyclerView.Adapter<ListViewAdapter.ViewHolder> {
public class ViewHolder extends RecyclerView.ViewHolder {
TextView title;
....
public ViewHolder(final View itemView) {
super(itemView);
title = (TextView) itemView.findViewById(R.id.title);
Button addbtn = (Button) view.findViewById(R.id.addbutton);
addbtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
....
}
});
}
}
@Override
public int getItemCount() {
....
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(inflater.inflate(R.layout.row, parent, false));
}
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
holder.itemView.setTag(holder);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
....
}
});
}
}
4.行の区切り線を実装
ListViewでは行毎の区切り線はデフォルトで定義されていましたが、RecyclerViewでは独自で定義する必要があルため、ItemDecorationクラスを継承したDividerItemDecorationクラスを実装します。
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private Drawable mDivider;
private int mOrientation;
public DividerItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}
5.AdapterをViewにsetする
ListView/RecyclerView共に、Viewをインスタンス化してAdapterをsetする処理がありますが、以下のように変更します。
また、RecyclerViewでは区切り線を独自実装したので、その実装クラスもsetします。
public class ComicMemo extends Activity {
private ListView listView;
private ListViewAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
adapter = new ListViewAdapter();
listView = (ListView) findViewById(R.id.comicListView);
listView.setAdapter(adapter);
// アイテムクリック時ののイベントを追加
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent,
View view, int pos, long id) {
...
}
});
}
}
↓↓↓
public class ComicMemo extends Activity {
private RecyclerView recyclerView;
private ListViewAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
adapter = new ListViewAdapter();
recyclerView = (RecyclerView) findViewById(R.id.comicListView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
// セル間に区切り線を実装する
RecyclerView.ItemDecoration itemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST);
recyclerView.addItemDecoration(itemDecoration);
...
}
6.ItemTouchHelperクラスで並べ替え操作を実装
onCreateメソッド内に、並べ替え操作を実現するItemTouchHelperクラスを実装し、RecyclerViewにアタッチします。
今回はItemTouchHelperのSimpleCallBackクラスを継承して実装します。
SimpleCallbackクラスでは
・onMove
・onSwiped
メソッドをOverrideする必要があります。
また、どのタッチ操作を有効にするかはコンストラクタのパラメータで指定できます。
protected void onCreate(Bundle savedInstanceState) {
...
// ドラックアンドドロップの操作を実装する
ItemTouchHelper itemDecor = new ItemTouchHelper(
new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
ItemTouchHelper.ACTION_STATE_IDLE) {
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
final int fromPos = viewHolder.getAdapterPosition();
final int toPos = target.getAdapterPosition();
adapter.notifyItemMoved(fromPos, toPos);
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}
});
itemDecor.attachToRecyclerView(recyclerView);
}
以上で、ListViewからRecyclerViewに変更する実装手順の説明は終わりになります。
ListView自体はもうレガシーな扱いになっているので、変更できてちょうどよかった。
なお、ListViewの実装については以前の記事の「[Java]ボタン付きのカスタムセルのListViewを作成する」で記載していますので、こちらも参考にしてみてください。
また、シンプルな操作感のパスワード管理アプリ「パスワードメモ」のアプリをGoogle Playで公開しています。
様々なサイトのアカウント管理をこのアプリで管理できます。パスコードのロック付きなので覗き見対策も!
こちらも同様に RecyclerViewでの並べ替えが実装されています。


