LoginSignup
53
54

More than 5 years have passed since last update.

GridViewからRecyclerViewへの乗り換え

Posted at

今日の話

リスト表示に使っている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でGridViewRecyclerViewに置き換える

GridViewでこのように書いていたものを.

activity_list.xml
<GridView
    android:id="@android:id/list"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipToPadding="false"
    android:numColumns="2" />

RecyclerViewでは,このように書き換えます.

activity_list.xml
<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の時はこんな感じでした.

ImageAdapter.java
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ではこうなります.

ImageAdapter.java
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の時はこうでした.

MyListActivity.java
mGridView = (GridView) findViewById(android.R.id.list);
mAdapter = new ImageAdapter(mAppContext, mDataSet);
mGridView.setAdapter(mAdapter);

RecyclerViewではちょっと長くなります.

MyListActivity.java
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を渡しています.

終わり

乗り換えたことで何か得したかというと,今のところ何も得していません.(笑)
また,上には書いていませんが,OnScrollListerOnItemClickListener
GridViewRecyclerViewとの間で変更点があって,とまどいました.

そのあたりについては,おまけに書きました.
困っている人の助けになれれば幸いです.

おまけ

乗り換えの時に,個人的に困ったことについて書きます.

OnScrollListenerの実装がGridViewの時と違う

変わった点

  • firstVisibleItem, visibleItemCount, totalItemCountが引数として渡されなくなる
  • onScroll(...)からonScrolled(...)に名前が変わる
  • setOnScrollListener(listener)からaddOnScrollListener(listener)に名前が変わる

GridViewの時はScrollListenerの中がこんな感じでした.

MyListActivity.java
@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になると.

MyListActivity.java
@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のときは,何も問題ありません.

MyListActivity.java
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がリスト内のどの位置にいるのか分からないことに気づきました.

ImageAdapter.java
@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)をタグとして,記録しておくようにしました.

ImageAdapter.java
@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というファイルを作り,その中で下のように定義しています.

integers.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <integer name="list_pos_key">1</integer>
</resources>

参考文献

Creating Lists and Cards | Android Developers

53
54
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
53
54