📜 ⬆️ ⬇️

Easy work with lists - RendererRecyclerViewAdapter (part 2)

Last time, we optimized work with RecyclerView , and also learned how to reuse cells in different lists and easily add new ones.

Today we will analyze:


If you liked the last article, I think you will like this one.

Diffutil


What is DiffUtil, I think you should not disassemble. Probably every developer has already tested it in his project and got nice buns in the form of animation and performance.
')
In the first days after the publication of the first article, I received a pull request with the implementation of DiffUtil, let's see how it is implemented. I recall that as a result of optimization, we have an adapter with the public method setItems (ArrayList <ItemModel> items). In this form, it is not very convenient to use DiffUtil, we need somewhere else to keep the old copy of the list, as a result we will get something like this:

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

Classic implementation of 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); } } 


And the advanced ItemModel interface:

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

In general, it is realizable and not difficult, but if it is done in several places, then it is worth considering why there are so many of the same code. Let's try to make general points in our implementation of 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); ... } 

In general, it turned out quite universally, we got rid of the routine and focused on the main methods - areItemsTheSame () and areContentsTheSame (), which are mandatory for implementation and may differ.

The implementation of the getChangePayload () method is intentionally omitted; its implementation can be viewed in the source code .

Now we can add another method with DiffUtil support to our adapter:

 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); } 

In general, with DiffUtil, that's all, now, if necessary, we use our abstract class - DiffCallback, and implement only two methods.

I think now we have warmed up and refreshed our memory, which means we can move on to more interesting things.

Nested RecyclerView


Often, at the will of the customer or the trend of designers, nested lists appear in our application. Until recently, I disliked them, I encountered such problems:


Some of these problems are dubious and easily solved, and some will go away if you connect our optimized adapter from the first article :). But, at least, the complexity of the implementation will remain. Let's formulate our requirements:


It is important to note that here I have separated the concept of a cell and an element of the list:
list item is an entity used in RecyclerView.
A cell is a set of classes that allow you to display one type of list item, in our case it is an implementation of previously known classes and interfaces: ViewRenderer, ItemModel, ViewHolder.

And so what we have. The key interface for us is ItemModel, it is obvious that it will be convenient for us to continue with it and work. Our composite model should contain child models, we add a new interface:

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

It looks good, respectively, the composite ViewRenderer should know about the child renderers - add:

 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 ...; } ... } 

Here I added two ways to add child renderers, I'm sure they will be useful to us.
Also pay attention to generic CompositeViewHolder - it will also be a separate class for the composite ViewHolder, which I don’t know yet. Now let's continue working with CompositeViewRenderer, we have two required methods - bindView (), createViewHolder (). In createViewHolder (), you need to initialize the adapter and introduce it to the renderers, and in bindView () we will do a simple, default update of the elements:

 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 ???; } ... } 

Almost turned out, as it turned out, for such an implementation in the createViewHolder () method, we need the viewHolder itself, we cannot initialize it here - we create a separate abstract method, at the same time I would like to introduce our adapter to RecyclerView, which we can take from an unimplemented CompositeViewHolder - we implement :

 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); } ... } 

Yes, right! I added the default implementation with LinearLayoutManager: (I thought it would bring more benefits, and if necessary, you can overload and set another LayoutManager.

It seems that this is all, it remains to implement specific classes and see what happens:

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)); } } 


Register our composite renderer:

 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); ... } ... } 

As can be seen from the last sample, to subscribe to clicks, we simply transfer the necessary interface to the renderer's constructor, so our root place implements this interface and knows about all the necessary clicks

Example of forwarding clicks
 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); } } 


Conclusion

We have achieved sufficient versatility and flexibility when working with nested lists, maximally simplified the process of adding composite cells. Now we can easily add new composite cells and easily combine single cells in nested and master lists.

Demonstration, more detailed implementation and solutions of some problems are available here .

Source: https://habr.com/ru/post/337774/


All Articles