$30 off During Our Annual Pro Sale. View Details »

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.

Cyril Mottier

October 30, 2015
Tweet

More Decks by Cyril Mottier

Other Decks in Programming

Transcript

  1. 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.
  2. 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)
  3. 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,
  4. 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
  5. 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
  6. Scroll /skɹoʊl/ The general process of moving the viewport -

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

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

    user drags and lifts her finger quickly. It is also known as ‘inertia scrolling’.
  9. 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
  10. 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
  11. 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
  12. 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
  13. aView.setOnScrollChangeListener(new OnScrollChangeListener() {
 @Override
 public void onScrollChange(View v, int scrollX,

    int scrollY, 
 int oldScrollX, int oldScrollY) { 
 Log.d(TAG, "(" + scrollX + ", " + scrollY + ")"); 
 }
 });
  14. aView instanceof WebView scroll(0, 1) scroll(0, 9) scroll(0, 12) scroll(0,

    22) scroll(0, 28) scroll(0, 34) scroll(0, 36) _
  15. Initialisation @Override
 public boolean shouldDelayChildPressedState() {
 return true;
 } Prevents

    the pressed state from appearing when the user is actually trying to scroll the content.
  16. 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;
 }
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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;
 }
  22. 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
  23. 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
  24. 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
  25. 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
 }
 }
 };
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. Scrollbars drawing @Override
 protected int computeVerticalScrollExtent() {
 return getHeight();
 }


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


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


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


    
 @Override
 protected int computeVerticalScrollOffset() {
 return mOffsetY;
 }
 
 @Override
 protected int computeVerticalScrollRange() {
 return mContentHeight;
 }
  35. 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(); } }
  36. 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
  37. 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
  38. 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
  39. 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
  40. Nested scrolling ScrollView API 7 API 4 API 21 NestedScrollView

    RecyclerView HorizontalScrollView ListView
  41. Fonts Geared Slab Mission Script Menlo Resources Dressed for Iceland

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