Slide 1

Slide 1 text

Mastering RecyclerView Layouts Dave Smith • NewCircle, Inc. @devunwired +DaveSmithDev

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

RecyclerView recyclerView = new RecyclerView(this);
 recyclerView.setLayoutManager(…);
 
 setContentView(recyclerView);

Slide 4

Slide 4 text

LinearLayoutManager LinearLayoutManager manager = new LinearLayoutManager( this, LinearLayoutManager.VERTICAL, false);

Slide 5

Slide 5 text

GridLayoutManager GridLayoutManager manager = new GridLayoutManager( this,
 2, /* Number of grid columns */
 GridLayoutManager.VERTICAL,
 false);

Slide 6

Slide 6 text

GridLayoutManager GridLayoutManager manager = new GridLayoutManager( this, 2, /* Number of grid columns */ GridLayoutManager.VERTICAL, false); manager.setSpanSizeLookup(
 new GridLayoutManager.SpanSizeLookup() {
 @Override
 public int getSpanSize(int position) {
 //Stagger every other row
 return (position % 3 == 0 ? 2 : 1);
 }
 });

Slide 7

Slide 7 text

StaggeredGridLayoutManager StaggeredGridLayoutManager manager = new StaggeredGridLayoutManager(
 2, /* Number of grid columns */
 StaggeredGridLayoutManager.VERTICAL);

Slide 8

Slide 8 text

StaggeredGridLayoutManager StaggeredGridLayoutManager manager = new StaggeredGridLayoutManager(
 2, /* Number of grid columns */
 StaggeredGridLayoutManager.VERTICAL); manager.setGapStrategy( StaggeredGridLayoutManager.GAP_HANDLING_NONE);

Slide 9

Slide 9 text

Case Study: FixedGridLayoutManager 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

Slide 10

Slide 10 text

Case Study: FixedGridLayoutManager 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

Slide 11

Slide 11 text

The Recycler LayoutManager Adapter ?

Slide 12

Slide 12 text

The Recycler LayoutManager Recycler Adapter Obtain View Recycle View Bind

Slide 13

Slide 13 text

Recycler The Recycler Scrap Heap detachAndScrapView() Recycle Pool removeAndRecycleView()

Slide 14

Slide 14 text

Handling Decorations Item View getDecoratedLeft() getDecoratedTop() getDecoratedRight() getDecoratedBottom()

Slide 15

Slide 15 text

Handling Decorations Item View getDecoratedWidth() getDecoratedHeight()

Slide 16

Slide 16 text

Handling Decorations Item View layoutDecorated() measureChild()

Slide 17

Slide 17 text

The Fill Technique fillGaps() 1. Discover first visible position/location 2. Find layout gaps (at the edges) 3. Scrap everything 4. Lay out all visible positions 5. Recycle remaining views Save as little state as possible.

Slide 18

Slide 18 text

private void fillGrid(int direction, …,
 RecyclerView.Recycler recycler,
 RecyclerView.State state) {
 //…Obtain the first visible item position… findFirstVisiblePosition(); 
 detachAndScrapAttachedViews(recycler);
 
 for (…) {
 int nextPosition = …;
 
 View View = recycler.getViewForPosition(nextPosition); addView(view);
 
 measureChildWithMargins(view, …);
 layoutDecorated(view, …);
 }
 
 //Remove anything that is left behind final List scrapList = recycler.getScrapList();
 for (int i=0; i < scrapList.size(); i++) {
 final View removingView = scrapList.get(i);
 recycler.recycleView(removingView);
 }
 }

Slide 19

Slide 19 text

private void fillGrid(int direction, …,
 RecyclerView.Recycler recycler,
 RecyclerView.State state) {
 //…Obtain the first visible item position… findFirstVisiblePosition(); 
 detachAndScrapAttachedViews(recycler);
 
 for (…) {
 int nextPosition = …;
 
 View View = recycler.getViewForPosition(nextPosition); addView(view);
 
 measureChildWithMargins(view, …);
 layoutDecorated(view, …);
 }
 
 //Remove anything that is left behind final List scrapList = recycler.getScrapList();
 for (int i=0; i < scrapList.size(); i++) {
 final View removingView = scrapList.get(i);
 recycler.recycleView(removingView);
 }
 }

Slide 20

Slide 20 text

private void fillGrid(int direction, …,
 RecyclerView.Recycler recycler,
 RecyclerView.State state) {
 //…Obtain the first visible item position… findFirstVisiblePosition(); 
 detachAndScrapAttachedViews(recycler);
 
 for (…) {
 int nextPosition = …;
 
 View View = recycler.getViewForPosition(nextPosition); addView(view);
 
 measureChildWithMargins(view, …);
 layoutDecorated(view, …);
 }
 
 //Remove anything that is left behind final List scrapList = recycler.getScrapList();
 for (int i=0; i < scrapList.size(); i++) {
 final View removingView = scrapList.get(i);
 recycler.recycleView(removingView);
 }
 }

Slide 21

Slide 21 text

private void fillGrid(int direction, …,
 RecyclerView.Recycler recycler,
 RecyclerView.State state) {
 //…Obtain the first visible item position… findFirstVisiblePosition(); 
 detachAndScrapAttachedViews(recycler);
 
 for (…) {
 int nextPosition = …;
 
 View View = recycler.getViewForPosition(nextPosition); addView(view);
 
 measureChildWithMargins(view, …);
 layoutDecorated(view, …);
 }
 
 //Remove anything that is left behind final List scrapList = recycler.getScrapList();
 for (int i=0; i < scrapList.size(); i++) {
 final View removingView = scrapList.get(i);
 recycler.recycleView(removingView);
 }
 }

Slide 22

Slide 22 text

Level 1: Make It Work onLayoutChildren() Run a FILL operation canScrollVertically() canScrollHorizontally() Which axes enable scrolling? scrollHorizontallyBy() scrollVerticallyBy() Clamp supplied delta against boundary conditions Shift all views in the layout Trigger a FILL operation Report back actual distance scrolled

Slide 23

Slide 23 text

Level 1: Make It Work public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
 …
 
 int delta;
 if (dx > 0) { // Contents are scrolling left
 delta = …;
 } else { // Contents are scrolling right
 delta = …;
 }
 
 offsetChildrenHorizontal(delta);
 
 if (dx > 0) {
 fillGrid(DIRECTION_START, …, recycler, state);
 } else {
 fillGrid(DIRECTION_END, …, recycler, state);
 }
 
 return -delta;
 }

Slide 24

Slide 24 text

Level 2: Data Set Changes onAdapterChanged() removeAllViews() notifyDataSetChanged() -> onLayoutChildren() Treat as a new layout, and just run a FILL…

Slide 25

Slide 25 text

Level 3: Add Some Flair scrollToPosition() Track Requested Position Trigger requestLayout() smoothScrollToPosition() Create a SmoothScroller instance Set the target position Invoke startSmoothScroll()

Slide 26

Slide 26 text

Level 3: Add Some Flair LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext()) {
 
 @Override
 public PointF computeScrollVectorForPosition(int targetPosition) {
 findFirstVisiblePosition(); final int rowOffset = …;
 final int columnOffset = …;
 
 return new PointF(columnOffset * stepWidth, rowOffset * stepHeight);
 }
 }; 
 scroller.setTargetPosition(position);
 startSmoothScroll(scroller);

Slide 27

Slide 27 text

Level 4: Zen Master supportsPredictiveItemAnimations() We have more to say about animations…

Slide 28

Slide 28 text

Default Item Animations

Slide 29

Slide 29 text

Default Item Animations

Slide 30

Slide 30 text

Level 4: Zen Master supportsPredictiveItemAnimations() We have more to say about animations… onLayoutChildren() [isPreLayout() == true] Note any removed views -> LayoutParams.isViewRemoved() Add extra views during FILL to cover space left behind onLayoutChildren() Place disappearing views off-screen

Slide 31

Slide 31 text

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
 … 
 if (state.isPreLayout()) {
 final View child = getChildAt(…); final LayoutParams lp = (LayoutParams) child.getLayoutParams();
 if (lp.isItemRemoved()) { //Track and count view removals… }
 }
 
 //Clear all attached views into the recycle bin
 detachAndScrapAttachedViews(recycler);
 
 //Fill the grid for the initial layout of views
 fillGrid(…);
 
 //Anything left? Lay out for disappearing animation
 if (!state.isPreLayout() && !recycler.getScrapList().isEmpty()) {
 final List scrapList = recycler.getScrapList();
 //Laying out a scrap item removes it from the list… //Beware concurrent modification! 
 …
 }
 }

Slide 32

Slide 32 text

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
 … 
 if (state.isPreLayout()) {
 final View child = getChildAt(…); final LayoutParams lp = (LayoutParams) child.getLayoutParams();
 if (lp.isItemRemoved()) { //Track and count view removals… }
 }
 
 //Clear all attached views into the recycle bin
 detachAndScrapAttachedViews(recycler);
 
 //Fill the grid for the initial layout of views…account for extras
 fillGrid(…);
 
 //Anything left? Lay out for disappearing animation
 if (!state.isPreLayout() && !recycler.getScrapList().isEmpty()) {
 final List scrapList = recycler.getScrapList();
 //Laying out a scrap item removes it from the list… //Beware concurrent modification! 
 …
 }
 }

Slide 33

Slide 33 text

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
 … 
 if (state.isPreLayout()) {
 final View child = getChildAt(…); final LayoutParams lp = (LayoutParams) child.getLayoutParams();
 if (lp.isItemRemoved()) { //Track and count view removals… }
 }
 
 //Clear all attached views into the recycle bin
 detachAndScrapAttachedViews(recycler);
 
 //Fill the grid for the initial layout of views
 fillGrid(…);
 
 //Anything left? Lay out for disappearing animation
 if (!state.isPreLayout() && !recycler.getScrapList().isEmpty()) {
 final List scrapList = recycler.getScrapList();
 //Laying out a scrap item removes it from the list… //Beware concurrent modification! 
 …
 }
 }

Slide 34

Slide 34 text

Predictive Item Animations

Slide 35

Slide 35 text

Predictive Item Animations

Slide 36

Slide 36 text

Resources RecyclerView Playground http://milehighandroid.com/ Building a RecyclerView LayoutManager http://wiresareobsolete.com/ Redux - Handling Disconnected Ranges Fixing LinearSmoothScroller https://blog.stylingandroid.com/scrolling-recyclerview-part-1/ Android SDK Reference https://developer.android.com/training/material/lists-cards.html