今日の話
リスト表示に使っているGridViewをRecyclerViewに置き換えました.
やったことは,大きく分けて以下の3つです.
- layout.xmlでGridViewをRecyclerViewに置き換える
- AdapterをRecyclerView用に書き換える
- ActivityでGridViewをRecyclerViewに置き換える
今日は,このことについて書いていきます.
ライブラリのインポート
app配下のbuild.gradleに
compile 'com.android.support:recyclerview-v7:22.2.1'
などと書けばOKです.
dependencies {
compile 'com.android.support:appcompat-v7:22.2.1'
compile 'com.android.support:recyclerview-v7:22.2.1'
}
appcompatとrecyclerviewのバージョンは揃えないと注意されるので,気をつけてください.
自分は最初,次のようにして,注意されました.(v7:22とv7:21でずれてる)
dependencies {
compile 'com.android.support:appcompat-v7:22.2.1'
compile 'com.android.support:recyclerview-v7:21.0.0'
}
では,内容に入っていきます.
layout.xmlでGridView
をRecyclerView
に置き換える
GridViewでこのように書いていたものを.
<GridView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:numColumns="2" />
RecyclerViewでは,このように書き換えます.
<android.support.v7.widget.RecyclerView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"/>
なくなったリストの列数の指定(android:numColumns
)は,Activity内で行います.
AdapterをRecyclerView用に書き換える
GridView
ではArrayAdapter<ListItem>
を使っていましたが,
RecyclerView
では,RecyclerView.Adapter<ImageAdapter.ViewHolder>
を使います.
GridViewの時はこんな感じでした.
public class ImageAdapter extends ArrayAdapter<ListItem> {
private final LayoutInflater mInflater;
public static class ViewHolder {
ImageView imageview;
}
public ImageAdapter(Context context, List<ListItem> objects) {
super(context, 0, objects);
mInflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public View getView(int position, View convertView,
ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.row_image_list, parent, false);
holder = new ViewHolder();
holder.imageview = (ImageView) convertView.findViewById(R.id.image);
// you can also set view size here. like this
// ViewGroup.LayoutParams params = convertView.getLayoutParams();
// params.height = view_size;
// convertView.setLayoutParams(params);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// attach data to covertView
ListItem item = getItem(position);
holder.imageview.setImageResource(item.img_id);
return convertView;
}
}
RecyclerViewではこうなります.
public class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ViewHolder> {
private final LayoutInflater mInflater;
private List<ListItem> mDataSet;
public static class ViewHolder extends RecyclerView.ViewHolder {
public ImageView imageview;
public ViewHolder(View v) {
super(v);
imageview = (ImageView) v.findViewById(R.id.image);
}
}
public ImageAdapter(Context context, List<ListItem> myDataSet) {
mDataSet = myDataSet;
mInflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public ImageAdapter.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
// Create a new view.
View v = mInflater.inflate(R.layout.row_image_list, viewGroup, false);
// you can also set view size here. like this
// ViewGroup.LayoutParams params = v.getLayoutParams();
// params.height = view_size;
// v.setLayoutParams(params);
return new ImageAdapter.ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
// attach data to covertView
ListItem item = mDataSet.get(position);
holder.imageview.setImageResource(item.img_id);
}
@Override
public int getItemCount() {
return mDataSet.size();
}
}
いろいろ変わったように見えますが,新しくできたメソッドによって,
書く位置が変わっただけで,やっていることは同じです.
変更点をまとめると
- ViewHolderをRecyclerView.ViewHolderを継承するように変える
- Listに表示するデータは,Constructorで受け取ってAdapterのフィールドとして持っておく
-
if (convertView == null)
の内容をonCreateViewHolder(...)
に移す -
getItem(position)
してデータをバインドするところは,onBindViewHolder(...)
に移す - 新しくできた
getItemCount()
で,データ数を返すようにする.
ActivityでGridViewをRecyclerViewに置き換える
GridViewの時はこうでした.
mGridView = (GridView) findViewById(android.R.id.list);
mAdapter = new ImageAdapter(mAppContext, mDataSet);
mGridView.setAdapter(mAdapter);
RecyclerViewではちょっと長くなります.
mRecyclerView = (RecyclerView) findViewById(android.R.id.list);
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new GridLayoutManager(mAppContext, COLUMN_NUM);
mRecyclerView.setLayoutManager(mLayoutManager);
mAdapter = new MyAdapter(myDataset);
mRecyclerView.setAdapter(mAdapter);
GridView
にはなかった,setHasFixedSize()
とsetLayoutManager()
が出てきました.
setHasFixedSize()
は,リストのコンテンツの大きさがデータによって変わらないならtrue
を渡します.
これをRecyclerView
に知らせることで,パフォーマンスが良くなるそうです.
setLayoutManager()
に渡すLayoutManager
によって,
RecyclerViewに1列表示なのか,Grid表示なのかなどを教えてあげます.
ここでは,Grid表示にしたいのでGridLayoutManager
を渡しています.
終わり
乗り換えたことで何か得したかというと,今のところ何も得していません.(笑)
また,上には書いていませんが,OnScrollLister
やOnItemClickListener
も
GridView
とRecyclerView
との間で変更点があって,とまどいました.
そのあたりについては,おまけに書きました.
困っている人の助けになれれば幸いです.
おまけ
乗り換えの時に,個人的に困ったことについて書きます.
###OnScrollListenerの実装がGridViewの時と違う
変わった点
-
firstVisibleItem
,visibleItemCount
,totalItemCount
が引数として渡されなくなる -
onScroll(...)
からonScrolled(...)
に名前が変わる -
setOnScrollListener(listener)
からaddOnScrollListener(listener)
に名前が変わる
GridViewの時はScrollListenerの中がこんな感じでした.
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
//load next item
if (!end_flg && !busy.get() && totalItemCount - firstVisibleItem <= LOAD_ITEM_NUM) {
int page = totalItemCount / LOAD_ITEM_NUM;
updateList(page, false); // false means do not refresh
}
// if it's loading and reached to bottom of the list, then show loading animation.
if (busy.get() && firstVisibleItem + visibleItemCount == totalItemCount) {
mProgress.setVisibility(View.VISIBLE);
}
}
それが,RecyclerViewになると.
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
GridLayoutManager layoutManager = (GridLayoutManager)recyclerView.getLayoutManager();
int firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
int visibleItemCount = layoutManager.getChildCount();
int totalItemCount = layoutManager.getItemCount();
//load next item
if (!end_flg && !busy.get() && totalItemCount - firstVisibleItem <= LOAD_ITEM_NUM) {
int page = totalItemCount / LOAD_ITEM_NUM;
updateList(page, false); // false means do not refresh
}
// if it's loading and reached to bottom of the list, then show loading animation.
if (busy.get() && firstVisibleItem + visibleItemCount == totalItemCount) {
mProgress.setVisibility(View.VISIBLE);
}
}
自分でfirstVisibleItem
などを用意します.
ちなみに,RecyclerViewの引数にあるdx
,dy
はそれぞれ,スクロールの横,縦の変化量だそうです.
setOnItemClickListener()
がなくなる
RecyclerViewではsetOnItemClickListener(listener)
がなくなってるので,
クリック時のアクションはRecyclerView.Adapter
内で,
setOnClickListener()
を使って実装しました.
とりあえず実装はできましたが,もっと良いやり方があると思います.
GridViewのときは,何も問題ありません.
mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String parsed_json = gson.toJson(mAdapter.getItem(position));
Intent intent = new Intent(getActivity(), DetailActivity.class);
intent.putExtra(DetailActivity.EXTRA_CONTENT, parsed_json);
startActivity(intent);
}
});
RecyclerViewでは,AdapterのonCreateViewHolder
でviewにClickListerをセットすれば良いだろうと思いました.
ただ,実装してみると,クリックされたViewがリスト内のどの位置にいるのか分からないことに気づきました.
@Override
public ImageAdapter.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View v = mInflater.inflate(R.layout.row_image_list, viewGroup, false);
ImageView imageview = (ImageView)v.findViewById(R.id.image);
imageview.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = ??? // We don't know its position
String parsed_json = gson.toJson(mDataSet.get(position));
Intent intent = new Intent(mContext, DetailActivity.class);
intent.putExtra(DetailActivity.EXTRA_CONTENT, parsed_json);
mContext.startActivity(intent);
}
});
return new ImageAdapter.ViewHolder(v);
}
そこで,Viewのリスト内の位置(position
)をタグとして,記録しておくようにしました.
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
// set list position as tag
holder.imageview.setTag(R.integer.list_pos_key, position);
// 省略
}
@Override
public ImageAdapter.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
// 省略
imageview.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = (Integer) v.getTag(R.integer.list_pos_key);
// 省略
}
}
}
onBindViewHolder
ではリスト内のposition
が分かるので,そこでクリックするviewにposition
をタグとしてくっつけるようにしました.
こうすることで,onClick()
の中でも,viewにくっつけておいたタグから,クリックされたデータのposition
が分かるようになりました.
また,タグのkeyに使っているR.integer.list_pos_key
は,
res/values/integers.xmlというファイルを作り,その中で下のように定義しています.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="list_pos_key">1</integer>
</resources>