Slide 1

Slide 1 text

@lisawrayz RecyclerView RADICAL

Slide 2

Slide 2 text

@lisawrayz +Lisa Wray Zeitouni

Slide 3

Slide 3 text

@lisawrayz A practical guide to complex layouts in a RecyclerView

Slide 4

Slide 4 text

@lisawrayz What’s special about RecyclerView?

Slide 5

Slide 5 text

@lisawrayz Viewport into a huge virtual layout

Slide 6

Slide 6 text

@lisawrayz What is an “Item” anyway?

Slide 7

Slide 7 text

@lisawrayz New lifecycle — items live, die, live again

Slide 8

Slide 8 text

“RV Animations & Behind the Scenes” Android Dev Summit 2015 Yigit’s talk at Android Dev Summit youtube.com/watch?v=imsr8NrIAMs “Pro RecyclerView” 360|AnDev speakerdeck.com/yigit/pro-recyclerview

Slide 9

Slide 9 text

@lisawrayz components 101

Slide 10

Slide 10 text

@lisawrayz • RecyclerView: Creates, binds, recycles • Adapter: Provides data • LayoutManager: Lays out & positions • ItemDecoration: Adds offsets, draws over / under • ItemAnimator: Animates changes • ItemTouchHelper: Handles drag&drop, swipe-to-delete • SnapHelper: Creates ViewPager-like scrolls & flings • DiffUtil: Calculates changes for you

Slide 11

Slide 11 text

@lisawrayz • RecyclerView: RecyclerView • Adapter: RecyclerView.Adapter • LayoutManager: Linear- / GridLayoutManager • ItemDecoration: nope • ItemAnimator: DefaultItemAnimator • ItemTouchHelper: SimpleItemTouchHelper / SimpleCallback • SnapHelper: LinearSnapHelper • DiffUtil: DiffUtil

Slide 12

Slide 12 text

@lisawrayz looks hard, actually easy!

Slide 13

Slide 13 text

carousel Item with a RecyclerView and a horizontal LinearLayoutManager Not like ListView — it just works!

Slide 14

Slide 14 text

layout/item_carousel.xml 
 
 
 


Slide 15

Slide 15 text

… lm = new LinearLayoutManager(context, HORIZONTAL, false);
 recyclerView.setLayoutManager(lm);
 recyclerView.addItemDecoration(carouselDecoration); onCreate: recyclerView.setAdapter(adapter); onBind:

Slide 16

Slide 16 text

snapping Gravity.START SnapHelper support lib 24.1

Slide 17

Slide 17 text

@lisawrayz snapping Base class: SnapHelper LinearSnapHelper: center snapping SnapHelper snapHelper = new GravitySnapHelper(Gravity.START);
 snapHelper.attachToRecyclerView(recyclerView); GravitySnapHelper: rubensousa.github.io/2016/08/recyclerviewsnap

Slide 18

Slide 18 text

viewpager (-like) width=MATCH_PARENT item LinearSnapHelper no fragments fling is allowed

Slide 19

Slide 19 text

swipe-to- delete Drag & drop uses the same mechanism

Slide 20

Slide 20 text

private TouchCallback touchCallback = new SwipeTouchCallback(); ItemTouchHelper itemTouchHelper = new ItemTouchHelper(touchCallback); 
 itemTouchHelper.attachToRecyclerView(recyclerView);

Slide 21

Slide 21 text

public class SwipeTouchCallback extends ItemTouchHelper.SimpleCallback {
 
 public SwipeTouchCallback() {
 super(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
 }
 }

Slide 22

Slide 22 text

public class SwipeTouchCallback extends ItemTouchHelper.SimpleCallback {
 
 public SwipeTouchCallback() {
 super(0, 0);
 }
 
 @Override public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { 
 if (viewHolder.getItemViewType() == R.layout.item_card) {
 return ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
 } else {
 return super.getSwipeDirs(recyclerView, viewHolder);
 }
 }
 }

Slide 23

Slide 23 text

public class SwipeTouchCallback extends ItemTouchHelper.SimpleCallback {
 
 …
 
 @Override public void onSwiped( RecyclerView.ViewHolder viewHolder, int direction) { 
 int position = viewHolder.getAdapterPosition();
 // remove & notify
 }
 
 @Override public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
 View child = viewHolder.itemView;
 
 // Fade out the item
 child.setAlpha(1 - (Math.abs(dX) / child.getWidth()));
 
 super.onChildDraw(…);
 }
 }

Slide 24

Slide 24 text

@lisawrayz DiffUtil

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

@lisawrayz 1. thou shalt notify as precisely as possible

Slide 27

Slide 27 text

@lisawrayz notifyItemChanged(…);
 notifyItemRangeChanged(…);
 notifyItemAdded(…);
 notifyItemRangeAdded(…);
 notifyItemRemoved(…);
 notifyItemRangeRemoved(…);
 notifyItemMoved(…); 
 … and notifyChanged();

Slide 28

Slide 28 text


 DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff( new Callback(items, newItems)); // Actually change adapter
 adapter.clear();
 adapter.addAll(newItems); // Notify
 diffResult.dispatchUpdatesTo(adapter);

Slide 29

Slide 29 text

private class Callback extends DiffUtil.Callback { …
 
 @Override public boolean areItemsTheSame( int oldItemPosition, int newItemPosition) {} 
 
 @Override public boolean areContentsTheSame( int oldItemPosition, int newItemPosition) {}
 }

Slide 30

Slide 30 text

@lisawrayz multiple view types

Slide 31

Slide 31 text

public class ViewTypes {
 
 public static final int HEADER = 0;
 public static final int CARD = 1;
 public static final int FULL_BLEED_CARD = 2;
 public static final int SQUARE_CARD = 3;
 public static final int SMALL_CARD = 4;
 
 } naïve way

Slide 32

Slide 32 text

@Override public RecyclerView.ViewHolder 
 onCreateViewHolder(ViewGroup parent, int viewType) {
 LayoutInflater inflater = … ;
 
 View view;
 switch (viewType) {
 case HEADER:
 view = inflater.inflate(
 R.layout.item_header, parent, false);
 return new HeaderViewHolder(view);
 case CARD:
 view = inflater.inflate(
 R.layout.item_card, parent, false);
 return new CardViewHolder(view);
 case FULL_BLEED_CARD:
 case SQUARE_CARD:
 case SMALL_CARD:
 …
 }
 } item creation

Slide 33

Slide 33 text

@Override public void onBindViewHolder( RecyclerView.ViewHolder viewHolder, int position) { 
 Model model = models.get(position);
 switch (viewHolder.getItemViewType()) {
 case HEADER:
 HeaderViewHolder headerVH = (HeaderViewHolder) viewHolder;
 headerVH.title.setText(model.getTitle());
 if (model.getSubtitle() != null) {
 headerVH.subtitle.setText(model.getSubtitle());
 }
 headerVH.subtitle.setVisibility(
 model.getSubtitle() != null ? View.VISIBLE : View.GONE);
 headerVH.icon.setImageDrawable(model.getIcon());
 break;
 case CARD:
 CardViewHolder cardVH = (CardViewHolder) viewHolder; …
 break;
 case FULL_BLEED_CARD:
 case SQUARE_CARD:
 …
 }
 } item bind

Slide 34

Slide 34 text

This is your adapter on switch statements

Slide 35

Slide 35 text

public interface AdapterDelegate { void onBind(RecyclerView.ViewHolder viewHolder, int position); boolean handles(ViewHolder viewHolder);
 } delegate — an ok way

Slide 36

Slide 36 text

public class Adapter extends RecyclerView.Adapter { List delegates; public Adapter() { delegates.add(new HeaderDelegate()); delegates.add(new CardDelegate()); } @Override public void onBindViewHolder( RecyclerView.ViewHolder viewHolder, int position) { for (AdapterDelegate delegate : delegates) { if (delegate.handles(viewHolder)) { delegate.onBind(viewHolder, position); } } } } boiler plate

Slide 37

Slide 37 text

public class ItemTypes {
 
 public static final int HEADER = 0;
 public static final int CARD = 1;
 public static final int FULL_BLEED_CARD = 2;
 public static final int SQUARE_CARD = 3;
 public static final int SMALL_CARD = 4;
 
 } better way

Slide 38

Slide 38 text

better way R.layout.item_header R.layout.item_card

Slide 39

Slide 39 text

public class SongItem extends Item {
 private final Song song;
 
 public SongItem(Song song) {
 this.song = song;
 }
 
 @Override public void bind(ViewHolder viewHolder, int position) { // binding logic here
 }
 
 @Override public int getLayout() {
 return R.layout.song;
 }
 } Item

Slide 40

Slide 40 text

@lisawrayz

Slide 41

Slide 41 text

EpoxyAdapter epoxyAdapter = new EpoxyAdapter(); EpoxyModel headerModel = new HeaderModel(); epoxyAdapter.addModel(headerModel); Epoxy

Slide 42

Slide 42 text

public class PhotoModel extends EpoxyModel { private final Photo photo; public PhotoModel(Photo photo) { this.photo = photo; } @LayoutRes public int getDefaultLayout() { return R.layout.view_model_photo; } @Override public void bind(PhotoView photoView) { photoView.setUrl(photo.getUrl()); } } Epoxy

Slide 43

Slide 43 text

public class PhotoModel extends EpoxyModel { private final Photo photo; public PhotoModel(Photo photo) { this.photo = photo; } @LayoutRes public int getDefaultLayout() { return R.layout.view_model_photo; } @Override public void bind(PhotoView photoView) { photoView.setUrl(photo.getUrl()); } } Epoxy

Slide 44

Slide 44 text

public class PhotoModel extends EpoxyModel { private final Photo photo; public PhotoModel(Photo photo) { this.photo = photo; } @LayoutRes public int getDefaultLayout() { return R.layout.view_model_photo; } @Override public void bind(PhotoView photoView) { photoView.setUrl(photo.getUrl()); } } Epoxy

Slide 45

Slide 45 text

public class PhotoModel extends EpoxyModel { private final Photo photo; public PhotoModel(Photo photo) { this.photo = photo; } @LayoutRes public int getDefaultLayout() { return R.layout.view_model_photo; } @Override public void bind(PhotoView photoView) { photoView.setUrl(photo.getUrl()); } } Epoxy need a custom view or view holder for each item

Slide 46

Slide 46 text

consider data binding /MyAdapter.java @Override public RecyclerView.ViewHolder onCreateViewHolder( ViewGroup parent, int layoutResId) {
 LayoutInflater inflater = LayoutInflater.from( parent.getContext());
 ViewDataBinding binding = DataBindingUtil.inflate( inflater, layoutResId, parent, false);
 return new ViewHolder<>(binding);
 } public class ViewHolder extends RecyclerView.ViewHolder {
 public final T binding;
 
 public ViewHolder(T binding) {
 super(binding.getRoot());
 this.binding = binding;
 }
 }

Slide 47

Slide 47 text

consider data binding /MyAdapter.java @Override public RecyclerView.ViewHolder onCreateViewHolder( ViewGroup parent, int layoutResId) {
 LayoutInflater inflater = LayoutInflater.from( parent.getContext());
 ViewDataBinding binding = DataBindingUtil.inflate( inflater, layoutResId, parent, false);
 return new ViewHolder<>(binding);
 } public class ViewHolder extends RecyclerView.ViewHolder {
 public final T binding;
 
 public ViewHolder(T binding) {
 super(binding.getRoot());
 this.binding = binding;
 }
 }

Slide 48

Slide 48 text

@lisawrayz multiple columns new GridLayoutManager(context, spanCount); total number of columns

Slide 49

Slide 49 text

@lisawrayz choosing a spanCount • Least common multiple (LCM) of all your desired column splits • I want single, double, triple & quad columns
 LCM(1, 2, 3, 4) = 12 • No performance hit from having a large num of columns. (Large num of items might be)

Slide 50

Slide 50 text

final int spanCount = 12;
 layoutManager = new GridLayoutManager(this, spanCount);
 layoutManager.setSpanSizeLookup( new GridLayoutManager.SpanSizeLookup() {
 @Override public int getSpanSize(int position) {
 int viewType = adapter.getItemViewType(position);
 switch (viewType) {
 case HEADER:
 return spanCount;
 case CARD:
 return spanCount / 2;
 case FULL_BLEED_CARD:
 return spanCount;
 case SMALL_CARD:
 return spanCount / 3;
 default:
 return 1;
 }
 }
 span size lookup

Slide 51

Slide 51 text

final int spanCount = 12;
 layoutManager = new GridLayoutManager(this, spanCount);
 layoutManager.setSpanSizeLookup( new GridLayoutManager.SpanSizeLookup() {
 @Override public int getSpanSize(int position) {
 Item item = groupAdapter.getItem(position);
 return item.getSpanSize(spanCount, position);
 }
 });

Slide 52

Slide 52 text

public class SongItem extends Item {
 private final Song song;
 
 public SongItem(Song song) {
 this.song = song;
 }
 
 @Override public void bind(ViewHolder viewHolder, int position) {…}
 
 @Override public int getLayout() {
 return R.layout.song;
 } @Override public int getSpanSize(int spanCount, int position) {
 // individual item’s span size
 }
 } Item

Slide 53

Slide 53 text

public class SongItem extends Item {
 private final Song song;
 
 public SongItem(Song song) {
 this.song = song;
 }
 
 @Override public void bind(ViewHolder viewHolder, int position) {…}
 
 @Override public int getLayout() {
 return R.layout.song;
 } @Override public int getSpanSize(int spanCount, int position) {
 // individual item’s span size
 }
 } Item

Slide 54

Slide 54 text

@lisawrayz All the same view type

Slide 55

Slide 55 text

@lisawrayz Columns of text

Slide 56

Slide 56 text

@lisawrayz Groups

Slide 57

Slide 57 text

@lisawrayz

Slide 58

Slide 58 text

@lisawrayz 0 1 onClick, pos=2 3

Slide 59

Slide 59 text

@lisawrayz server

Slide 60

Slide 60 text

@lisawrayz 3 5

Slide 61

Slide 61 text

Header commentHeader; List comments; int index = adapter.getPosition(commentHeader) + 1; adapter.addAll(index, comments); adapter.notifyInsert(index, comments.size()); Don’t hold adapter position!

Slide 62

Slide 62 text

Header commentHeader; List comments; int index = adapter.getPosition(commentHeader) + 1; adapter.addAll(index, comments); adapter.notifyInsert(index, comments.size()); Use references List.indexOf()

Slide 63

Slide 63 text

Header commentHeader; List comments; int index = adapter.getPosition(commentHeader) + 1; adapter.addAll(index, comments); adapter.notifyInsert(index, comments.size()); Use references

Slide 64

Slide 64 text

EpoxyModel commentHeaderModel; List commentModels; for (int i = commentModels.size - 1; i >= 0 ; i--) { expoxyAdapter.insertModelAfter(commentHeaderModel); } Epoxy

Slide 65

Slide 65 text

EpoxyModel commentHeaderModel; List commentModels; for (int i = commentModels.size - 1; i >= 0 ; i--) { expoxyAdapter.insertModelAfter(commentHeaderModel); } Epoxy

Slide 66

Slide 66 text

EpoxyModel commentHeaderModel; List commentModels; for (int i = commentModels.size - 1; i >= 0 ; i--) { expoxyAdapter.insertModelAfter(commentHeaderModel); } epoxyAdapter.hideModels(commentModels); Epoxy

Slide 67

Slide 67 text

@lisawrayz

Slide 68

Slide 68 text

GroupAdapter groupAdapter; Item item = new TitleItem(); groupAdapter.add(item); HeaderItem header = new HeaderItem(“Comments”); ExpandableGroup commentGroup = new ExpandableGroup(header); groupAdapter.add(commentGroup); groupie

Slide 69

Slide 69 text

GroupAdapter groupAdapter; Item item = new TitleItem(); groupAdapter.add(item); HeaderItem header = new HeaderItem(“Comments”); ExpandableGroup commentGroup = new ExpandableGroup(header); groupAdapter.add(commentGroup); groupie

Slide 70

Slide 70 text

GroupAdapter groupAdapter; Item item = new TitleItem(); groupAdapter.add(item); HeaderItem header = new HeaderItem(“Comments”); ExpandableGroup commentGroup = new ExpandableGroup(header); groupAdapter.add(commentGroup); groupie

Slide 71

Slide 71 text

List commentItems; ExpandableGroup commentGroup; commentGroup.addAll(commentItems); commentGroup.toggleExpanded(); groupie

Slide 72

Slide 72 text

List commentItems; ExpandableGroup commentGroup; commentGroup.addAll(commentItems); commentGroup.toggleExpanded(); groupie

Slide 73

Slide 73 text

List commentItems; ExpandableGroup commentGroup; commentGroup.addAll(commentItems); commentGroup.toggleExpanded(); commentGroup.toggleExpanded(); groupie

Slide 74

Slide 74 text

@lisawrayz DiffUtil x Groupie.UpdatingGroup

Slide 75

Slide 75 text

@lisawrayz

Slide 76

Slide 76 text

@lisawrayz Groups are like a mini adapter — can fool GLM into vertical columns

Slide 77

Slide 77 text

Encapsulation along with efficient recycling (instead of one large item)

Slide 78

Slide 78 text

No content

Slide 79

Slide 79 text

@lisawrayz github.com/Genius/groupie examples here! (whether or not you use the lib)

Slide 80

Slide 80 text

@lisawrayz ItemDecoration

Slide 81

Slide 81 text

public class ItemDecoration {
 
 public void getItemOffsets(…) {}
 
 public void onDraw(…) {}
 
 public void onDrawOver(…) {} 
 }

Slide 82

Slide 82 text

public class ItemDecoration {
 
 public void getItemOffsets(…) {}
 
 public void onDraw(…) {}
 
 public void onDrawOver(…) {} 
 }

Slide 83

Slide 83 text

@lisawrayz common request: space my columns evenly

Slide 84

Slide 84 text

@lisawrayz simple solution 1/2 padding on outsides of RV, 1/2 padding on each side of item

Slide 85

Slide 85 text

@lisawrayz ½ ½ ½

Slide 86

Slide 86 text

@lisawrayz android:paddingTop=“@dimen/padding” android:clipToPadding=“false”

Slide 87

Slide 87 text

@lisawrayz complex solution what if we can’t use padding? full bleed item

Slide 88

Slide 88 text

@lisawrayz full padding 2x offsets on edges?

Slide 89

Slide 89 text

Is this item on the left edge, right edge, middle, …? @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 
 RecyclerView.ViewHolder viewHolder = parent.getChildViewHolder(view);
 
 GridLayoutManager.LayoutParams layoutParams = view.getLayoutParams();
 GridLayoutManager gridLayoutManager = parent.getLayoutManager(); 
 int spanSize = layoutParams.getSpanSize();
 int totalSpanSize = gridLayoutManager.getSpanCount();
 
 if (spanSize + layoutParams.getSpanIndex() == totalSpanSize) {
 // Item reaches to right edge of list
 outRect.right = padding;
 }
 if (layoutParams.getSpanIndex() == 0) {
 // Item's left edge is on left edge of list
 outRect.left = padding;
 }
 }

Slide 90

Slide 90 text

Is this item on the left edge, right edge, middle, …? @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 
 RecyclerView.ViewHolder viewHolder = parent.getChildViewHolder(view);
 
 GridLayoutManager.LayoutParams layoutParams = view.getLayoutParams();
 GridLayoutManager gridLayoutManager = parent.getLayoutManager(); 
 int spanSize = layoutParams.getSpanSize();
 int totalSpanSize = gridLayoutManager.getSpanCount();
 
 if (spanSize + layoutParams.getSpanIndex() == totalSpanSize) {
 // Item reaches to right edge of list
 outRect.right = padding;
 }
 if (layoutParams.getSpanIndex() == 0) {
 // Item's left edge is on left edge of list
 outRect.left = padding;
 }
 }

Slide 91

Slide 91 text

Is this item on the left edge, right edge, middle, …? @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 
 RecyclerView.ViewHolder viewHolder = parent.getChildViewHolder(view);
 
 GridLayoutManager.LayoutParams layoutParams = view.getLayoutParams();
 GridLayoutManager gridLayoutManager = parent.getLayoutManager(); 
 int spanSize = layoutParams.getSpanSize();
 int totalSpanSize = gridLayoutManager.getSpanCount();
 
 if (spanSize + layoutParams.getSpanIndex() == totalSpanSize) {
 // Item reaches to right edge of list
 outRect.right = padding;
 }
 if (layoutParams.getSpanIndex() == 0) {
 // Item's left edge is on left edge of list
 outRect.left = padding;
 }
 }

Slide 92

Slide 92 text

Is this item on the left edge, right edge, middle, …? @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 
 RecyclerView.ViewHolder viewHolder = parent.getChildViewHolder(view);
 
 GridLayoutManager.LayoutParams layoutParams = view.getLayoutParams();
 GridLayoutManager gridLayoutManager = parent.getLayoutManager(); 
 int spanSize = layoutParams.getSpanSize();
 int totalSpanSize = gridLayoutManager.getSpanCount();
 
 if (spanSize + layoutParams.getSpanIndex() == totalSpanSize) {
 // Item reaches to right edge of list
 outRect.right = padding;
 }
 if (layoutParams.getSpanIndex() == 0) {
 // Item's left edge is on left edge of list
 outRect.left = padding;
 }
 }

Slide 93

Slide 93 text

Is this item on the left edge, right edge, middle, …? @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 
 RecyclerView.ViewHolder viewHolder = parent.getChildViewHolder(view);
 
 GridLayoutManager.LayoutParams layoutParams = view.getLayoutParams();
 GridLayoutManager gridLayoutManager = parent.getLayoutManager(); 
 int spanSize = layoutParams.getSpanSize();
 int totalSpanSize = gridLayoutManager.getSpanCount();
 
 if (spanSize + layoutParams.getSpanIndex() == totalSpanSize) {
 // Item reaches to right edge of list
 outRect.right = padding;
 }
 if (layoutParams.getSpanIndex() == 0) {
 // Item's left edge is on left edge of list
 outRect.left = padding;
 }
 }

Slide 94

Slide 94 text

@lisawrayz eek! different size squares!! 2x offsets on edges? uneven item widths — offsets don’t change measured item width

Slide 95

Slide 95 text

@lisawrayz each item needs same total padding … just differently distributed “DebugItemDecoration” in example project

Slide 96

Slide 96 text

even column padding @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
 
 GridLayoutManager.LayoutParams layoutParams = view.getLayoutParams();
 GridLayoutManager gridLayoutManager = parent.getLayoutManager();
 float spanSize = layoutParams.getSpanSize();
 float totalSpanSize = gridLayoutManager.getSpanCount();
 
 float n = totalSpanSize / spanSize; // num columns
 float c = layoutParams.getSpanIndex() / spanSize; // column index
 
 float leftPadding = padding * ((n - c) / n);
 float rightPadding = padding * ((c + 1) / n);
 
 outRect.left = (int) leftPadding;
 outRect.right = (int) rightPadding;
 }

Slide 97

Slide 97 text

even column padding @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
 
 GridLayoutManager.LayoutParams layoutParams = view.getLayoutParams();
 GridLayoutManager gridLayoutManager = parent.getLayoutManager();
 float spanSize = layoutParams.getSpanSize();
 float totalSpanSize = gridLayoutManager.getSpanCount();
 
 float n = totalSpanSize / spanSize; // num columns
 float c = layoutParams.getSpanIndex() / spanSize; // column index
 
 float leftPadding = padding * ((n - c) / n);
 float rightPadding = padding * ((c + 1) / n);
 
 outRect.left = (int) leftPadding;
 outRect.right = (int) rightPadding;
 }

Slide 98

Slide 98 text

even column padding @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
 
 GridLayoutManager.LayoutParams layoutParams = view.getLayoutParams();
 GridLayoutManager gridLayoutManager = parent.getLayoutManager();
 float spanSize = layoutParams.getSpanSize();
 float totalSpanSize = gridLayoutManager.getSpanCount();
 
 float n = totalSpanSize / spanSize; // num columns
 float c = layoutParams.getSpanIndex() / spanSize; // column index
 
 float leftPadding = padding * ((n - c) / n);
 float rightPadding = padding * ((c + 1) / n);
 
 outRect.left = (int) leftPadding;
 outRect.right = (int) rightPadding;
 }

Slide 99

Slide 99 text

@lisawrayz phew … nice and even

Slide 100

Slide 100 text

@lisawrayz ItemDecorations are additive recyclerView.addItemDecoration( new SpacingItemDecoration()); recyclerView.addItemDecoration( new HeaderItemDecoration(blue));

Slide 101

Slide 101 text

public class ItemDecoration {
 
 public void getItemOffsets(…) {}
 
 public void onDraw(…) {}
 
 public void onDrawOver(…) {} 
 }

Slide 102

Slide 102 text

@Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 
 for (int i = 0; i < parent.getChildCount(); i++) {
 View child = parent.getChildAt(i);
 if (!isHeader(child, parent)) continue;
 
 float top = child.getTop();
 float bottom = child.getBottom();
 float right = child.getRight();
 float left = child.getLeft();
 c.drawRect(left, top, right, bottom, paint);
 }
 }

Slide 103

Slide 103 text

@Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 
 for (int i = 0; i < parent.getChildCount(); i++) {
 View child = parent.getChildAt(i);
 if (!isHeader(child, parent)) continue;
 
 float top = child.getTop();
 float bottom = child.getBottom();
 float right = child.getRight();
 float left = child.getLeft();
 c.drawRect(left, top, right, bottom, paint);
 }
 }

Slide 104

Slide 104 text

@Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 
 for (int i = 0; i < parent.getChildCount(); i++) {
 View child = parent.getChildAt(i);
 if (!isHeader(child, parent)) continue;
 
 float top = child.getTop();
 float bottom = child.getBottom();
 float right = child.getRight();
 float left = child.getLeft();
 c.drawRect(left, top, right, bottom, paint);
 }
 }

Slide 105

Slide 105 text

@Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 
 for (int i = 0; i < parent.getChildCount(); i++) {
 View child = parent.getChildAt(i);
 if (!isHeader(child, parent)) continue;
 
 float top = child.getTop();
 float bottom = child.getBottom();
 float right = child.getRight();
 float left = child.getLeft();
 c.drawRect(left, top, right, bottom, paint);
 }
 }

Slide 106

Slide 106 text

@lisawrayz gap

Slide 107

Slide 107 text

@lisawrayz gap

Slide 108

Slide 108 text

@Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
 for (int i = 0; i < parent.getChildCount(); i++) {
 View child = parent.getChildAt(i);
 if (!isHeader(child, parent)) continue;
 
 RecyclerView.LayoutManager lm = parent.getLayoutManager();
 
 float top = lm.getDecoratedTop(child);
 float bottom = lm.getDecoratedBottom(child);
 float right = lm.getDecoratedRight(child);
 float left = lm.getDecoratedLeft(child);
 c.drawRect(left, top, right, bottom, paint);
 }
 } use decorated bounds

Slide 109

Slide 109 text

@lisawrayz much better

Slide 110

Slide 110 text

@lisawrayz A full bleed item Items move! yikes

Slide 111

Slide 111 text

@Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
 for (int i = 0; i < parent.getChildCount(); i++) {
 View child = parent.getChildAt(i);
 if (!isHeader(child, parent)) continue;
 
 RecyclerView.LayoutManager lm = parent.getLayoutManager();
 
 float top = lm.getDecoratedTop(child) + child.getTranslationY();
 float bottom = lm.getDecoratedBottom(child) + child.getTranslationY();
 float right = lm.getDecoratedRight(child) + child.getTranslationX();
 float left = lm.getDecoratedLeft(child) + child.getTranslationX();
 c.drawRect(left, top, right, bottom, paint);
 }
 use translation

Slide 112

Slide 112 text

@Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
 for (int i = 0; i < parent.getChildCount(); i++) {
 View child = parent.getChildAt(i);
 if (!isHeader(child, parent)) continue;
 
 RecyclerView.LayoutManager lm = parent.getLayoutManager();
 
 float top = lm.getDecoratedTop(child) + child.getTranslationY();
 float bottom = lm.getDecoratedBottom(child) + child.getTranslationY();
 float right = lm.getDecoratedRight(child) + child.getTranslationX();
 float left = lm.getDecoratedLeft(child) + child.getTranslationX();
 c.drawRect(left, top, right, bottom, paint);
 }
 use translation

Slide 113

Slide 113 text

@lisawrayz better

Slide 114

Slide 114 text

@Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
 for (int i = 0; i < parent.getChildCount(); i++) {
 View child = parent.getChildAt(i);
 
 int position = parent.getChildAdapterPosition(child); }
 } use layout position

Slide 115

Slide 115 text

@Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
 for (int i = 0; i < parent.getChildCount(); i++) {
 View child = parent.getChildAt(i);
 
 int position = parent.getChildAdapterPosition(child); }
 } use layout position can be NO_POSITION during animation

Slide 116

Slide 116 text

@Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
 for (int i = 0; i < parent.getChildCount(); i++) {
 View child = parent.getChildAt(i);
 int position = parent.getChildLayoutPosition(child); }
 } use layout position

Slide 117

Slide 117 text

@lisawrayz other/custom layout managers

Slide 118

Slide 118 text

StaggeredGrid
 LayoutManager included in Android:

Slide 119

Slide 119 text

SpannedGrid
 LayoutManager ? not in Android:

Slide 120

Slide 120 text

@lisawrayz Two way view only maven snapshots right now lucasr.org/2014/07/31/the-new-twowayview/ github.com/lucasr/twoway-view

Slide 121

Slide 121 text

@lisawrayz wiresareobsolete.com/2014/09/building-a- recyclerview-layoutmanager-part-1/ “Building a RecyclerView LayoutManager: Part 1-3”, Dave Smith in-depth tutorial

Slide 122

Slide 122 text

@lisawrayz with great power comes great responsibility -recyclerview

Slide 123

Slide 123 text

@lisawrayz +Lisa Wray Zeitouni github.com/Genius/groupie