12
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

RecyclerViewはカスタムビュー化して使おう

Posted at

最近業務でRecyclerViewを使ったViewを大量に作っていたのですが、他のViewと比べて必要な記述量が多いこともあり、基本カスタムビュー化してしまっていいのではと思いました。

例えば、以下のようなGooglePlayストアのような画面を作るとします。
play.gif
ここまで独立したViewの場合、Activityに直接書こうという人は少ないかもしれませんがRecyclerViewの一例です。
以下のようなカスタムビューを作ります。

※ 以下のコードではDataBinding, Glideを使用しています。

HorizontalAppListView
public class HorizontalAppListView extends RelativeLayout {
    public HorizontalAppListView(Context context) {
        this(context, null);
    }

    public HorizontalAppListView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public HorizontalAppListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        ViewHorizontalAppListBinding binding = ViewHorizontalAppListBinding.inflate(LayoutInflater.from(context), this, true);

        HorizontalAppListAdapter adapter = new HorizontalAppListAdapter();
        adapter.addApps(generateApps());

        RecyclerView recyclerView = binding.appList;
        recyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false));
        recyclerView.setAdapter(adapter);
        recyclerView.setHasFixedSize(true);
    }

    private class HorizontalAppListAdapter extends RecyclerView.Adapter<HorizontalAppListAdapter.HorizontalAppListViewHolder> {
        private List<AppData> apps = new ArrayList<>();

        @Override
        public HorizontalAppListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new HorizontalAppListViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.row_app_card, parent, false));
        }

        @Override
        public void onBindViewHolder(HorizontalAppListViewHolder holder, int position) {
            holder.render(position);
        }

        @Override
        public int getItemCount() {
            return apps.size();
        }

        void addApps(List<AppData> apps) {
            this.apps.addAll(apps);
            notifyDataSetChanged();
        }

        class HorizontalAppListViewHolder extends RecyclerView.ViewHolder {
            private RowAppCardBinding appCardBinding;

            HorizontalAppListViewHolder(View itemView) {
                super(itemView);
                appCardBinding = RowAppCardBinding.bind(itemView);
            }

            void render(final int position) {
                Glide.with(getContext()).load(apps.get(position).getDrawableId()).into(appCardBinding.appImage);
                appCardBinding.appTitle.setText(apps.get(position).getName());
                appCardBinding.appScore.setText(apps.get(position).getScore());
                appCardBinding.appImage.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        Toast.makeText(getContext(), apps.get(position).getName() + " is Clicked", Toast.LENGTH_LONG).show();
                    }
                });
            }
        }
    }

    private List<AppData> generateApps() {
        // ダミーデータ : 画像, アプリ名, 評価
        List<AppData> apps = new ArrayList<>();
        apps.add(new AppData(R.drawable.gmail, "Gmail", "4.0"));
        apps.add(new AppData(R.drawable.chrome, "Chrome", "4.1"));
        apps.add(new AppData(R.drawable.game, "Google Play Games", "4.2"));
        apps.add(new AppData(R.drawable.map, "Map", "4.3"));
        apps.add(new AppData(R.drawable.home, "Google Home", "4.4"));
        apps.add(new AppData(R.drawable.music, "Google Play Music", "4.5"));
        apps.add(new AppData(R.drawable.news, "Google News", "4.6"));
        Collections.shuffle(apps);
        return apps;
    }

カード部分をカスタムビューにしていますが、ケースによってはRecyclerViewを継承しても良いと思います。
とりあえずカスタムビューにしておくことで、以下のようなメリットがあります。

名前がつく

上記の例ではHorizontalAppListViewをいう名前にしました。名前が良いかはさておき、なんかアプリのリストを水平に表示するViewなんだなと分かると思います。
これによって、Activityで実際に利用する際にも分かりやすくなります。

MainActivity
HorizontalAppListView appListView = new HorizontalAppListView();
// or 
HorizontalAppListView appListView = (HorizontalAppListView) findViewById(R.id.app_list_view);

設定などをActivityから切り離せる

RecyclerViewは柔軟性が高いとよく言われますが、柔軟性が高いということは、カスタマイズが必要ということで、カスタマイズが必要ということは、それだけ設定のための記述量が増えるということです。
LayoutManagerや、ItemDecoration、SnapHelper、OnScrollChangeListenerなど標準の使い方をするだけでも多くの設定が必要です。
これらを毎回Activityに書いていると肥大化の原因になります。

HorizontalAppListView
RecyclerView recyclerView = binding.appList;
recyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false));
recyclerView.setAdapter(adapter);
recyclerView.setHasFixedSize(true);

Adapter、ViewHolderも内部クラスで持てる

内部クラスにするかどうかは好みもあるかもしれませんが、上記の設定多い問題と関連して、Adapter + ViewHolderもそこそこのコード量になります。
内部クラスに持つことで、Viewと紐付けて管理でき、ViewHolderでClick処理も書いてしまえば、このカスタムビューからどこに遷移するのかなども追いやすくなります。

差し替えが容易

後からこの部分を差し替えたいとなった際にカスタムビューに切り出してあると、Activityからその部分を外して、新しいカスタムビューと差し替えれば良いので、非常に楽です。
また、使わなくなったカスタムビューも残しておけるので、ABテストや試してから戻すようなことも容易になります。
シンプルな画面なので、Activityに直接書いても大丈夫と思っていると、後から追加や変更が入って辛くなります。

まとめ

ライフサイクル依存の処理や、View同士の連携などは一手間加える必要があるかもしれませんが、Activityから操作するようなメソッド生やしたり、RxJavaやEventBus等でだいたいなんとかなるので、とりあえずカスタムビュー化していいのではと思いました。

12
12
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
12
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?