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

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. Advanced
    Scrolling Techniques
    on Android

    View Slide

  2. @cyrilmottier

    View Slide

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

    View Slide

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

    View Slide

  5. View Slide

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

    View Slide

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

    View Slide

  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.

    View Slide

  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)

    View Slide

  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,

    View Slide

  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

    View Slide

  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

    View Slide

  13. Scrolling is essential

    View Slide

  14. Scrolling is essential
    Most important gesture on mobile

    View Slide

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

    View Slide

  16. Scroll
    /skɹoʊl/
    The general process of moving the viewport - that is, the
    ‘window’ of content you’re looking at.

    View Slide

  17. Drag
    /drӕɡ/
    A type of scrolling that occurs when a user drags her finger
    across the touch screen.

    View Slide

  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’.

    View Slide

  19. View TextView
    AbsListView
    ScrollView
    ListView
    Horizontal
    ScrollView
    GridView
    ViewPager
    RecyclerView
    MapView
    Android
    ViewGroup

    View Slide

  20. iOS
    UIScrollView
    UITextView
    UITableView
    UICollectionView
    UIView

    View Slide

  21. How to listen to
    scrolling events?

    View Slide

  22. Listening to scrolls

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  27. getWindow().getDecorView()

    .getViewTreeObserver()

    .addOnScrollChangedListener(

    new ViewTreeObserver.OnScrollChangedListener() {

    @Override

    public void onScrollChanged() {

    // A View in the hierarchy scrolled

    }

    });
    At the View hierarchy level

    View Slide

  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!

    View Slide

  29. It’s quiz time!
    Let’s have fun…

    View Slide

  30. aView.setOnScrollChangeListener(new OnScrollChangeListener() {

    @Override

    public void onScrollChange(View v, int scrollX, int scrollY, 

    int oldScrollX, int oldScrollY) {

    Log.d(TAG, "(" + scrollX + ", " + scrollY + ")"); 

    }

    });

    View Slide

  31. aView instanceof ScrollView

    View Slide

  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)
    _

    View Slide

  33. aView instanceof WebView

    View Slide

  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)
    _

    View Slide

  35. aView instanceof ListView

    View Slide

  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)
    _

    View Slide

  37. aView instanceof DrawerLayout

    View Slide

  38. aView instanceof DrawerLayout
    _

    View Slide

  39. aView instanceof MapView

    View Slide

  40. aView instanceof MapView
    _

    View Slide

  41. Scrolling on Android
    is a mess

    View Slide

  42. Scrolling on Android
    is complicated
    Scrolling on Android

    View Slide

  43. Scrolling is complicated

    View Slide

  44. vs
    iOS
    Android

    View Slide

  45. vs
    Consistency
    Customisation
    vs

    View Slide

  46. 5
    behaviours

    View Slide

  47. Initialisation
    5
    behaviours

    View Slide

  48. Movement tracking
    Initialisation
    5
    behaviours

    View Slide

  49. Inertia scrolling
    Movement
    Initialisation
    5
    behaviours

    View Slide

  50. Movement
    Inertia scrolling
    Scrollbars drawing
    Initialisation
    5
    behaviours

    View Slide

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

    View Slide

  52. Initialisation
    setScrollContainer(true)
    Inform the system the View is a
    scrollable container in its Window

    View Slide

  53. Initialisation
    @Override

    public boolean shouldDelayChildPressedState() {

    return true;

    }
    Prevents the pressed state from
    appearing when the user is actually
    trying to scroll the content.

    View Slide

  54. Initialisation
    ViewConfiguration.getMaximumFlingVelocity()
    getMinimumFlingVelocity()
    getScaledTouchSlop()
    and more…

    View Slide

  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;

    }

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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;

    }

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    }

    }

    };

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  70. Scrollbars drawing
    @Override

    protected int computeVerticalScrollExtent() {

    return getHeight();

    }


    @Override

    protected int computeVerticalScrollOffset() {

    return mOffsetY;

    }


    @Override

    protected int computeVerticalScrollRange() {

    return mContentHeight;

    }

    View Slide

  71. Scrollbars drawing
    @Override

    protected int computeVerticalScrollExtent() {

    return getHeight();

    }


    @Override

    protected int computeVerticalScrollOffset() {

    return mOffsetY;

    }


    @Override

    protected int computeVerticalScrollRange() {

    return mContentHeight;

    }

    View Slide

  72. Scrollbars drawing
    @Override

    protected int computeVerticalScrollExtent() {

    return getHeight();

    }


    @Override

    protected int computeVerticalScrollOffset() {

    return mOffsetY;

    }


    @Override

    protected int computeVerticalScrollRange() {

    return mContentHeight;

    }

    View Slide

  73. Scrollbars drawing
    @Override

    protected int computeVerticalScrollExtent() {

    return getHeight();

    }


    @Override

    protected int computeVerticalScrollOffset() {

    return mOffsetY;

    }


    @Override

    protected int computeVerticalScrollRange() {

    return mContentHeight;

    }

    View Slide

  74. Scrollbars drawing
    if (!awakenScrollBars()) {
    postInvalidateOnAnimation();
    }

    View Slide

  75. Edges feedback

    View Slide

  76. Edges feedback
    EdgeEffect

    View Slide

  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();
    }
    }

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  82. Edges feedback
    onAbsorb(int velocity)
    onPull(float deltaDistance)
    onRelease()

    View Slide

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

    View Slide

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

    View Slide

  85. Edges feedback
    onAbsorb(int velocity)
    onPull(float deltaDistance)
    onRelease()
    Called when the
    object is released

    View Slide

  86. What about nesting
    scroll containers ?

    View Slide

  87. Simple and elementary solution…

    View Slide

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

    View Slide

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

    View Slide

  90. Bezel scrolling
    DrawerLayout | ViewPager

    View Slide

  91. Bezel scrolling
    DrawerLayout | ViewPager

    View Slide

  92. Bezel scrolling
    DrawerLayout | ViewPager

    View Slide

  93. Boundary scrolling
    ViewPager

    View Slide

  94. Boundary scrolling
    ViewPager

    View Slide

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

    View Slide

  96. Nested scrolling

    View Slide

  97. Nested scrolling

    View Slide

  98. Nested scrolling
    ScrollView
    API 7
    API 4 API 21
    NestedScrollView
    RecyclerView
    HorizontalScrollView
    ListView

    View Slide

  99. Thank you!
    @cyrilmottier cyrilmottier.com

    View Slide

  100. Fonts
    Geared Slab
    Mission Script
    Menlo
    Resources
    Dressed for Iceland - Cécile Bernard
    Moelwynion, Eryri, Cymru - Marc Poppleton
    Scroll icon - Darren Wilson

    View Slide