リサイクルアダプターをきれいにします。 パート1

はじめに







リスト、リスト、リスト...垂直、水平、結合。 ほとんどのモバイルアプリケーションはそれらなしではできません。 さらに、多くの場合、アプリケーションはリストのみで構成されます。



そして、「同種の」リストに何も問題がない場合、さまざまなタイプのセルがすでに質問を提起する可能性があります。





if-elseの何が問題なの



全体として、「ifas」( if-elseおよびswitch-case構造がここに含まれています)では、バイナリ選択(たとえば、ゼロの悪名高いチェック)に使用される限り、心配する必要はありません。 しかし、オプションの数が2つの答えを超える場合、これはコードの何が問題で、どのように修正するかを考える機会です。



それでは、なぜ選択演算子の豊富さが悪いのでしょうか?



まあ、最初に、ある場所のifasはほとんど常にコードの他の場所のifasを生成するためです。 したがって、編集する場所は1から無限(制限内)になります。 また、変更が必要な場合は、「別の場所」を変更するのを忘れがちです。



第二に、再び限界の状況を見ると、無限の数の選択肢を得ることができます。 「メイン」クラスのコードでは見苦しく、潜在的な(そして非常に可能性の高い)エラーの場所になります。



そして第三に、多くの「ifas」は基本的に悪いマナーであり、建築の観点から見ると、すべてが一見すると思われるほどバラ色ではないという兆候です。



さて、どうすれば状況を修正できますか?



問題を解決するには、少なくとも2つの方法があります。



まず、いわゆる「表形式の方法」(Steve McConnellの「Perfect Code」)を使用し、選択ロジックを準備済みのデータのセットに置き換えます。 第一に、いコードの量を排除し、第二に、外部ソースを使用してこのデータを提供できるため、コード自体を変更する必要がなくなります。



第二に、ファクトリパターン(4つのギャングの「デザインパターン」)を使用して、選択ロジックを1つの場所にカプセル化できます-(主な任務に加えて-同じタイプの新しいオブジェクトの生成を隠します-ファクトリは選択ロジックもカプセル化できます)。 これは、前の方法のように「ifas」を完全に排除するものではありませんが、そのような場所の数を1つに減らすことができます。 したがって、コードはより美しくなり、保守が容易になります。 変更が発生した場合、これは1か所で行う必要があります。



型チェックの何が問題になっていますか



型チェック自体は何も悪いことをしません。 さらに、Androidの世界最大のプレーヤーのソースコードを見ても、そのようなチェックに遭遇することがよくありました。



それにもかかわらず、私の意見では、型チェックはアーキテクチャの省略です(ところで、Scott Myersは私に同意します)。 そして、そのようなチェックを取り除く機会があれば、これをしなければなりません。



どうやって?



最初に思い浮かぶのは、おなじみの「表形式の方法」です。 たとえば、タイプマップのコレクションを準備して、タイプマッチングを事前に定義できます。



そして2つ目。 しかし、もはやそのような明確な推奨事項はありません。 すべては特定のケースに依存します。 可能であれば、Java Genericsを使用してみてください。 多態性などのシステムのプロパティを非常に注意深く見ることができます。 sayingにもあるように、「インターフェースはどこでも動作します。」



型キャストの何が問題なのか



タイプキャストはAndroid SDK( findViewById()getSystemService()など )でも見つけることができますが、この手順では安全ではありません。 型キャストは常にClassCastExceptionによるアプリケーションクラッシュの潜在的な脅威をもたらします



try-catchブロックで「カースト」をラップするのは最善の方法ではありません。 まず、このデザイン自体は見苦しいです。 そして第二に、そのような問題を見つけるのは非常に困難です。なぜなら、 クラッシュはなく、アプリケーションは予期しない動作をします。



オプションとして
ところで、良い解決策は、致命的でない例外をすべて送信するようにFabricを構成することです。 「正しい」決定のための人件費がその使用の利益を上回る場合があります。 したがって、私が繰り返し言ったように、これは状況を修正する方法を考える機会です。 そして、決定が「高すぎる」場合、...これは考える機会です。



いずれにしても、型キャストは最良の選択ではありません。 そしてそれを避ける方が良いです。



どうやって?



主なレシピのうち、これらはJavaジェネリックとポリモーフィズムです。 また、Visitorパターン(4つのギャングの「デザインパターン」)の存在を考慮することも役立ちます。



「伝統的な」アプローチ



問題を整理しました。 ここで、「異種」リストを「従来の」方法で表示するタスクがどのように解決されるかを思い出してみましょう。 それは「伝統的」です。第一に、インターネットはそのように行動するように私たちに告げ、第二に、私の個人的な観察によれば、圧倒的な数のジュニアと抑制されない数の中間者がまさにそれを行うからです。



たとえば、次の3種類のセルがあります。



ProgressVo.java
/** * Just a marker for progress header/footer. */ public class ProgressVo { }
      
      







AdVo.java
 public class AdVo { private String title; private String description; // Getters, Setters, Builder, etc. }
      
      







UserVo.java
 public class UserVo { private String firstName; private String lastName; private String age; // Getters, Setters, Builder, etc. }
      
      







最初に、各タイプのセルに対して定数を宣言する必要があります。



 private static final int TYPE_PROGRESS = 10; private static final int TYPE_AD = 20; private static final int TYPE_USER = 30;
      
      





次に、それぞれ特定の位置のセルのタイプを決定する必要があります(位置の計算で地獄を避けるために、リスト内の異なるタイプのセルの任意の場所では、型なしコレクションを使用するのが最も簡単です):



 @Override public int getItemViewType(int position) { Object item = itemList.get(position); if (item instanceof ProgressVo) { return TYPE_PROGRESS; } else if (item instanceof AdVo) { return TYPE_AD; } else if (item instanceof UserVo) { return TYPE_USER; } else { throw new NoSuchRecyclerItemTypeException(); } }
      
      





そして、適切なホルダーを作成します。



 @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); if (viewType == TYPE_PROGRESS) { View view = inflater.inflate(R.layout.cell_progress, parent, false); return new UsersRecyclerAdapter.ProgressViewHolder(view); } else if (viewType == TYPE_AD) { View view = inflater.inflate(R.layout.cell_ad, parent, false); return new UsersRecyclerAdapter.AdViewHolder(view); } else if (viewType == TYPE_USER) { View view = inflater.inflate(R.layout.cell_user, parent, false); return new UsersRecyclerAdapter.UserViewHolder(view); } else { throw new NoSuchRecyclerViewTypeException(); } }
      
      





また、ホルダーをデータに接続します。



 @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (holder instanceof ProgressViewHolder) { // Do nothing. } else if (holder instanceof AdViewHolder) { ((AdViewHolder) holder).bind((AdVo) itemList.get(position)); } else if (holder instanceof UserViewHolder) { ((UserViewHolder) holder).bind((UserVo) itemList.get(position)); } }
      
      





クラス全体は次のとおりです。



UsersUglyRecyclerAdapter.java
 public class UsersUglyRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int TYPE_PROGRESS = 10; private static final int TYPE_AD = 20; private static final int TYPE_USER = 30; private List itemList = new ArrayList(); public UsersUglyRecyclerAdapter() { itemList.add(new ProgressVo()); } @Override public int getItemViewType(int position) { Object item = itemList.get(position); if (item instanceof ProgressVo) { return TYPE_PROGRESS; } else if (item instanceof AdVo) { return TYPE_AD; } else if (item instanceof UserVo) { return TYPE_USER; } else { throw new NoSuchRecyclerItemTypeException(); } } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); if (viewType == TYPE_PROGRESS) { View view = inflater.inflate(R.layout.cell_progress, parent, false); return new UsersRecyclerAdapter.ProgressViewHolder(view); } else if (viewType == TYPE_AD) { View view = inflater.inflate(R.layout.cell_ad, parent, false); return new UsersRecyclerAdapter.AdViewHolder(view); } else if (viewType == TYPE_USER) { View view = inflater.inflate(R.layout.cell_user, parent, false); return new UsersRecyclerAdapter.UserViewHolder(view); } else { throw new NoSuchRecyclerViewTypeException(); } } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (holder instanceof ProgressViewHolder) { // Do nothing. } else if (holder instanceof AdViewHolder) { ((AdViewHolder) holder).bind((AdVo) itemList.get(position)); } else if (holder instanceof UserViewHolder) { ((UserViewHolder) holder).bind((UserVo) itemList.get(position)); } } @Override public int getItemCount() { return itemList.size(); } public void setUsers(List<UserVo> users) { itemList.clear(); itemList.addAll(users); decorateItemList(); notifyDataSetChanged(); } private void decorateItemList() { int listSize = itemList.size(); int shift = 0; for (int i = 1; i < listSize; i++) { if (i % 7 == 0) { itemList.add(i + shift, new AdVo()); shift++; } } itemList.add(new ProgressVo()); } protected static class ProgressViewHolder extends RecyclerView.ViewHolder { public ProgressViewHolder(View itemView) { super(itemView); } } protected static class AdViewHolder extends RecyclerView.ViewHolder { public AdViewHolder(View itemView) { super(itemView); } public void bind(AdVo ad) { // Bind ad... } } protected static class UserViewHolder extends RecyclerView.ViewHolder { public UserViewHolder(View itemView) { super(itemView); } public void bind(UserVo user) { // Bind user... } } }
      
      







最後に何がありますか? 選択ロジック、豊富な「ifof」、型チェック、型変換を備えた3つの場所。 変更が必要な場合は、すでに4つの場所があります(スケーリング時の新しいホルダーの作成を除く)。



混乱のようなものすべて。 状況を修正する方法を考えてみましょう。



よりクリーンなアプローチ



示された問題を解決するには、いくつかの方法があります。 この記事のフレームワークでは、 enumが「テーブル」として機能する「表形式メソッド」の適応を使用します。



私たちの目標は、コードを「1行」形式にすることです。



 @Override public int getItemViewType(int position) { return CellType.get(itemList.get(position)).type(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return CellType.get(viewType).viewHolder(parent); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { CellType.get(itemList.get(position)).bind(holder, itemList.get(position)); }
      
      





そのため、最初に使用するセルのタイプを決定する必要があります。



 private enum CellType { PROGRESS, AD, USER }
      
      





セルのタイプを決定し、適切なホルダーを作成するために必要なものから始めましょう。



 private enum CellType { PROGRESS { @Override int type() { return R.layout.cell_progress; } @Override RecyclerView.ViewHolder viewHolder(ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.cell_progress, parent, false); return new ProgressViewHolder(view); } }, AD { @Override int type() { return R.layout.cell_ad; } @Override RecyclerView.ViewHolder viewHolder(ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.cell_ad, parent, false); return new AdViewHolder(view); } }, USER { @Override int type() { return R.layout.cell_user; } @Override RecyclerView.ViewHolder viewHolder(ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.cell_user, parent, false); return new UserViewHolder(view); } }; abstract int type(); abstract RecyclerView.ViewHolder viewHolder(ViewGroup parent); }
      
      





セルレイアウトのidが viewTypeとして使用されるという事実に注意を喚起したいと思います。 したがって、第一に、定数を定義する必要がなく、第二に、一意のIDが競合状態を除外します。 一部のライブラリは、特定の定数を自分用に予約できますが、現在のコードベースではこれを行っています。 そして、そのようなことは簡単に忘れられ、最終的に不快な結果につながります。



なぜなら getItemViewType()およびonBindViewHolder()メソッドのandroid SDKはコレクション内の要素の位置を使用し、 onCreateViewHolder()メソッドはviewType変数を使用します。対応するenumを取得するには2つのメソッドが必要です。



 private enum CellType { PROGRESS { @Override boolean is(Object item) { return item instanceof ProgressVo; } ... }, AD { @Override boolean is(Object item) { return item instanceof AdVo; } ... }, USER { @Override boolean is(Object item) { return item instanceof UserVo; } ... }; static CellType get(Object item) { for (CellType cellType : CellType.values()) { if (cellType.is(item)) { return cellType; } } throw new NoSuchRecyclerItemTypeException(); } static CellType get(int viewType) { for (CellType cellType : CellType.values()) { if (cellType.type() == viewType) { return cellType; } } throw new NoSuchRecyclerViewTypeException(); } abstract boolean is(Object item); ... }
      
      





この場合のis()メソッドは、「内部ニーズ」にのみ使用されます。



ホルダーとデータを接続するためだけに残ります。



 private enum CellType { PROGRESS { ... @Override void bind(RecyclerView.ViewHolder holder, Object item) { // Do nothing. } }, AD { ... @Override void bind(RecyclerView.ViewHolder holder, Object item) { try { AdViewHolder adViewHolder = (AdViewHolder) holder; AdVo ad = (AdVo) item; adViewHolder.bind(ad); } catch (ClassCastException e) { L.printStackTrace(e); } } }, USER { ... @Override void bind(RecyclerView.ViewHolder holder, Object item) { try { UserViewHolder userViewHolder = (UserViewHolder) holder; UserVo user = (UserVo) item; userViewHolder.bind(user); } catch (ClassCastException e) { L.printStackTrace(e); } } }; ... abstract void bind(RecyclerView.ViewHolder holder, Object item); }
      
      





結果のクラスは次のとおりです。



UsersRecyclerAdapter.java
 public class UsersRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private List itemList = new ArrayList(); public UsersRecyclerAdapter() { itemList.add(new ProgressVo()); } @Override public int getItemViewType(int position) { return CellType.get(itemList.get(position)).type(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return CellType.get(viewType).viewHolder(parent); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { Object item = itemList.get(position); CellType.get(item).bind(holder, item); } @Override public int getItemCount() { return itemList.size(); } public void setUsers(List<UserVo> users) { itemList.clear(); itemList.addAll(users); decorateItemList(); notifyDataSetChanged(); } private void decorateItemList() { int listSize = itemList.size(); int shift = 0; for (int i = 1; i < listSize; i++) { if (i % 7 == 0) { itemList.add(i + shift, new AdVo()); shift++; } } itemList.add(new ProgressVo()); } private enum CellType { PROGRESS { @Override boolean is(Object item) { return item instanceof ProgressVo; } @Override int type() { return R.layout.cell_progress; } @Override RecyclerView.ViewHolder viewHolder(ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.cell_progress, parent, false); return new ProgressViewHolder(view); } @Override void bind(RecyclerView.ViewHolder holder, Object item) { // Do nothing. } }, AD { @Override boolean is(Object item) { return item instanceof AdVo; } @Override int type() { return R.layout.cell_ad; } @Override RecyclerView.ViewHolder viewHolder(ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.cell_ad, parent, false); return new AdViewHolder(view); } @Override void bind(RecyclerView.ViewHolder holder, Object item) { try { AdViewHolder adViewHolder = (AdViewHolder) holder; AdVo ad = (AdVo) item; adViewHolder.bind(ad); } catch (ClassCastException e) { L.printStackTrace(e); } } }, USER { @Override boolean is(Object item) { return item instanceof UserVo; } @Override int type() { return R.layout.cell_user; } @Override RecyclerView.ViewHolder viewHolder(ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.cell_user, parent, false); return new UserViewHolder(view); } @Override void bind(RecyclerView.ViewHolder holder, Object item) { try { UserViewHolder userViewHolder = (UserViewHolder) holder; UserVo user = (UserVo) item; userViewHolder.bind(user); } catch (ClassCastException e) { L.printStackTrace(e); } } }; static CellType get(Object item) { for (CellType cellType : CellType.values()) { if (cellType.is(item)) { return cellType; } } throw new NoSuchRecyclerItemTypeException(); } static CellType get(int viewType) { for (CellType cellType : CellType.values()) { if (cellType.type() == viewType) { return cellType; } } throw new NoSuchRecyclerViewTypeException(); } abstract boolean is(Object item); abstract int type(); abstract RecyclerView.ViewHolder viewHolder(ViewGroup parent); abstract void bind(RecyclerView.ViewHolder holder, Object item); } protected static class ProgressViewHolder extends RecyclerView.ViewHolder { public ProgressViewHolder(View itemView) { super(itemView); } } protected static class AdViewHolder extends RecyclerView.ViewHolder { public AdViewHolder(View itemView) { super(itemView); } public void bind(AdVo ad) { // Bind ad... } } protected static class UserViewHolder extends RecyclerView.ViewHolder { public UserViewHolder(View itemView) { super(itemView); } public void bind(UserVo user) { // Bind user... } } }
      
      







もう少し「純度」



別の方法として、型チェックを別の「表形式のメソッド」に置き換えることができます。 マップコレクションを使用して、タイプの一致を確認できます。



is()メソッドを削除し、対応するマップコレクションを初期化します



 private enum CellType { ... static Map<Class, CellType> typeTable = new HashMap<>(); static { typeTable.put(ProgressVo.class, PROGRESS); typeTable.put(AdVo.class, AD); typeTable.put(UserVo.class, USER); } static CellType get(Object item) { return typeTable.get(item.getClass()); } … }
      
      





このアプローチは代替手段として考慮されるべきです。 つまり それはそのような半分の尺度であり(タイプチェックの問題は解決しましたが、タイプ変換には影響しませんでした)、 列挙型コントラクトも簡素化します。



これは何を脅かすのですか?



そして、変更行うという戦いの 最中に 、このtypeTableを忘れてNPEを取得するのは非常に簡単です。



「フル」契約をサポートすることで(is()メソッドについて話している)、この状況は除外されます。



おわりに



それで、私たちは何を逃げ出しましたか?



これから始めました:



Uいアダプター
  private static final int TYPE_PROGRESS = 10; private static final int TYPE_AD = 20; private static final int TYPE_USER = 30; @Override public int getItemViewType(int position) { Object item = itemList.get(position); if (item instanceof ProgressVo) { return TYPE_PROGRESS; } else if (item instanceof AdVo) { return TYPE_AD; } else if (item instanceof UserVo) { return TYPE_USER; } else { throw new NoSuchRecyclerItemTypeException(); } } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); if (viewType == TYPE_PROGRESS) { View view = inflater.inflate(R.layout.cell_progress, parent, false); return new UsersRecyclerAdapter.ProgressViewHolder(view); } else if (viewType == TYPE_AD) { View view = inflater.inflate(R.layout.cell_ad, parent, false); return new UsersRecyclerAdapter.AdViewHolder(view); } else if (viewType == TYPE_USER) { View view = inflater.inflate(R.layout.cell_user, parent, false); return new UsersRecyclerAdapter.UserViewHolder(view); } else { throw new NoSuchRecyclerViewTypeException(); } } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (holder instanceof ProgressViewHolder) { // Do nothing. } else if (holder instanceof AdViewHolder) { ((AdViewHolder) holder).bind((AdVo) itemList.get(position)); } else if (holder instanceof UserViewHolder) { ((UserViewHolder) holder).bind((UserVo) itemList.get(position)); } }
      
      







そしてこれに来ました:



きれいなアダプター
  @Override public int getItemViewType(int position) { return CellType.get(itemList.get(position)).type(); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return CellType.get(viewType).holder(parent); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { Object item = itemList.get(position); CellType.get(item).bind(holder, item); } private enum CellType { PROGRESS { @Override boolean is(Object item) { return item instanceof ProgressVo; } @Override int type() { return R.layout.cell_progress; } @Override RecyclerView.ViewHolder holder(ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.cell_progress, parent, false); return new ProgressViewHolder(view); } @Override void bind(RecyclerView.ViewHolder holder, Object item) { // Do nothing. } }, AD { @Override boolean is(Object item) { return item instanceof AdVo; } @Override int type() { return R.layout.cell_ad; } @Override RecyclerView.ViewHolder holder(ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.cell_ad, parent, false); return new AdViewHolder(view); } @Override void bind(RecyclerView.ViewHolder holder, Object item) { try { AdViewHolder adViewHolder = (AdViewHolder) holder; AdVo ad = (AdVo) item; adViewHolder.bind(ad); } catch (ClassCastException e) { L.printStackTrace(e); } } }, USER { @Override boolean is(Object item) { return item instanceof UserVo; } @Override int type() { return R.layout.cell_user; } @Override RecyclerView.ViewHolder holder(ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.cell_user, parent, false); return new UserViewHolder(view); } @Override void bind(RecyclerView.ViewHolder holder, Object item) { try { UserViewHolder userViewHolder = (UserViewHolder) holder; UserVo user = (UserVo) item; userViewHolder.bind(user); } catch (ClassCastException e) { L.printStackTrace(e); } } }; static CellType get(Object item) { for (CellType cellType : CellType.values()) { if (cellType.is(item)) { return cellType; } } throw new NoSuchRecyclerItemTypeException(); } static CellType get(int viewType) { for (CellType cellType : CellType.values()) { if (cellType.type() == viewType) { return cellType; } } throw new NoSuchRecyclerViewTypeException(); } abstract boolean is(Object item); abstract int type(); abstract RecyclerView.ViewHolder holder(ViewGroup parent); abstract void bind(RecyclerView.ViewHolder holder, Object item); }
      
      







記事の冒頭で特定した問題について見ていきましょう。





既存のセルに変更を加える(顧客の要望が急速に変化する)ためと、新しいセルを追加するための場所が1つだけあります。 さらに、新しいタイプのセルを追加するときに、何かを忘れてしまう可能性があります。 現在の列挙型のコントラクトを維持するために必要です。 シンプルで安全。





面倒でいロジックの選択はもうありません。 このロジックが使用された場所は3つありません。 したがって、エラーのリスクは除外されます。 そして同僚はあえてしません。





この記事の枠組みにおけるこの質問は、部分的に未解決のままです。 彼らが言うように、一度にすべてではありません。



提起された4つの質問のうち3つ半が解決されました。 それでは、すべての問題を解決できないのに、なぜこの方法を提案したのですか?



まあ、まず、1つの記事に関する情報がたくさんあります。



第二に、このアプローチはほとんどの問題を解決し、コードの開発と保守を簡素化します。



そして第三に、この方法は非常にシンプルで簡潔かつ高速です。 つまり 労働規模-ここでの利益は明らかに利益側にあります。 そして、かなりのメリットがあります。



はい、トピックはややあいまいに見え、論争を引き起こす可能性があります。 しかし、以来 リストはモバイルアプリケーションのほぼ重要な役割であり、Android SDKはすぐに異種のセルを操作する美しい方法を提供しないため、この問題を解決するための良い方法の1つを共有する必要があることがわかりました。



2番目のパートでは、型チェックと型変換を取り除く方法について説明します。



更新する



私は記事に短い説明を書かなければなりません コメントで誤解がありました。 この記事では、 アイデアを提示し、問題を解決するためのアプローチを示します。 可能な限り単純な言語、可能な限り単純なコード(1つのクラス、列挙、明示的なローカル変数など)。



このアイデアの実装は、個人的な好みに基づいて異なる場合があります。 しかし、実践が示しているように、人々は記事から既製のソリューションを待っています。 つまり 問題解決する方法ではなく、この方法の具体的な実装です



さて、注意してください。



コメントでは、同志r_iiは脳をオンにし、アイデアの彼の実現を示しました。 以下では、自分自身を共有します(可能な質問の次の波を予想するために、コードにクリック処理オプションをすぐに含めます)。



アイデアの私の実装
 public class UsersArbitraryCellAdapter extends ArbitraryCellAdapter { private ProgressArbitraryCell progressArbitraryCell = new ProgressArbitraryCell(); private AdArbitraryCell adArbitraryCell = new AdArbitraryCell(); private UserArbitraryCell userArbitraryCell = new UserArbitraryCell(); public UsersArbitraryCellAdapter() { arbitraryCellSelector.addCell(progressArbitraryCell); arbitraryCellSelector.addCell(adArbitraryCell); arbitraryCellSelector.addCell(userArbitraryCell); } public Observable<AdVo> asAdObservable() { return adArbitraryCell.asAdObservable(); } public Observable<UserVo> asUserObservable() { return userArbitraryCell.asUserObservable(); } // Set Users, Ads, Progress... } public abstract class ArbitraryCellAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { protected ArbitraryCellSelector arbitraryCellSelector = new ArbitraryCellSelector(); protected List itemList = new ArrayList(); @Override public final int getItemViewType(int position) { return arbitraryCellSelector.getCell(itemList.get(position)).type(); } @Override public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return arbitraryCellSelector.getCell(viewType).holder(parent); } @Override public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { Object item = itemList.get(position); arbitraryCellSelector.getCell(item).bind(holder, item); } @Override public final int getItemCount() { return itemList.size(); } } public abstract class ArbitraryCellHolder<T> extends RecyclerView.ViewHolder { public ArbitraryCellHolder(View itemView) { super(itemView); ButterKnife.bind(this, itemView); } public abstract void bind(T item); } public final class ArbitraryCellSelector { private List<Cell> cellList = new ArrayList<>(); public void addCell(Cell cell) { cellList.add(cell); } public void removeCell(Cell cell) { cellList.remove(cell); } public Cell getCell(Object item) { for (Cell cell : cellList) { if (cell.is(item)) { return cell; } } throw new NoSuchRecyclerRowException(); } public Cell getCell(int viewType) { for (Cell cell : cellList) { if (cell.type() == viewType) { return cell; } } throw new NoSuchRecyclerRowException(); } public interface Cell { boolean is(Object item); int type(); RecyclerView.ViewHolder holder(ViewGroup parent); void bind(RecyclerView.ViewHolder holder, Object item); } } public class AdArbitraryCell implements ArbitraryCellSelector.Cell { private PublishSubject<AdVo> adPublishSubject = PublishSubject.create(); @Override public boolean is(Object item) { return item instanceof AdVo; } @Override public int type() { return R.layout.cell_ad; } @Override public RecyclerView.ViewHolder holder(ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.cell_ad, parent, false); return new AdViewHolder(view); } @Override public void bind(RecyclerView.ViewHolder holder, Object item) { try { AdViewHolder adViewHolder = (AdViewHolder) holder; AdVo ad = (AdVo) item; adViewHolder.bind(ad); } catch (ClassCastException e) { L.printStackTrace(e); } } public Observable<AdVo> asAdObservable() { return adPublishSubject; } protected class AdViewHolder extends ArbitraryCellHolder<AdVo> { @BindView(R.id.ad_text_view) protected TextView adTextView; public AdViewHolder(View itemView) { super(itemView); } @Override public void bind(AdVo item) { adTextView.setText(item.getTitle()); itemView.setOnClickListener(view -> adPublishSubject.onNext(item)); } } } // Other arbitrary cells...
      
      







同志zagayevskiyは、AdapterDelegatesと呼ばれるHannes Dorfmannライブラリーに注目を集めました。 アプローチは堅実に見え、ソリューションはエレガントです。 お勧めです。



All Articles