Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RecyclerView: Tips and Recipes

GDG Cherkasy
February 24, 2017

RecyclerView: Tips and Recipes

Serhii Yaremych - Android developer, Master of Code Global
Я дам кілька порад, які точно стануть в нагоді. І Ви переконаєтесь, що працювати із безліччю viewType просто, а ItemDecorator не страшний.

GDG Cherkasy

February 24, 2017
Tweet

More Decks by GDG Cherkasy

Other Decks in Programming

Transcript

  1. • Do not animate views inside viewholder itself (e.g. calling

    itemView.animate()). ItemAnimator is the ONLY component that can animate views. Death 1: • Can’t recycle view properly. How to fix:
  2. • Granular adapter updates, notifyItemChanged(4) • Do not animate crossfade

    for all views • Never ever use notifyItemRangeChanged() this way notifyItemRangeChanged(0, getItemsCount()) • Use DiffUtil to manage adapter updates Death 2: • Too many ViewHolders of the same type. How to fix:
  3. Implement View.OnClickListener on RecyclerView.ViewHolder class and set itemView.setOnClickListener(this) in the

    constructor: class ColorViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
 ColorViewHolder(View itemView, ItemClickListener clickListener) {
 super(itemView);
 itemView.setOnClickListener(this);
 }
 @Override
 public void onClick(View v) {
 removeAtPosition(getAdapterPosition())
 }
 }
  4. • Use notifyItemChanged(), notifyItemRangeChanged(), notifyItemInserted(), notifyItemMoved(), notifyItemRemoved() and others notifyItem*()

    to visualize dataset changes with animations. • Use DiffUtil. It handles all change calculations for you and dispatches them to the adapter • Use setHasStableIds(true) with getItemId(int position) and RecyclerView will automatically handle all animations on simple call notifyDataSetChanged()
  5. Interface for adapters: public interface DelegateAdapter { RecyclerView.ViewHolder onCreateViewHolder(@NonNull LayoutInflater

    inflater, @NonNull ViewGroup parent); void onBindViewHolder(RecyclerView.ViewHolder holder, ViewItem viewItem); } Interface for items: public interface ViewItem { long distinctId(); int viewType(); }
  6. Implement ViewItem on item wrappers class BigPictureViewItem implements ViewItem class

    CommonViewItem implements ViewItem class HeaderViewItem implements ViewItem class HorizontalViewItem implements ViewItem
  7. The item wrapper public class BigPictureViewItem implements ViewItem { @NonNull

    private final BigPictureItem item; public BigPictureViewItem(@NonNull BigPictureItem item) { this.item = item; } @NonNull public BigPictureItem getItem() { return item; } @Override public long distinctId() { return item.hashCode(); } @Override public int viewType() { return R.layout.big_picture_item_layout; } }
  8. The adapter class BigPicturesAdapter implements DelegateAdapter { @Override public RecyclerView.ViewHolder

    onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { return new PictureViewHolder(inflater.inflate(R.layout.big_picture_item_layout, parent, false)); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, ViewItem viewItem) { final PictureViewHolder vh = (PictureViewHolder) holder; vh.bind((BigPictureViewItem) viewItem); } private static class PictureViewHolder extends RecyclerView.ViewHolder { // fields PictureViewHolder(View itemView) { super(itemView); // setup views } private void bind(BigPictureViewItem item) { // bind the item } } }
  9. Main adapter class MultiAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private final SparseArray<DelegateAdapter>

    adapters = new SparseArray<>(); private final List<ViewItem> items = new ArrayList<>(); private final ViewItem emptyItem = new ViewItem() {}; private final ViewItem progressItem = new ViewItem() {}; public MultiAdapter() { adapters.put(R.layout.header_item_layout, new HeaderItemsAdapter()); adapters.put(R.layout.common_item_layout, new CommonItemsAdapter()); adapters.put(R.layout.big_picture_item_layout, new BigPicturesAdapter()); adapters.put(R.layout.horizontall_item_layout, new HorizontalItemsAdapter()); adapters.put(R.layout.loading_item_layout, new ProgressItemAdapter()); adapters.put(R.layout.empty_item_layout, new EmptyItemAdapter()); // Add default item items.add(emptyItem); setHasStableIds(true); }
  10. Main adapter public void showProgressView() {} public void updateAdapter(@Nullable List<ViewItem>

    viewItems) {} @Override public long getItemId(int position) { return items.get(position).distinctId(); } @Override public int getItemViewType(int position) { return items.get(position).viewType(); }
  11. Main adapter @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    return adapters.get(viewType) .onCreateViewHolder(LayoutInflater.from(parent.getContext()), parent); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { adapters.get(getItemViewType(position)) .onBindViewHolder(holder, items.get(position)); } @Override public int getItemCount() { return items.size(); } }
  12. final List <ViewItem> items = new ArrayList<> (); final HeaderViewItem

    firstHeader = new HeaderViewItem("Header 1") items.add(firstHeader); for (int i = 0; i < 5; i++) { // create common type items items.add(item); } final List < ColorItem > subHorizontalItems = new ArrayList<> (); for (int i = 0; i < 15; i++) { final ColorItem colorItem = new ColorItem(); subHorizontalItems.add(colorItem); } final HorizontalViewItem horizontalItem = new HorizontalViewItem("Horizontal item #1", subHorizontalItems); items.add(horizontalItem); final HeaderViewItem secondHeader = new HeaderViewItem("Header 2"); items.add(secondHeader); for (int i = 0; i < 3; i++) { // create big picture type item items.add(viewItem); } Wrap all items into our ViewItem models
  13. class MultiDecorator extends RecyclerView.ItemDecoration { private final Drawable generalDivider; private

    final Drawable headerDivider; public MultiDecorator(Drawable generalDivider, Drawable headerDivider) { this.generalDivider = generalDivider; this.headerDivider = headerDivider; } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {} @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {} }
  14. @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State

    state) { super.getItemOffsets(outRect, view, parent, state); final int offset = generalDivider.getIntrinsicHeight() / 2; for (int i = 0; i < parent.getChildCount(); i++) { final View child = parent.getChildAt(i); final int adapterPosition = parent.getChildAdapterPosition(child); final RecyclerView.Adapter adapter = parent.getAdapter(); if (adapterPosition != RecyclerView.NO_POSITION) { final int viewType = adapter.getItemViewType(adapterPosition); if (isAcceptableViewType(viewType)) { outRect.top = offset; outRect.bottom = offset; } } } }
  15. @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {

    super.onDraw(c, parent, state); for (int i = 0; i < parent.getChildCount(); i++) { // find child, adapter, position, viewType, adapterItemsCount final int childTop = parent.getLayoutManager().getDecoratedTop(child); final int childBottom = parent.getLayoutManager().getDecoratedBottom(child); if (viewType == header_item_layout) { decorateHeader(c, child, childTop, childBottom); } else { decorateOther(c, parent, child, adapterPosition, adapter, viewType, adapterItemsCount); } } }
  16. private void decorateHeader(Canvas c, View child, int childTop, int childBottom)

    { int dividerTop = (int) (childTop + child.getTranslationY()); int dividerBottom = (int) (childBottom + child.getTranslationY()); int dividerLeft = child.getLeft(); int dividerRight = child.getRight(); headerDivider.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom); headerDivider.draw(c); }
  17. private void decorateOther(Canvas c, RecyclerView parent, View child, int adapterPosition,

    RecyclerView.Adapter adapter, int viewType, int adapterItemsCount) { final int nextItem = adapterPosition + 1; if (nextItem < adapterItemsCount && isAcceptableViewType(adapter.getItemViewType(nextItem))) { int dividerTop = (int) (child.getBottom() + child.getTranslationY()); int dividerBottom = (int) (dividerTop + generalDivider.getIntrinsicHeight() + child.getTranslationY()); int dividerLeft = child.getLeft(); int dividerRight = child.getRight(); if (viewType == common_item_layout && adapter.getItemViewType(nextItem) == common_item_layout) { dividerLeft = Utils.dp2Px(parent.getContext(), 76); } generalDivider.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom); generalDivider.draw(c); } }