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

RecyclerView: In & Out

PShchahelski
December 24, 2016

RecyclerView: In & Out

Useful tips for android developer while using recycler view widget

PShchahelski

December 24, 2016
Tweet

More Decks by PShchahelski

Other Decks in Programming

Transcript

  1. Components: • RecyclerView • Adapter • LayoutManager • ItemDecoration •

    ItemAnimator • ItemTouchHelper • SnapHelper • DiffUtil
  2. Components: • RecyclerView creating, binding and recycling our views •

    Adapter • LayoutManager • ItemDecoration • ItemAnimator • ItemTouchHelper • SnapHelper • DiffUtil
  3. Components: • RecyclerView creating, binding and recycling our views •

    Adapter provides data (RecyclerView.Adapter) • LayoutManager • ItemDecoration • ItemAnimator • ItemTouchHelper • SnapHelper • DiffUtil
  4. class MyAdapter extends RecyclerView.Adapter<ItemHolder> { public ItemHolder onCreateViewHolder(ViewGroup parent, int

    viewType) { …... } public void onBindViewHolder(ItemHolder holder, int position) { …... } }
  5. Components: • RecyclerView creating, binding and recycling our views •

    Adapter provides data (RecyclerView.Adapter) • LayoutManager layouting and positioning (Linear/GridLayout managers) • ItemDecoration • ItemAnimator • ItemTouchHelper • SnapHelper • DiffUtil
  6. Components: • RecyclerView creating, binding and recycling our views •

    Adapter provides data (RecyclerView.Adapter) • LayoutManager layouting and positioning (Linear/GridLayout managers) • ItemDecoration provides decoration for items (DividerItemDecoration) • ItemAnimator • ItemTouchHelper • SnapHelper • DiffUtil
  7. ItemDecoration class ItemDecoration{ public void onDraw(Canvas c, RecyclerView parent, State

    state) {...} public void onDrawOver(Canvas c, RecyclerView parent, State state) {...} public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {...} }
  8. ItemDecoration class ItemDecoration{ public void onDraw(Canvas c, RecyclerView parent, State

    state) {...} public void onDrawOver(Canvas c, RecyclerView parent, State state) {...} public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {...} }
  9. What if we want to customize the padding override fun

    getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { //access view holder val viewHolder = parent.getChildViewHolder(view) val params = view.layoutParams as GridLayoutManager.LayoutParams val layoutManager = parent.layoutManager as GridLayoutManager //how much space view is occupied val spanSize = params.spanSize //number of spans in the grid val totalSpanSize = layoutManager.spanCount //position depends on the RTL val position = params.spanIndex ... }
  10. What if we want to customize the padding override fun

    getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) //access view holder val viewHolder = parent.getChildViewHolder(view) val params = view.layoutParams as GridLayoutManager.LayoutParams val layoutManager = parent.layoutManager as GridLayoutManager //how much space view is occupied val spanSize = params.spanSize //number of spans in the grid val totalSpanSize = layoutManager.spanCount //position depends on the RTL val position = params.spanIndex … }
  11. What if we want to customize the padding override fun

    getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) //access view holder val viewHolder = parent.getChildViewHolder(view) val params = view.layoutParams as GridLayoutManager.LayoutParams val layoutManager = parent.layoutManager as GridLayoutManager //how much space view is occupied val spanSize = params.spanSize //number of spans in the grid val totalSpanSize = layoutManager.spanCount //position depends on the RTL val position = params.spanIndex ... }
  12. What if we want to customize the padding override fun

    getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { … if ((spanSize + position) == total) { outRect.right = padding; } else if (position == 0) { outRect.left = padding; } else { outRect.left = padding; outRect.right = padding; } } <ImageView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/image" android:layout_width="match_parent" android:layout_height="96dp"/>
  13. What if we want to customize the padding override fun

    getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { val params = view.layoutParams as GridLayoutManager.LayoutParams val layoutManager = parent.layoutManager as GridLayoutManager val spanSize = params.spanSize val totalSpanSize = layoutManager.spanCount val position = params.spanIndex val n = (totalSpanSize / spanSize).toFloat() val percent = (position / spanSize).toFloat() val leftPadding = padding * ((n - percent) / n) val rightPadding = padding * ((percent + 1) / n) outRect.left = leftPadding.toInt() outRect.right = rightPadding.toInt() }
  14. Solution GridLayoutManager(сontext, spanCount) But how wisely we can control the

    span size of each item? layouManager.spanSizeLookup = GridLayoutManager.SpanSizeLookup() { override fun getSpanSize(position: Int): Int { return (...) } }
  15. ItemDecoration class ItemDecoration{ public void onDraw(Canvas c, RecyclerView parent, State

    state) {...} public void onDrawOver(Canvas c, RecyclerView parent, State state) {...} public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {...} }
  16. override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { val

    childCount = parent.childCount val manager = parent.layoutManager for (i in 0..childCount - 1) { val child = parent.get(i) val right = manager.getDecoratedRight(child) val left = manager.getDecoratedLeft(child) + paddingLeft val height = divider.intrinsicHeight ?: 0 val top = manager.getDecoratedBottom(child) val bottom = top + height divider.setBounds(left, top, right, bottom) divider.draw(c) } }
  17. override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { val

    childCount = parent.childCount val manager = parent.layoutManager for (i in 0..childCount - 1) { val child = parent.get(i) val right = manager.getDecoratedRight(child) val left = manager.getDecoratedLeft(child) + paddingLeft val height = divider.intrinsicHeight val top = manager.getDecoratedBottom(child) val bottom = top + height divider.setBounds(left, top, right, bottom) divider.draw(c) } }
  18. Position override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {

    for (i in 0..childCount - 1) { val child = parent.get(i) parent.getChildAdapterPosition(child) } }
  19. Position override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {

    for (i in 0..childCount - 1) { val child = parent.get(i) parent.getChildAdapterPosition(child) } } NO_POSITION while animating
  20. Position override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {

    for (i in 0..childCount - 1) { val child = parent.get(i) parent.getChildLayoutPosition(child) } }
  21. Components: • RecyclerView creating, binding and recycling our views •

    Adapter provides data (RecyclerView.Adapter) • LayoutManager layouting and positioning (Linear/GridLayout managers) • ItemDecoration provides decoration for items (DividerItemDecoration) • ItemAnimator defines the animations (DefaultItemAnimator) • ItemTouchHelper • SnapHelper • DiffUtil
  22. Components: • RecyclerView creating, binding and recycling our views •

    Adapter provides data (RecyclerView.Adapter) • LayoutManager layouting and positioning (Linear/GridLayout managers) • ItemDecoration provides decoration for items (DividerItemDecoration) • ItemAnimator defines the animations (DefaultItemAnimator) • ItemTouchHelper swipe-to-delete, drag&drop • SnapHelper • DiffUtil
  23. class SwipeTouchCallback(private val callback: ItemTouchHelperAdapter) : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.START or ItemTouchHelper.END)

    { override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { callback.onItemMove(viewHolder.adapterPosition, target.adapterPosition) return true } override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { callback.onItemDismiss(viewHolder.adapterPosition) } }
  24. class SwipeTouchCallback(private val callback: ItemTouchHelperAdapter) : ItemTouchHelper.SimpleCallback(0, 0) { override

    fun getSwipeDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {...} }
  25. Components: • RecyclerView creating, binding and recycling our views •

    Adapter provides data (RecyclerView.Adapter) • LayoutManager layouting and positioning (Linear/GridLayout managers) • ItemDecoration provides decoration for items (DividerItemDecoration) • ItemAnimator defines the animations (DefaultItemAnimator) • ItemTouchHelper swipe-to-delete, drag&drop • SnapHelper LinearSnapHelper • DiffUtil
  26. Items with RecyclerView and horizontal LinearLayoutManager like CAROUSEL val snapHelper

    = LinearSnapHelper() snapHelper.attachToRecyclerView(this) LinearSnapHelper - Center snapping GravitySnapHelper(...): Gravity.START Gravity.END Gravity.BOTTOM Gravity.TOP rubensousa.github.io/2016/08/recyclerviewsnap
  27. Components: • RecyclerView creating, binding and recycling our views •

    Adapter provides data (RecyclerView.Adapter) • LayoutManager layouting and positioning (Linear/GridLayout managers) • ItemDecoration provides decoration for items (DividerItemDecoration) • ItemAnimator defines the animations (DefaultItemAnimator) • ItemTouchHelper swipe-to-delete, drag&drop • SnapHelper LinearSnapHelper • DiffUtil compares, batches updates and invalidates adapter by positions
  28. class MyDiffCallback(private var newBricks: List<Brick>, private var oldBricks: List<Brick>) :

    DiffUtil.Callback() { override fun getOldListSize(): Int { return oldBricks.size } override fun getNewListSize(): Int { return newBricks.size } override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { return oldBricks[oldItemPosition].id === newBricks[newItemPosition].id } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { return oldBricks[oldItemPosition].equals(newBricks[newItemPosition]) } }
  29. class MyDiffCallback(private var newBricks: List<Brick>, private var oldBricks: List<Brick>) :

    DiffUtil.Callback() { … override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any { return super.getChangePayload(oldItemPosition, newItemPosition) } } notifyItemChanged(int position, Object payload) notifyItemRangeChanged(int positionStart, int itemCount, Object payload)
  30. Naïve way public class ViewTypes { private static final int

    VIEW_TYPE_NORMAL = 0; private static final int VIEW_TYPE_SUBHEADER = 1; private static final int VIEW_TYPE_SEPARATOR = 2; private static final int VIEW_TYPE_HEADER = 3; }
  31. Naïve way @Override public int getItemViewType(int position) { NavigationMenuItem item

    = mItems.get(position); if (item instanceof NavigationMenuSeparatorItem) { return VIEW_TYPE_SEPARATOR; } else if (item instanceof NavigationMenuHeaderItem) { return VIEW_TYPE_HEADER; } else if (item instanceof NavigationMenuTextItem) { NavigationMenuTextItem textItem = (NavigationMenuTextItem) item; if (textItem.getMenuItem().hasSubMenu()) { return VIEW_TYPE_SUBHEADER; } else { return VIEW_TYPE_NORMAL; } } throw new RuntimeException("Unknown item type."); }
  32. Naïve way - item creation @Override public ViewHolder onCreateViewHolder(ViewGroup parent,

    int viewType) { switch (viewType) { case VIEW_TYPE_NORMAL: return new NormalViewHolder(mLayoutInflater, parent, mOnClickListener); case VIEW_TYPE_SUBHEADER: return new SubheaderViewHolder(mLayoutInflater, parent); case VIEW_TYPE_SEPARATOR: return new SeparatorViewHolder(mLayoutInflater, parent); case VIEW_TYPE_HEADER: return new HeaderViewHolder(mHeaderLayout); } return null; }
  33. Binding @Override public void onBindViewHolder(ViewHolder holder, int position) { switch

    (getItemViewType(position)) { case VIEW_TYPE_NORMAL: { NavigationMenuItemView itemView = (NavigationMenuItemView) holder.itemView; ... break; } case VIEW_TYPE_SUBHEADER: { TextView subHeader = (TextView) holder.itemView; NavigationMenuTextItem item = (NavigationMenuTextItem) mItems.get(position); ... break; } case VIEW_TYPE_SEPARATOR: case VIEW_TYPE_HEADER: ... } }
  34. Solutions - Favor composition over inheritance interface AdapterDelegate<T> { fun

    isForViewType(items: T, position: Int): Boolean fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder fun onBindViewHolder(items: T, position: Int, holder: RecyclerView.ViewHolder) }
  35. Solutions - Favor composition over inheritance class NewsTipAdapter : RecyclerView.Adapter{

    var delegates: MutableList<AdapterDelegate>() init{ delegates.add(NewsTeaserAdapterDelegate()) // Assigns internally ViewType integer delegates.add(PetFoodTipAdapterDelegate()) } .... }
  36. Solutions - Favor composition over inheritance class NewsTipAdapter : RecyclerView.Adapter()

    { ... override fun onBindViewHolder(holder: VH, position: Int) { delegates.forEach { if (it.isForViewType(items, position)) { // Main concern here it.onBindViewHolder(items, position, holder) return@forEach } } } }
  37. Composition class CatAdapterDelegate() : AdapterDelegate<List<DisplayItem>>() { protected fun isForViewType(items: List<DisplayItem>,

    position: Int): Boolean { return items[position] is Cat } } http://hannesdorfmann.com/android/adapter-delegates
  38. Epoxy class HeaderModel : EpoxyModel<HeaderView>() { var city: City fun

    bind(headerView: HeaderView) { headerView.setImage(city.getImage()) headerView.setTitle(city.getName()) headerView.setDescription(city.getDescription()) } val defaultLayout: Int @LayoutRes get() = R.layout.model_header_view }
  39. Epoxy class HeaderModel : EpoxyModel<HeaderView>() { var city: City fun

    bind(headerView: HeaderView) { headerView.setImage(city.getImage()) headerView.setTitle(city.getName()) headerView.setDescription(city.getDescription()) } val defaultLayout: Int @LayoutRes get() = R.layout.model_header_view }
  40. Epoxy class HeaderModel : EpoxyModel<HeaderView>() { var city: City fun

    bind(headerView: HeaderView) { headerView.setImage(city.getImage()) headerView.setTitle(city.getName()) headerView.setDescription(city.getDescription()) } val defaultLayout: Int @LayoutRes get() = R.layout.model_header_view }
  41. Epoxy class HeaderModel : EpoxyModel<HeaderView>() { var city: City fun

    bind(headerView: HeaderView) { headerView.setImage(city.getImage()) headerView.setTitle(city.getName()) headerView.setDescription(city.getDescription()) } val defaultLayout: Int @LayoutRes get() = R.layout.model_header_view }
  42. Factory interface TypeFactoryImpl : TypeFactory { fun type(category: CategoryVisitor): Int

    fun type(categoryMovie: OverviewCategoryMovieVisitor): Int fun type(space: OverviewSpaceVisitor): Int fun type(movie: MoviesMovieVisitor): Int }
  43. interface TypeFactory { fun holder(type: Int, view: View): AdapterViewHolder<*> }

    ... class TypeFactoryImpl : TypeFactory { override fun type(category: CategoryVisitor) = R.layout.li_vod_category override fun holder(type: Int, view: View): AdapterViewHolder<*> { return when (type) { R.layout.li_vod_category -> CategoryHolder(view) R.layout.li_vod_overview_category_movie -> OverviewCategoryMovieHolder(view) R.layout.v_li_space_horizontal -> OverviewSpaceHolder(view) R.layout.li_vod_movie -> MoviesMovieHolder(view) else -> throw RuntimeException("Illegal view type") } } }
  44. interface TypeFactory { fun holder(type: Int, view: View): AdapterViewHolder<*> }

    ... class TypeFactoryImpl : TypeFactory { override fun type(category: CategoryVisitor) = R.layout.li_vod_category override fun holder(type: Int, view: View): AdapterViewHolder<*> { return when (type) { R.layout.li_vod_category -> CategoryHolder(view) R.layout.li_vod_overview_category_movie -> OverviewCategoryMovieHolder(view) R.layout.v_li_space_horizontal -> OverviewSpaceHolder(view) R.layout.li_vod_movie -> MoviesMovieHolder(view) else -> throw RuntimeException("Illegal view type") } } }
  45. abstract class AdapterViewHolder<in T>(view: View) : RecyclerView.ViewHolder(view) { abstract fun

    bind(item: T, position: Int) abstract fun recycled(holder: AdapterViewHolder<*>) } class VodMoviesMovieVisitor(val movie: MovieDisplayObject) : VodViewVisitor { override fun type(typeFactory: VodTypeFactory): Int = typeFactory.type(this) } class VodMoviesMovieHolder(view: View) : SimpleAdapterViewHolder<VodMoviesMovieVisitor>(view) { ... override fun bind(item: VodMoviesMovieVisitor, position: Int) { .. } }
  46. abstract class AdapterViewHolder<in T>(view: View) : RecyclerView.ViewHolder(view) { abstract fun

    bind(item: T, position: Int) abstract fun recycled(holder: AdapterViewHolder<*>) } class VodMoviesMovieVisitor(val movie: MovieDisplayObject) : VodViewVisitor { override fun type(typeFactory: VodTypeFactory): Int = typeFactory.type(this) } class VodMoviesMovieHolder(view: View) : SimpleAdapterViewHolder<VodMoviesMovieVisitor>(view) { ... override fun bind(item: VodMoviesMovieVisitor, position: Int) { .. } }
  47. abstract class AdapterViewHolder<in T>(view: View) : RecyclerView.ViewHolder(view) { abstract fun

    bind(item: T, position: Int) abstract fun recycled(holder: AdapterViewHolder<*>) } class VodMoviesMovieVisitor(val movie: MovieDisplayObject) : VodViewVisitor { override fun type(typeFactory: VodTypeFactory): Int = typeFactory.type(this) } class VodMoviesMovieHolder(view: View) : SimpleAdapterViewHolder<VodMoviesMovieVisitor>(view) { ... override fun bind(item: VodMoviesMovieVisitor, position: Int) { .. } }
  48. abstract class AdapterViewHolder<in T>(view: View) : RecyclerView.ViewHolder(view) { abstract fun

    bind(item: T, position: Int) abstract fun recycled(holder: AdapterViewHolder<*>) } class VodMoviesMovieVisitor(val movie: MovieDisplayObject) : VodViewVisitor { override fun type(typeFactory: VodTypeFactory): Int = typeFactory.type(this) } class VodMoviesMovieHolder(view: View) : SimpleAdapterViewHolder<VodMoviesMovieVisitor>(view) { ... override fun bind(item: VodMoviesMovieVisitor, position: Int) { .. } }
  49. Read&watch • https://www.youtube.com/watch?v=imsr8NrIAMs • https://realm.io/news/360andev-yigit-boyar-pro-recyclerview-android-ui-java/ • http://frogermcs.github.io/recyclerview-animations-androiddevsummit-write-up/ • http://hannesdorfmann.com/android/adapter-delegates •

    https://medium.com/@nullthemall/diffutil-is-a-must-797502bc1149#.28n56v7zr • https://medium.com/@dpreussler/writing-better-adapters-1b09758407d2#.9vzaoxz5e • http://www.devexchanges.info/2016/09/android-tip-recyclerview-snapping-with.html • https://guides.codepath.com/android/using-the-recyclerview • https://medium.com/@ipaulpro/drag-and-swipe-with-recyclerview-b9456d2b1aaf