簡単なリスト管理-RendererRecyclerViewAdapter(パート2)

前回、RecyclerViewでの作業を最適化したほか 、異なるリストでセルを再利用して新しいセルを簡単に追加する方法も学びました。



今日は分析します:





あなたは過去の記事が好きなら、私はあなたがこれを好きになると思います。



Diffiff



DiffUtilとは何ですか、分解する必要はないと思います。 おそらく、各開発者は自分のプロジェクトで既にテストし、アニメーションとパフォーマンスの形で素敵なパンを受け取っています。



最初の記事が公開されてから最初の数日で、DiffUtilの実装を含むプルリクエストを受け取りました。どのように実装されているかを見てみましょう。 最適化の結果、パブリックメソッドsetItems(ArrayList <ItemModel> items)を備えたアダプターが得られたことを思い出してください。 このフォームでは、DiffUtilを使用するのはあまり便利ではありません。リストの古いコピーをさらにどこかに保存する必要があります。その結果、次のようになります。



... final MyDiffCallback diffCallback = new MyDiffCallback(getOldItems(), getNewItems()); final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback); mRecyclerViewAdapter.setItems(getNewItems()); diffResult.dispatchUpdatesTo(mRecyclerViewAdapter); ...
      
      





DiffUtil.Callbackの古典的な実装
 public class MyDiffCallback extends DiffUtil.Callback { private final List<BaseItemModel> mOldList; private final List<BaseItemModel> mNewList; public MyDiffCallback(List<BaseItemModel> oldList, List<BaseItemModel> newList) { mOldList = oldList; mNewList = newList; } @Override public int getOldListSize() { return mOldList.size(); } @Override public int getNewListSize() { return mNewList.size(); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { return mOldList.get(oldItemPosition).getID() == mNewList.get( newItemPosition).getID(); } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { BaseItemModel oldItem = mOldList.get(oldItemPosition); BaseItemModel newItem = mNewList.get(newItemPosition); return oldItem.equals(newItem); } @Nullable @Override public Object getChangePayload(int oldItemPosition, int newItemPosition) { return super.getChangePayload(oldItemPosition, newItemPosition); } }
      
      







そして、拡張ItemModelインターフェース:



 public interface BaseItemModel extends ItemModel { int getID(); }
      
      





一般に、それは実現可能であり、難しくはありませんが、これを複数の場所で行う場合は、なぜ同じコードなのかを考える必要があります。 DiffUtil.Callbackの実装の一般的なポイントを引き出してみましょう。



 public abstract static class DiffCallback <BM extends ItemModel> extends DiffUtil.Callback { private final List<BM> mOldItems = new ArrayList<>(); private final List<BM> mNewItems = new ArrayList<>(); void setItems(List<BM> oldItems, List<BM> newItems) { mOldItems.clear(); mOldItems.addAll(oldItems); mNewItems.clear(); mNewItems.addAll(newItems); } @Override public int getOldListSize() { return mOldItems.size(); } @Override public int getNewListSize() { return mNewItems.size(); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { return areItemsTheSame( mOldItems.get(oldItemPosition), mNewItems.get(newItemPosition) ); } public abstract boolean areItemsTheSame(BM oldItem, BM newItem); @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { return areContentsTheSame( mOldItems.get(oldItemPosition), mNewItems.get(newItemPosition) ); } public abstract boolean areContentsTheSame(BM oldItem, BM newItem); ... }
      
      





一般に、それは非常に普遍的に判明し、ルーチンを取り除き、主要メソッドであるareItemsTheSame()およびareContentsTheSame()に焦点を合わせました。これらは実装に必須であり、異なる場合があります。



getChangePayload()メソッドの実装は意図的に省略されており、その実装はsourcesで見ることができます



これで、別のDiffUtil対応メソッドをアダプターに追加できます。



 public void setItems(List<ItemModel> items, DiffCallback diffCallback) { diffCallback.setItems(mItems, items); final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback); mItems.clear(); mItems.addAll(items); diffResult.dispatchUpdatesTo(this); }
      
      





一般に、これはすべてDiffUtilで行われます。必要に応じて、抽象クラスDiffCallbackを使用し、2つのメソッドのみを実装します。



今、私たちはウォームアップして記憶をリフレッシュしたと思います。これは、私たちがより興味深いものに進むことができることを意味しています。



ネストされたRecyclerView



多くの場合、顧客の意志またはデザイナーの動向により、ネストされたリストがアプリケーションに表示されます。 最近まで、私はそれらが好きではありませんでした、私はそのような問題に遭遇しました:





これらの問題の一部は疑わしく解決が容易であり、最初の記事から最適化されたアダプターを接続すると解消されるものもあります:)。 しかし、少なくとも、実装の複雑さは残ります。 要件を定式化しましょう:





ここで、セルとリストアイテムの概念を分けていることに注意することが重要です。

リスト項目は、RecyclerViewで使用されるエンティティです。

cell -1種類のリスト項目を表示できるクラスのセット。この場合、これは既知のクラスとインターフェイスの実装です:ViewRenderer、ItemModel、ViewHolder。



そして、私たちが持っているもの。 私たちにとって重要なインターフェイスはItemModelです。これを使い続けることが便利であることは明らかです。 複合モデルには子モデルが含まれている必要があり、新しいインターフェイスを追加します。



 public interface CompositeItemModel extends ItemModel { List<ItemModel> getItems(); }
      
      





それぞれ、見栄えがよく、複合ViewRendererは子レンダラーについて知っている必要があります-追加:



 public abstract class CompositeViewRenderer <M extends CompositeItemModel, VH extends CompositeViewHolder> extends ViewRenderer<M, VH> { private final ArrayList<ViewRenderer> mRenderers = new ArrayList<>(); public CompositeViewRenderer(int viewType, Context context) { super(viewType, context); } public CompositeViewRenderer(int viewType, Context context, ViewRenderer... renderers) { super(viewType, context); Collections.addAll(mRenderers, renderers); } public CompositeViewRenderer registerRenderer(ViewRenderer renderer) { mRenderers.add(renderer); return this; } public void bindView(M model, VH holder) {} public VH createViewHolder(ViewGroup parent) { return ...; } ... }
      
      





ここで、子レンダラーを追加するための2つの方法を追加しました。これらは私たちにとって役立つはずです。

また、一般的なCompositeViewHolderにも注意してください。これは、コンポジットViewHolderの別のクラスにもなりますが、これはまだわかりません。 そして、CompositeViewRendererを引き続き使用し、bindView()、createViewHolder()という2つの必須メソッドがまだあります。 createViewHolder()では、アダプターを初期化してレンダラーに導入する必要があり、bindView()では、要素のデフォルトの単純な更新を行います。



 public abstract class CompositeViewRenderer <M extends CompositeItemModel, VH extends CompositeViewHolder> extends ViewRenderer<M, VH> { private final ArrayList<ViewRenderer> mRenderers = new ArrayList<>(); private RendererRecyclerViewAdapter mAdapter; ... public void bindView(M model, VH holder) { mAdapter.setItems(model.getItems()); mAdapter.notifyDataSetChanged(); } public VH createViewHolder(ViewGroup parent) { mAdapter = new RendererRecyclerViewAdapter(); for (final ViewRenderer renderer : mRenderers) { mAdapter.registerRenderer(renderer); } return ???; } ... }
      
      





ほぼ判明しましたが、createViewHolder()メソッドのこのような実装ではviewHolder自体が必要なので、ここで初期化することはできません-別個の抽象メソッドを作成すると同時に、RecyclerViewにアダプターを導入します。 :



 public abstract class CompositeViewHolder extends RecyclerView.ViewHolder { public RecyclerView mRecyclerView; public CompositeViewHolder(View itemView) { super(itemView); } }
      
      





 public abstract class CompositeViewRenderer <M extends CompositeItemModel, VH extends CompositeViewHolder> extends ViewRenderer<M, VH> { public VH createViewHolder(ViewGroup parent) { mAdapter = new RendererRecyclerViewAdapter(); for (final ViewRenderer renderer : mRenderers) { mAdapter.registerRenderer(renderer); } VH viewHolder = createCompositeViewHolder(parent); viewHolder.mRecyclerView.setLayoutManager(createLayoutManager()); viewHolder.mRecyclerView.setAdapter(mAdapter); return viewHolder; } public abstract VH createCompositeViewHolder(ViewGroup parent); protected RecyclerView.LayoutManager createLayoutManager() { return new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false); } ... }
      
      





はい、そうです! LinearLayoutManagerでデフォルトの実装を追加しました:(これはもっと便利だと思いました。必要であれば、メソッドをオーバーロードして別のLayoutManagerを設定できます。



これがすべてのようで、特定のクラスを実装し、何が起こったのかを見ることが残っています:



SomeCompositeItemModel
 public class SomeCompositeItemModel implements CompositeItemModel { public static final int TYPE = 999; private int mID; private final List<ItemModel> mItems; public SomeCompositeItemModel(final int ID, List<ItemModel> items) { mID = ID; mItems = items; } public int getID() { return mID; } public int getType() { return TYPE; } public List<ItemModel> getItems() { return mItems; } }
      
      







SomeCompositeViewHolder
 public class SomeCompositeViewHolder extends CompositeViewHolder { public SomeCompositeViewHolder(View view) { super(view); mRecyclerView = (RecyclerView) view.findViewById(R.id.composite_recycler_view); } }
      
      







SomeCompositeViewRenderer
 public class SomeCompositeViewRenderer extends CompositeViewRenderer<SomeCompositeModel, SomeCompositeViewHolder> { public SomeCompositeViewRenderer(int viewType, Context context) { super(viewType, context); } public SomeCompositeViewHolder createCompositeViewHolder(ViewGroup parent) { return new SomeCompositeViewHolder(inflate(R.layout.item_composite, parent)); } }
      
      







コンポジットレンダラーを登録します。



 public class SomeActivity extends AppCompatActivity { protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... SomeCompositeViewRenderer composite = new SomeCompositeViewRenderer( SomeCompositeItemModel.TYPE, this, new SomeViewRenderer(SomeModel.TYPE, this, mListener) ); mRecyclerViewAdapter.registerRenderer(composite); ... } ... }
      
      





最後のサンプルからわかるように、クリックをサブスクライブするには、必要なインターフェイスをレンダラーコンストラクターに渡すだけであるため、ルートプレースはこのインターフェイスを実装し、必要なすべてのクリックを認識します。



進む例をクリック
 public class SomeViewRenderer extends ViewRenderer<SomeModel, SomeViewHolder> { private final Listener mListener; public SomeViewRenderer(int type, Context context, Listener listener) { super(type, context); mListener = listener; } public void bindView(SomeModel model, SomeViewHolder holder) { ... holder.itemView.setOnClickListener(new View.OnClickListener() { public void onClick(final View view) { mListener.onSomeItemClicked(model); } }); } ... public interface Listener { void onSomeItemClicked(SomeModel model); } }
      
      







おわりに



ネストされたリストを操作する際に十分な汎用性と柔軟性を実現し、可能な限り複合セルを追加するプロセスを簡素化しました。 これで、新しい複合セルを簡単に追加し、ネストされたリストとメインリストの単一セルを簡単に結合できます。



デモ、より詳細な実装、およびいくつかの問題の解決策はこちらから入手できます



All Articles