Advanced Scrolling Techniques on Android

Advanced Scrolling Techniques on Android

Scrolling is one of the most important gesture on mobile platforms. Indeed, it easily allows you to access a fairly large amount of content just by swiping your fingers on screen. In order to implement such gestures, the Android SDK comes with a bunch of scrolling containers: View, ListView, RecyclerView, ScrollView, etc. While using these components independently is relatively simple, it starts getting complicated when nesting them… In this session you will get a complete overview of how Android deals with scrolling in general and how to master the scrolling mechanism to create compelling mobile user experience.

E9bf8f6d5480ea2a2623df7dccfd1f70?s=128

Cyril Mottier

October 30, 2015
Tweet

Transcript

  1. Advanced Scrolling Techniques on Android

  2. @cyrilmottier

  3. capitainetrain.com • @capitainetrain captaintrain.com @captaintrain

  4. capitainetrain.com • @capitainetrain captaintrain.com @captaintrain

  5. None
  6. Joe, Max, and Kevin are in a boat…

  7. Joe, Max, and Kevin are designing an app…

  8. We've always defined ourselves by the ability to overcome the

    impossible. And we count these moments. These moments when we dare to aim higher, to break barriers, to reach for the stars, to make the unknown known. We count these moments as our proudest achievements. But we lost all that. Or perhaps we've just forgotten that we are still pioneers. And we've barely begun. And that our greatest accomplishments cannot be behind us, because our destiny lies above us. - Cooper (Interstellar) Don't talk like one of them. You're not! Even if you'd like to be. To them, you're just a freak, like me! They need you right now, but when they don't, they'll cast you out, like a leper! You see, their morals, their code, it's a bad joke. Dropped at the first sign of trouble. They're only as good as the world allows them to be. I'll show you. When the chips are down, these... these civilized people, they'll eat each other. See, I'm not a monster.
  9. Optician Joe We've always defined ourselves by the ability to

    overcome the impossible. And we count these moments. These moments when we dare to aim higher, to break barriers, to reach for the stars, to make the unknown known. We count these moments as our proudest achievements. But we lost all that. Or perhaps we've just forgotten that we are still pioneers. And we've barely begun. And that our greatest accomplishments cannot be behind us, because our destiny lies above us. - Cooper (Interstellar) Don't talk like one of them. You're not! Even if you'd like to be. To them, you're just a freak, like me! They need you right now, but when they don't, they'll cast you out, like a leper! You see, their morals, their code, it's a bad joke. Dropped at the first sign of trouble. They're only as good as the world allows them to be. I'll show you. When the chips are down, these... these civilized people, they'll eat each other. See, I'm not a monster. I'm just ahead of the curve. - The Joker (The Dark Knight) My name is Max. My world is fire and blood. Once, I was a cop. A road warrior searching for a righteous cause. As the world fell, each of us in our own way was broken. It was hard to know who was more crazy... me... or everyone else. - Max (Mad Max Fury Road) What is the most resilient parasite? Bacteria? A virus? An intestinal worm? An idea. Resilient... highly contagious. Once an idea has taken hold of the brain it's almost impossible to eradicate. An idea that is fully formed - fully understood - that sticks; right in there somewhere. - Cobb (Inception) It's light. Handle's adjustable for easy carrying, good for righties and lefties. Breaks down into four parts, undetectable by x-ray, ideal for quick, discreet interventions. A word on firepower. Titanium recharger, three thousand round clip with bursts of three to three hundred, and with the Replay button - another Zorg invention - it's even easier. - Zorg (Th Fifth Element) Anyway, like I was sayin', shrimp is the fruit of the sea. You can barbecue it, boil it, broil it, bake it, saute it. Dey's uh, shrimp- kabobs, shrimp creole, shrimp gumbo. Pan fried, deep fried, stir-fried. There's pineapple shrimp, lemon shrimp, coconut shrimp, pepper shrimp, shrimp soup, shrimp stew, shrimp salad, shrimp and potatoes, shrimp burger, shrimp sandwich. That- that's about it. - Bubba (Forrest Gump) You tell God the Father it was a kindness you done. I know you hurtin' and worryin', I can feel it on you, but you oughta quit on it now. Because I want it over and done. I do. I'm tired, boss. Tired of bein' on the road, lonely as a sparrow in the rain. Tired of not ever having me a buddy to be with, or tell me where we's coming from or going to, or why. Mostly I'm tired of people being ugly to each other. I'm tired of all the pain I feel and hear in the world everyday. There's too much of it. It's like pieces of glass in my head all the time. Can you understand? - John Coffey (The Green Mile) You're trying? Now? Where were you when it mattered? I needed this guy back when I was a kid. I don't need you now. It's too late now. Everything's already happened. You and Brendan don't seem to understand that. Let me explain something to you: the only thing I have in common with Brendan Conlon is that we have absolutely no use for you. - Tom Colon (Warrior)
  10. Salesman Max We've always defined ourselves by the ability to

    overcome the impossible. And we count these moments. These moments when we dare to aim higher, to break barriers, to reach for the stars, to make the unknown known. We count these moments as our proudest achievements. But we lost all that. Or perhaps we've just forgotten that we are still pioneers. And we've barely begun. And that our greatest accomplishments cannot be behind us, because our destiny lies above us. - Cooper (Interstellar) Don't talk like one of them. You're not! Even if you'd like to be. To them, you're just a freak, like me! They need you right now, but when they don't, they'll cast you out, like a leper! You see, their morals, their code, it's a bad joke. Dropped at the first sign of trouble. They're only as good as the world allows them to be. I'll show you. When the chips are down, these... these civilized people, they'll eat each other. See, I'm not a monster. I'm just ahead of the curve. - The Joker (The Dark Knight) My name is Max. My world is fire and blood. Once, I was a cop. A road warrior searching for a righteous cause. As the world fell, each of us in our own way was broken. It was hard to know who was more crazy... me... or everyone else. - Max (Mad Max Fury Road) What is the most resilient parasite? Bacteria? A virus? An intestinal worm? An idea. Resilient... highly contagious. Once an idea has taken hold of the brain it's almost impossible to eradicate. An idea that is fully formed - fully understood - that sticks; right in there somewhere. - Cobb (Inception) It's light. Handle's adjustable for easy carrying, good for righties and lefties. Breaks down into four parts, undetectable by x-ray, ideal for quick, discreet interventions. A word on firepower. Titanium recharger, three thousand round clip with bursts of three to three hundred, and with the Replay button - another Zorg invention - it's even easier. - Zorg (Th Fifth Element) Anyway, like I was sayin', shrimp is the fruit of the sea. You can barbecue it, boil it, broil it, bake it, saute it. Dey's uh, shrimp-kabobs, shrimp creole, shrimp gumbo. Pan fried, deep fried, stir-fried. There's pineapple shrimp, lemon shrimp, coconut shrimp, pepper shrimp, shrimp soup, shrimp stew, shrimp salad, shrimp and potatoes,
  11. We've always defined ourselves by the ability to overcome the

    impossible. And we count these moments. These moments when we dare to aim higher, to break barriers, to reach for the stars, to make the unknown known. We count these moments as our proudest achievements. But we lost all that. Or perhaps we've just forgotten that we are still pioneers. And we've barely begun. And that our greatest accomplishments cannot be behind us, because our destiny lies above us. - Cooper (Interstellar) Don't talk like one of them. You're not! Even if you'd like to be. To them, you're just a freak, like me! They need you right now, but when they don't, they'll cast you out, like a leper! You see, their morals, their code, it's a bad joke. Dropped at the first sign of trouble. They're only as good as the world allows them to be. I'll show you. When the chips are down, these... these civilized people, they'll eat each other. See, I'm not a monster. Dev / designer Kevin
  12. accomplishments cannot be behind us, because our destiny lies above

    us. - Cooper (Interstellar) Don't talk like one of them. You're not! Even if you'd like to be. To them, you're just a freak, like me! They need you right now, but when they don't, they'll cast you out, like a leper! You see, their morals, their code, it's a bad joke. Dropped at the first sign of trouble. They're only as good as the world allows them to be. I'll show you. When the chips are down, these... these civilized people, they'll eat each other. See, I'm not a monster. I'm just ahead of the curve. - The Joker (The Dark Knight) My name is Max. My world is fire and blood. Once, I was a cop. A road warrior searching for a righteous cause. As the world fell, each of us in our own way was broken. It was hard to know who was more crazy... me... or everyone else. - Max (Mad Max Fury Road) What is the most resilient parasite? Bacteria? A virus? Dev / designer Kevin
  13. Scrolling is essential

  14. Scrolling is essential Most important gesture on mobile

  15. Scrolling is essential Most important gesture on mobile (after tap?)

  16. Scroll /skɹoʊl/ The general process of moving the viewport -

    that is, the ‘window’ of content you’re looking at.
  17. Drag /drӕɡ/ A type of scrolling that occurs when a

    user drags her finger across the touch screen.
  18. Fling /flɪŋ/ A type of scrolling that occurs when a

    user drags and lifts her finger quickly. It is also known as ‘inertia scrolling’.
  19. View TextView AbsListView ScrollView ListView Horizontal ScrollView GridView ViewPager RecyclerView

    MapView Android ViewGroup
  20. iOS UIScrollView UITextView UITableView UICollectionView UIView

  21. How to listen to scrolling events?

  22. Listening to scrolls

  23. public class YoScrollView extends ScrollView {
 
 @Override
 protected void

    onScrollChanged(int l, int t, int oldl, int oldt) {
 super.onScrollChanged(l, t, oldl, oldt);
 
 // Called when scroll properties changed
 } // ... 
 } At the View level
  24. public class YoScrollView extends ScrollView {
 
 @Override
 protected void

    onScrollChanged(int l, int t, int oldl, int oldt) {
 super.onScrollChanged(l, t, oldl, oldt);
 
 // Called when scroll properties changed
 } // ... 
 } At the View level Old & new scroll values available
  25. findViewById(android.R.id.scrollView)
 .setOnScrollChangeListener(new OnScrollChangeListener() {
 @Override
 public void onScrollChange(View v, int

    scrollX, int scrollY, 
 int oldScrollX, int oldScrollY) {
 // Called when scroll properties changed
 }
 }); At the View level Requires API 23
  26. findViewById(android.R.id.scrollView)
 .setOnScrollChangeListener(new OnScrollChangeListener() {
 @Override
 public void onScrollChange(View v, int

    scrollX, int scrollY, 
 int oldScrollX, int oldScrollY) {
 // Called when scroll properties changed
 }
 }); At the View level Requires API 23 No more @Overrides and custom Views
  27. getWindow().getDecorView()
 .getViewTreeObserver()
 .addOnScrollChangedListener(
 new ViewTreeObserver.OnScrollChangedListener() {
 @Override
 public void onScrollChanged()

    {
 // A View in the hierarchy scrolled
 }
 }); At the View hierarchy level
  28. getWindow().getDecorView()
 .getViewTreeObserver()
 .addOnScrollChangedListener(
 new ViewTreeObserver.OnScrollChangedListener() {
 @Override
 public void onScrollChanged()

    {
 // A View in the hierarchy scrolled
 }
 }); At the View hierarchy level No parameters, no info : I’m blind!
  29. It’s quiz time! Let’s have fun…

  30. aView.setOnScrollChangeListener(new OnScrollChangeListener() {
 @Override
 public void onScrollChange(View v, int scrollX,

    int scrollY, 
 int oldScrollX, int oldScrollY) { 
 Log.d(TAG, "(" + scrollX + ", " + scrollY + ")"); 
 }
 });
  31. aView instanceof ScrollView

  32. aView instanceof ScrollView scroll(0, 1) scroll(0, 12) scroll(0, 23) scroll(0,

    46) scroll(0, 67) scroll(0, 69) scroll(0, 70) _
  33. aView instanceof WebView

  34. aView instanceof WebView scroll(0, 1) scroll(0, 9) scroll(0, 12) scroll(0,

    22) scroll(0, 28) scroll(0, 34) scroll(0, 36) _
  35. aView instanceof ListView

  36. aView instanceof ListView scroll(0, 0) scroll(0, 0) scroll(0, 0) scroll(0,

    0) scroll(0, -5) scroll(0, -1) scroll(0, 0) _
  37. aView instanceof DrawerLayout

  38. aView instanceof DrawerLayout _

  39. aView instanceof MapView

  40. aView instanceof MapView _

  41. Scrolling on Android is a mess

  42. Scrolling on Android is complicated Scrolling on Android

  43. Scrolling is complicated

  44. vs iOS Android

  45. vs Consistency Customisation vs

  46. 5 behaviours

  47. Initialisation 5 behaviours

  48. Movement tracking Initialisation 5 behaviours

  49. Inertia scrolling Movement Initialisation 5 behaviours

  50. Movement Inertia scrolling Scrollbars drawing Initialisation 5 behaviours

  51. Movement Inertia scrolling Scrollbars drawing Edges feedback Initialisation 5 behaviours

  52. Initialisation setScrollContainer(true) Inform the system the View is a scrollable

    container in its Window
  53. Initialisation @Override
 public boolean shouldDelayChildPressedState() {
 return true;
 } Prevents

    the pressed state from appearing when the user is actually trying to scroll the content.
  54. Initialisation ViewConfiguration.getMaximumFlingVelocity() getMinimumFlingVelocity() getScaledTouchSlop() and more…

  55. Movement tracking @Override
 public boolean onTouchEvent(MotionEvent event) {
 switch (event.getAction())

    {
 case MotionEvent.ACTION_DOWN:
 break;
 
 case MotionEvent.ACTION_MOVE:
 break;
 
 case MotionEvent.ACTION_UP:
 break;
 
 case MotionEvent.ACTION_CANCEL:
 break;
 }
 return true;
 }
  56. Movement tracking @Override
 public boolean onTouchEvent(MotionEvent event) {
 switch (event.getAction())

    {
 case MotionEvent.ACTION_DOWN:
 break;
 
 case MotionEvent.ACTION_MOVE:
 break;
 
 case MotionEvent.ACTION_UP:
 break;
 
 case MotionEvent.ACTION_CANCEL:
 break;
 }
 return true;
 } Stop animations and start a new drag gesture
  57. Movement tracking @Override
 public boolean onTouchEvent(MotionEvent event) {
 switch (event.getAction())

    {
 case MotionEvent.ACTION_DOWN:
 break;
 
 case MotionEvent.ACTION_MOVE:
 break;
 
 case MotionEvent.ACTION_UP:
 break;
 
 case MotionEvent.ACTION_CANCEL:
 break;
 }
 return true;
 } Move/drag the object according to the pointer
  58. Movement tracking @Override
 public boolean onTouchEvent(MotionEvent event) {
 switch (event.getAction())

    {
 case MotionEvent.ACTION_DOWN:
 break;
 
 case MotionEvent.ACTION_MOVE:
 break;
 
 case MotionEvent.ACTION_UP:
 break;
 
 case MotionEvent.ACTION_CANCEL:
 break;
 }
 return true;
 } Compute the object’s velocity and start animating it
  59. Movement tracking @Override
 public boolean onTouchEvent(MotionEvent event) {
 switch (event.getAction())

    {
 case MotionEvent.ACTION_DOWN:
 break;
 
 case MotionEvent.ACTION_MOVE:
 break;
 
 case MotionEvent.ACTION_UP:
 break;
 
 case MotionEvent.ACTION_CANCEL:
 break;
 }
 return true;
 } The gesture was intercepted by a parent
  60. Inertia scrolling @Override
 public boolean onTouchEvent(MotionEvent event) {
 if (mVelocityTracker

    == null) {
 mVelocityTracker = VelocityTracker.obtain();
 }
 mVelocityTracker.addMovement(event); 
 switch (event.getAction()) {
 case MotionEvent.ACTION_UP:
 mVelocityTracker.computeCurrentVelocity(1000);
 if (mVelocityTracker.getXVelocity() > mMinVelocity) {
 // Animate the object
 }
 // no break; 
 case MotionEvent.ACTION_CANCEL:
 mVelocityTracker.recycle();
 mVelocityTracker = null;
 break;
 }
 return true;
 }
  61. Inertia scrolling @Override
 public boolean onTouchEvent(MotionEvent event) {
 if (mVelocityTracker

    == null) {
 mVelocityTracker = VelocityTracker.obtain();
 }
 mVelocityTracker.addMovement(event); 
 switch (event.getAction()) {
 case MotionEvent.ACTION_UP:
 mVelocityTracker.computeCurrentVelocity(1000);
 if (mVelocityTracker.getXVelocity() > mMinVelocity) {
 // Animate the object
 }
 // no break; 
 case MotionEvent.ACTION_CANCEL:
 mVelocityTracker.recycle();
 mVelocityTracker = null;
 break;
 }
 return true;
 } Add MotionEvents from the current gesture to a VelocityTracker
  62. Inertia scrolling @Override
 public boolean onTouchEvent(MotionEvent event) {
 if (mVelocityTracker

    == null) {
 mVelocityTracker = VelocityTracker.obtain();
 }
 mVelocityTracker.addMovement(event); 
 switch (event.getAction()) {
 case MotionEvent.ACTION_UP:
 mVelocityTracker.computeCurrentVelocity(1000);
 if (mVelocityTracker.getXVelocity() > mMinVelocity) {
 // Animate the object
 }
 // no break; 
 case MotionEvent.ACTION_CANCEL:
 mVelocityTracker.recycle();
 mVelocityTracker = null;
 break;
 }
 return true;
 } Finger velocity was important enough to initiate a fling
  63. Inertia scrolling @Override
 public boolean onTouchEvent(MotionEvent event) {
 if (mVelocityTracker

    == null) {
 mVelocityTracker = VelocityTracker.obtain();
 }
 mVelocityTracker.addMovement(event); 
 switch (event.getAction()) {
 case MotionEvent.ACTION_UP:
 mVelocityTracker.computeCurrentVelocity(1000);
 if (mVelocityTracker.getXVelocity() > mMinVelocity) {
 // Animate the object
 }
 // no break; 
 case MotionEvent.ACTION_CANCEL:
 mVelocityTracker.recycle();
 mVelocityTracker = null;
 break;
 }
 return true;
 } Clean up our mess with the VelocityTracker
  64. Inertia scrolling public void fling() {
 mOverScroller.fling(mStartX, mStartY, // start

    x/y
 mVX, mVY, // velocity x/y
 mMinX, mMaxX, // min/max x
 mMinY, mMaxY); // min/max y
 postOnAnimation(mScrollRunnable);
 } private final Runnable mScrollRunnable = new Runnable() {
 @Override
 public void run() {
 if (mOverScroller.computeScrollOffset()) {
 final int x = mOverScroller.getCurrX();
 final int y = mOverScroller.getCurrY();
 // Move object to (x, y).
 postOnAnimation(this);
 } else {
 // animation is over
 }
 }
 };
  65. Inertia scrolling public void fling() {
 mOverScroller.fling(mStartX, mStartY, // start

    x/y
 mVX, mVY, // velocity x/y
 mMinX, mMaxX, // min/max x
 mMinY, mMaxY); // min/max y
 postOnAnimation(mScrollRunnable);
 } private final Runnable mScrollRunnable = new Runnable() {
 @Override
 public void run() {
 if (mOverScroller.computeScrollOffset()) {
 final int x = mOverScroller.getCurrX();
 final int y = mOverScroller.getCurrY();
 // Move object to (x, y).
 postOnAnimation(this);
 } else {
 // animation is over
 }
 }
 }; Pass all gesture info to initiate a fling animation
  66. Inertia scrolling public void fling() {
 mOverScroller.fling(mStartX, mStartY, // start

    x/y
 mVX, mVY, // velocity x/y
 mMinX, mMaxX, // min/max x
 mMinY, mMaxY); // min/max y
 postOnAnimation(mScrollRunnable);
 } private final Runnable mScrollRunnable = new Runnable() {
 @Override
 public void run() {
 if (mOverScroller.computeScrollOffset()) {
 final int x = mOverScroller.getCurrX();
 final int y = mOverScroller.getCurrY();
 // Move object to (x, y).
 postOnAnimation(this);
 } else {
 // animation is over
 }
 }
 }; Schedule the next scrolling frame
  67. Inertia scrolling public void fling() {
 mOverScroller.fling(mStartX, mStartY, // start

    x/y
 mVX, mVY, // velocity x/y
 mMinX, mMaxX, // min/max x
 mMinY, mMaxY); // min/max y
 postOnAnimation(mScrollRunnable);
 } private final Runnable mScrollRunnable = new Runnable() {
 @Override
 public void run() {
 if (mOverScroller.computeScrollOffset()) {
 final int x = mOverScroller.getCurrX();
 final int y = mOverScroller.getCurrY();
 // Move object to (x, y).
 postOnAnimation(this);
 } else {
 // animation is over
 }
 }
 }; Use the current OverScroller state to determine the object’s position
  68. Inertia scrolling public void fling() {
 mOverScroller.fling(mStartX, mStartY, // start

    x/y
 mVX, mVY, // velocity x/y
 mMinX, mMaxX, // min/max x
 mMinY, mMaxY); // min/max y
 postOnAnimation(mScrollRunnable);
 } private final Runnable mScrollRunnable = new Runnable() {
 @Override
 public void run() {
 if (mOverScroller.computeScrollOffset()) {
 final int x = mOverScroller.getCurrX();
 final int y = mOverScroller.getCurrY();
 // Move object to (x, y).
 postOnAnimation(this);
 } else {
 // animation is over
 }
 }
 }; Schedule the next scrolling frame
  69. Inertia scrolling public void fling() {
 mOverScroller.fling(mStartX, mStartY, // start

    x/y
 mVX, mVY, // velocity x/y
 mMinX, mMaxX, // min/max x
 mMinY, mMaxY); // min/max y
 postOnAnimation(mScrollRunnable);
 } private final Runnable mScrollRunnable = new Runnable() {
 @Override
 public void run() {
 if (mOverScroller.computeScrollOffset()) {
 final int x = mOverScroller.getCurrX();
 final int y = mOverScroller.getCurrY();
 // Move object to (x, y).
 postOnAnimation(this);
 } else {
 // animation is over
 }
 }
 }; Animation is complete. Let’s rest
  70. Scrollbars drawing @Override
 protected int computeVerticalScrollExtent() {
 return getHeight();
 }


    
 @Override
 protected int computeVerticalScrollOffset() {
 return mOffsetY;
 }
 
 @Override
 protected int computeVerticalScrollRange() {
 return mContentHeight;
 }
  71. Scrollbars drawing @Override
 protected int computeVerticalScrollExtent() {
 return getHeight();
 }


    
 @Override
 protected int computeVerticalScrollOffset() {
 return mOffsetY;
 }
 
 @Override
 protected int computeVerticalScrollRange() {
 return mContentHeight;
 }
  72. Scrollbars drawing @Override
 protected int computeVerticalScrollExtent() {
 return getHeight();
 }


    
 @Override
 protected int computeVerticalScrollOffset() {
 return mOffsetY;
 }
 
 @Override
 protected int computeVerticalScrollRange() {
 return mContentHeight;
 }
  73. Scrollbars drawing @Override
 protected int computeVerticalScrollExtent() {
 return getHeight();
 }


    
 @Override
 protected int computeVerticalScrollOffset() {
 return mOffsetY;
 }
 
 @Override
 protected int computeVerticalScrollRange() {
 return mContentHeight;
 }
  74. Scrollbars drawing if (!awakenScrollBars()) { postInvalidateOnAnimation(); }

  75. Edges feedback

  76. Edges feedback EdgeEffect

  77. Edges feedback @Override public void draw(Canvas canvas) { super.draw(canvas); if

    (!mTopEdgeEffect.isFinished()) { canvas.save(); canvas.translate(getPaddingLeft(), 0); final int w = getWidth() - getPaddingLeft() - getPaddingRight(); mTopEdgeEffect.setSize(w, getHeight()); if (mTopEdgeEffect.draw(canvas)) { postInvalidateOnAnimation(); } canvas.restore(); } }
  78. Edges feedback @Override public void draw(Canvas canvas) { super.draw(canvas); if

    (!mTopEdgeEffect.isFinished()) { canvas.save(); canvas.translate(getPaddingLeft(), getPaddingTop()); final int w = getWidth() - getPaddingLeft() - getPaddingRight(); mTopEdgeEffect.setSize(w, getHeight()); if (mTopEdgeEffect.draw(canvas)) { postInvalidateOnAnimation(); } canvas.restore(); } } Draw only if animation is in progress
  79. Edges feedback @Override public void draw(Canvas canvas) { super.draw(canvas); if

    (!mTopEdgeEffect.isFinished()) { canvas.save(); canvas.translate(getPaddingLeft(), getPaddingTop()); final int w = getWidth() - getPaddingLeft() - getPaddingRight(); mTopEdgeEffect.setSize(w, getHeight()); if (mTopEdgeEffect.draw(canvas)) { postInvalidateOnAnimation(); } canvas.restore(); } } Move the Canvas to the correct position
  80. Edges feedback @Override public void draw(Canvas canvas) { super.draw(canvas); if

    (!mTopEdgeEffect.isFinished()) { canvas.save(); canvas.translate(getPaddingLeft(), getPaddingTop()); final int w = getWidth() - getPaddingLeft() - getPaddingRight(); mTopEdgeEffect.setSize(w, getHeight()); if (mTopEdgeEffect.draw(canvas)) { postInvalidateOnAnimation(); } canvas.restore(); } } Set the size to the edge feedback
  81. Edges feedback @Override public void draw(Canvas canvas) { super.draw(canvas); if

    (!mTopEdgeEffect.isFinished()) { canvas.save(); canvas.translate(getPaddingLeft(), getPaddingTop()); final int w = getWidth() - getPaddingLeft() - getPaddingRight(); mTopEdgeEffect.setSize(w, getHeight()); if (mTopEdgeEffect.draw(canvas)) { postInvalidateOnAnimation(); } canvas.restore(); } } Draw this frame & schedule the next one
  82. Edges feedback onAbsorb(int velocity) onPull(float deltaDistance) onRelease()

  83. Edges feedback onAbsorb(int velocity) onPull(float deltaDistance) onRelease() Call when a

    fling reaches a scroll boundary
  84. Edges feedback onAbsorb(int velocity) onPull(float deltaDistance) onRelease() Called when content

    is pulled away from an edge
  85. Edges feedback onAbsorb(int velocity) onPull(float deltaDistance) onRelease() Called when the

    object is released
  86. What about nesting scroll containers ?

  87. Simple and elementary solution…

  88. Simple and elementary solution… Don’t do it!

  89. Simple and elementary solution… One axis, one scroll

  90. Bezel scrolling DrawerLayout | ViewPager

  91. Bezel scrolling DrawerLayout | ViewPager

  92. Bezel scrolling DrawerLayout | ViewPager

  93. Boundary scrolling ViewPager

  94. Boundary scrolling ViewPager

  95. View.canScrollVertically(int) canScrollHorizontally(int)

  96. Nested scrolling

  97. Nested scrolling

  98. Nested scrolling ScrollView API 7 API 4 API 21 NestedScrollView

    RecyclerView HorizontalScrollView ListView
  99. Thank you! @cyrilmottier cyrilmottier.com

  100. Fonts Geared Slab Mission Script Menlo Resources Dressed for Iceland

    - Cécile Bernard Moelwynion, Eryri, Cymru - Marc Poppleton Scroll icon - Darren Wilson