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

Secrets of the Support Library (Droidcon NYC 2016)

Chris Banes
September 30, 2016

Secrets of the Support Library (Droidcon NYC 2016)

The support library has become a necessity for the majority of Android developers. This talk will delve into some of its secrets, from bugs we’ve fixed, things we haven’t (yet), and how some of the features you’re using actually work back to API 9.

Chris Banes

September 30, 2016
Tweet

More Decks by Chris Banes

Other Decks in Technology

Transcript

  1. SECRETS OF THE
    SUPPORT LIBRARY
    +ChrisBanes

    @chrisbanes

    View Slide

  2. SECRETS OF THE SUPPORT LIBRARY
    banes.me/dcnyc16
    http://

    View Slide

  3. SECRETS OF THE SUPPORT LIBRARY
    DayNight

    View Slide

  4. Allows your app to be
    automatically themed
    based on time of day

    View Slide

  5. Allows your app to be
    automatically themed
    based on time of day

    View Slide

  6. AppCompatDelegate.setDefaultNightMode(...)
    Change night mode
    Change theme
    Theme.AppCompat.DayNight

    View Slide

  7. MODE_NIGHT_FOLLOW_SYSTEM
    Follows the system night mode
    DEFAULT
    MODE_NIGHT_YES
    Always night mode
    MODE_NIGHT_NO
    Never night mode (aka day mode)
    MODE_NIGHT_AUTO
    Switches based on time of day

    View Slide

  8. MODE_NIGHT_FOLLOW_SYSTEM
    Follows the system night mode
    DEFAULT
    MODE_NIGHT_YES
    Always night mode
    MODE_NIGHT_NO
    Never night mode (aka day mode)
    MODE_NIGHT_AUTO
    Switches based on time of day

    View Slide

  9. MODE_NIGHT_FOLLOW_SYSTEM
    Follows the system night mode
    DEFAULT
    MODE_NIGHT_YES
    Always night mode
    MODE_NIGHT_NO
    Never night mode (aka day mode)
    MODE_NIGHT_AUTO
    Switches based on time of day

    View Slide

  10. Night mode has existed since API 8
    -night
    -notnight

    View Slide

  11. Car mode Docked ⚓
    © Philip Greenspun

    View Slide

  12. public final class Configuration {
    // Lots of other configuration fields
    public int uiMode;
    }

    View Slide

  13. public static final int UI_MODE_NIGHT_MASK;

    public static final int UI_MODE_NIGHT_UNDEFINED;
    public static final int UI_MODE_NIGHT_NO;
    public static final int UI_MODE_NIGHT_YES;

    View Slide

  14. public static final int UI_MODE_NIGHT_MASK;

    public static final int UI_MODE_NIGHT_UNDEFINED;
    public static final int UI_MODE_NIGHT_NO;
    public static final int UI_MODE_NIGHT_YES;

    View Slide

  15. public static final int UI_MODE_NIGHT_MASK;

    public static final int UI_MODE_NIGHT_UNDEFINED;
    public static final int UI_MODE_NIGHT_NO;
    public static final int UI_MODE_NIGHT_YES;

    View Slide

  16. public static final int UI_MODE_NIGHT_NO;
    public static final int UI_MODE_NIGHT_YES;
    AppCompatDelegate.MODE_NIGHT_NO
    AppCompatDelegate.MODE_NIGHT_YES

    View Slide

  17. Resources res = getResources();
    Configuration config = res.getConfiguration();
    // Update the UI Mode to reflect the new night mode
    config.uiMode = newNightMode
    | (config.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
    // Now update the configuration
    res.updateConfiguration(config, ...);

    View Slide

  18. Resources res = getResources();
    Configuration config = res.getConfiguration();
    // Update the UI Mode to reflect the new night mode
    config.uiMode = newNightMode
    | (config.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
    // Now update the configuration
    res.updateConfiguration(config, ...);
    Configuration.UI_MODE_NIGHT_YES

    View Slide

  19. Resources res = getResources();
    Configuration config = res.getConfiguration();
    // Update the UI Mode to reflect the new night mode
    config.uiMode = newNightMode
    | (config.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
    // Now update the configuration
    res.updateConfiguration(config, ...);

    View Slide

  20. values-night
    drawable-night
    raw-night

    View Slide

  21. parent=“Theme.AppCompat.Light" />
    parent="Theme.AppCompat" />
    res/values-night
    res/values

    View Slide

  22. SECRETS OF THE SUPPORT LIBRARY
    AppBarLayout & flinging
    http://b.android.com/179501

    View Slide

  23. User swipes up, collapsing the
    AppBarLayout and scrolling the list

    View Slide

  24. User swipes down, scrolling the list

    View Slide

  25. User needs to swipe down again,
    to expand the AppBarLayout

    View Slide

  26. The problem:
    Flinging does not transfer
    left over velocity

    View Slide

  27. The goal:
    Flinging transfers

    velocity when it reaches

    the end

    View Slide

  28. >
    SECRETS OF THE SUPPORT LIBRARY
    Basic Scrolling
    APPBARLAYOUT FLINGING

    View Slide

  29. onTouchEvent(MotionEvent)
    onInterceptTouchEvent(MotionEvent)
    There’s also but we’ll skip that

    View Slide

  30. ACTION_DOWN

    View Slide

  31. ACTION_DOWN
    // ev = MotionEvent parameter
    // Initialise VelocityTracker and add event
    mVelocityTracker = VelocityTracker.obtain();
    mVelocityTracker.addMovement(ev);
    int y = ev.getY(); // 1020
    mLastMotionY = y;

    View Slide

  32. ACTION_MOVE
    A scroll is usually made up with 100s of events

    View Slide

  33. ACTION_MOVE
    // Add event to velocity tracker
    mVelocityTracker.addMovement(ev);
    int y = ev.getY(); // 1010
    // Calculate difference from last event
    int dy = mLastMotionY - y; // -10
    for (int i = 0; i < getChildCount(); i++) {
    View child = getChildAt(i);
    child.offsetTopAndBottom(dy);
    }A


    View Slide

  34. ACTION_MOVE
    // Add event to velocity tracker
    mVelocityTracker.addMovement(ev);
    int y = ev.getY(); // 1010
    // Calculate difference from last event
    int dy = mLastMotionY - y; // -10
    for (int i = 0; i < getChildCount(); i++) {
    View child = getChildAt(i);
    child.offsetTopAndBottom(dy);
    }A

    // Finally update our recorded Y
    mLastMotionY = y;

    View Slide

  35. ACTION_UP

    View Slide

  36. ACTION_UP
    // Calculate velocity over 1000ms
    mVelocityTracker.computeCurrentVelocity(1000);
    float velY = mVelocityTracker.getYVelocity();
    // TODO Implement some flinging
    fling(velY);

    View Slide

  37. OverScroller mScroller = new OverScroller();
    void fling(int velocityY) {
    // How much we can scroll
    int scrollRange = ...;
    mScroller.fling(mScrollX, mScrollY,
    0, velocityY,
    0, 0,
    0, scrollRange,
    0, height / 2);
    ViewCompat.postInvalidateOnAnimation(this);
    }

    View Slide

  38. OverScroller mScroller = new OverScroller();
    void fling(int velocityY) {
    // How much we can scroll
    int scrollRange = ...;
    mScroller.fling(mScrollX, mScrollY,
    0, velocityY,
    0, 0,
    0, scrollRange,
    0, height / 2);
    ViewCompat.postInvalidateOnAnimation(this);
    }

    View Slide

  39. OverScroller mScroller = new OverScroller();
    void fling(int velocityY) {
    // How much we can scroll
    int scrollRange = ...;
    mScroller.fling(mScrollX, mScrollY,
    0, velocityY,
    0, 0,
    0, scrollRange,
    0, height / 2);
    ViewCompat.postInvalidateOnAnimation(this);
    }
    // Passed in from touch handling

    View Slide

  40. OverScroller mScroller = new OverScroller();
    void fling(int velocityY) {
    // How much we can scroll
    int scrollRange = ...;
    mScroller.fling(mScrollX, mScrollY,
    0, velocityY,
    0, 0,
    0, scrollRange,
    0, height / 2);
    ViewCompat.postInvalidateOnAnimation(this);
    }

    View Slide

  41. OverScroller mScroller = new OverScroller();
    void fling(int velocityY) {
    // How much we can scroll
    int scrollRange = ...;
    mScroller.fling(mScrollX, mScrollY,
    0, velocityY,
    0, 0,
    0, scrollRange,
    0, height / 2);
    ViewCompat.postInvalidateOnAnimation(this);
    }

    View Slide

  42. OverScroller mScroller = new OverScroller();
    void fling(int velocityY) {
    // How much we can scroll
    int scrollRange = ...;
    mScroller.fling(mScrollX, mScrollY,
    0, velocityY,
    0, 0,
    0, scrollRange,
    0, height / 2);
    ViewCompat.postInvalidateOnAnimation(this);
    }
    // Current scroll position

    View Slide

  43. OverScroller mScroller = new OverScroller();
    void fling(int velocityY) {
    // How much we can scroll
    int scrollRange = ...;
    mScroller.fling(mScrollX, mScrollY,
    0, velocityY,
    0, 0,
    0, scrollRange,
    0, height / 2);
    ViewCompat.postInvalidateOnAnimation(this);
    }
    // Fling velocity

    View Slide

  44. OverScroller mScroller = new OverScroller();
    void fling(int velocityY) {
    // How much we can scroll
    int scrollRange = ...;
    mScroller.fling(mScrollX, mScrollY,
    0, velocityY,
    0, 0,
    0, scrollRange,
    0, height / 2);
    ViewCompat.postInvalidateOnAnimation(this);
    }
    // minX, maxX

    View Slide

  45. OverScroller mScroller = new OverScroller();
    void fling(int velocityY) {
    // How much we can scroll
    int scrollRange = ...;
    mScroller.fling(mScrollX, mScrollY,
    0, velocityY,
    0, 0,
    0, scrollRange,
    0, height / 2);
    ViewCompat.postInvalidateOnAnimation(this);
    }
    // minY, maxY

    View Slide

  46. OverScroller mScroller = new OverScroller();
    void fling(int velocityY) {
    // How much we can scroll
    int scrollRange = ...;
    mScroller.fling(mScrollX, mScrollY,
    0, velocityY,
    0, 0,
    0, scrollRange,
    0, height / 2);
    ViewCompat.postInvalidateOnAnimation(this);
    }
    // overscroll X, Y

    View Slide

  47. OverScroller mScroller = new OverScroller();
    void fling(int velocityY) {
    // How much we can scroll
    int scrollRange = ...;
    mScroller.fling(mScrollX, mScrollY,
    0, velocityY,
    0, 0,
    0, scrollRange,
    0, height / 2);
    ViewCompat.postInvalidateOnAnimation(this);
    }

    View Slide

  48. @Override
    public void computeScroll() {
    if (mScroller.computeScrollOffset()) {
    int x = mScroller.getCurrX();
    int y = mScroller.getCurrY();
    // Update the scroll position
    scrollTo(x, y);
    }
    }

    View Slide

  49. >
    SECRETS OF THE SUPPORT LIBRARY
    Nested Scrolling
    APPBARLAYOUT FLINGING

    View Slide

  50. Nested scrolling views was difficult

    View Slide

  51. Nested scrolling views was difficult








    View Slide

  52. View Slide






  53. View Slide






  54. View Slide







  55. View Slide

  56. Nested scrolling was created

    to solve this

    View Slide

  57. An event system where scroll events are dispatched

    up the view hierarchy

    View Slide

  58. ScrollView
    LinearLayout
    ImageView ListView

    View Slide

  59. void setNestedScrollingEnabled(boolean enabled);
    boolean isNestedScrollingEnabled();
    boolean startNestedScroll(int axes);
    boolean hasNestedScrollingParent();
    boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed,

    int[] offsetInWindow);
    boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,

    int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);
    boolean dispatchNestedPreFling(float velocityX, float velocityY);
    boolean dispatchNestedFling(float velocityX, float velocityY,

    boolean consumed);
    void stopNestedScroll();
    Sending

    View Slide

  60. void setNestedScrollingEnabled(boolean enabled);
    boolean isNestedScrollingEnabled();
    boolean startNestedScroll(int axes);
    boolean hasNestedScrollingParent();
    boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed,

    int[] offsetInWindow);
    boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,

    int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);
    boolean dispatchNestedPreFling(float velocityX, float velocityY);
    boolean dispatchNestedFling(float velocityX, float velocityY,

    boolean consumed);
    void stopNestedScroll();
    Sending

    View Slide

  61. boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed,

    int[] offsetInWindow);
    boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,

    int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);
    boolean dispatchNestedPreFling(float velocityX, float velocityY);
    boolean dispatchNestedFling(float velocityX, float velocityY,

    boolean consumed);
    Sending

    View Slide

  62. boolean onStartNestedScroll(View child, View target,

    int nestedScrollAxes);
    void onNestedScrollAccepted(View child, View target,

    int nestedScrollAxes);
    void onStopNestedScroll(View target);
    int getNestedScrollAxes();
    Receiving
    void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
    void onNestedScroll(View target, int dxConsumed, int dyConsumed,

    int dxUnconsumed, int dyUnconsumed);
    boolean onNestedPreFling(View target, float velocityX,

    float velocityY);
    boolean onNestedFling(View target, float velocityX,

    float velocityY, boolean consumed);

    View Slide

  63. boolean onStartNestedScroll(View child, View target,

    int nestedScrollAxes);
    void onNestedScrollAccepted(View child, View target,

    int nestedScrollAxes);
    void onStopNestedScroll(View target);
    int getNestedScrollAxes();
    Receiving
    void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
    void onNestedScroll(View target, int dxConsumed, int dyConsumed,

    int dxUnconsumed, int dyUnconsumed);
    boolean onNestedPreFling(View target, float velocityX,

    float velocityY);
    boolean onNestedFling(View target, float velocityX,

    float velocityY, boolean consumed);

    View Slide

  64. Receiving
    void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
    void onNestedScroll(View target, int dxConsumed, int dyConsumed,

    int dxUnconsumed, int dyUnconsumed);
    boolean onNestedPreFling(View target, float velocityX,

    float velocityY);
    boolean onNestedFling(View target, float velocityX,

    float velocityY, boolean consumed);

    View Slide

  65. Two things to know about the API…

    View Slide

  66. Sending (scrolling view)
    boolean dispatchNestedPreScroll(int dx, int dy,

    int[] consumed, int[] offsetInWindow);
    Receiving (parent)
    void onNestedPreScroll(View target, int dx, int dy,

    int[] consumed);

    View Slide

  67. Pre vs Non-Pre
    Scroll events allow the parent to observe what the view scrolled
    The view then scrolls the remainder
    Pre-scroll events allow a parent to intercept and consume
    the event

    View Slide

  68. Let’s see how it happens…

    View Slide

  69. ACTION_DOWN

    View Slide

  70. ACTION_DOWN
    // Initialise VelocityTracker and add event
    mVelocityTracker.addMovement(ev);
    int y = ev.getY(); // 1020
    mInitialMotionY = y;
    mLastMotionY = y;
    startNestedScroll(SCROLL_AXIS_VERTICAL);

    View Slide

  71. ACTION_DOWN
    startNestedScroll(SCROLL_AXIS_VERTICAL);
    // Initialise VelocityTracker and add event
    mVelocityTracker.addMovement(ev);
    int y = ev.getY(); // 1020
    mInitialMotionY = y;
    mLastMotionY = y;

    View Slide

  72. ACTION_DOWN
    startNestedScroll(SCROLL_AXIS_VERTICAL);
    @Override
    boolean onStartNestedScroll(View child, View target, int axes) {
    // child == LinearLayout
    // target == ListView
    // axes == SCROLL_AXIS_VERTICAL
    return true;
    }

    View Slide

  73. ACTION_DOWN
    startNestedScroll(SCROLL_AXIS_VERTICAL);
    @Override
    void onNestedScrollAccepted(View child, View target, int axes) {
    // child == LinearLayout
    // target == ListView
    // axes == SCROLL_AXIS_VERTICAL
    }

    View Slide

  74. ACTION_MOVE

    View Slide

  75. ACTION_MOVE
    int[] mScrollConsumed = new int[2];
    int y = ev.getY(); // 1010
    int dy = mLastMotionY - y; // -10
    if (dispatchNestedPreScroll(0, dy, mScrollConsumed, ...)) {
    dy -= mScrollConsumed[1];
    }
    // INSERT Move children by remaining dy
    // TODO call dispatchNestedScroll()

    View Slide

  76. ACTION_MOVE
    @Override
    void onNestedPreScroll(View target, int dx, int dy,
    int[] consumed) {
    // dx = 0, dy = -10, consumed = int[2]
    int amountWeConsume = doSomethingWithPreScroll(dy);
    // consumed[0] = y, consumed[1] = x
    consumed[1] = amountWeConsume;
    }
    dispatchNestedPreScroll(0, dy, mScrollConsumed, ...)

    View Slide

  77. ACTION_MOVE
    int[] mScrollConsumed = new int[2];
    int y = ev.getY(); // 1010
    int dy = mLastMotionY - y; // -10
    if (dispatchNestedPreScroll(0, dy, mScrollConsumed, ...)) {
    dy -= mScrollConsumed[1];
    }
    // INSERT Move children by remaining dy
    // TODO call dispatchNestedScroll()
    // -3 remember
    // dy = -7

    View Slide

  78. ACTION_MOVE
    int[] mScrollConsumed = new int[2];
    int y = ev.getY(); // 1010
    int dy = mLastMotionY - y; // -10
    if (dispatchNestedPreScroll(0, dy, mScrollConsumed, ...)) {
    dy -= mScrollConsumed[1];
    }
    // INSERT Move children by remaining dy
    // TODO call dispatchNestedScroll()

    View Slide

  79. ACTION_MOVE
    // INSERT Move children by remaining dy
    // TODO call dispatchNestedScroll()

    View Slide

  80. ACTION_MOVE
    // INSERT Move children by remaining dy
    dispatchNestedScroll(0, dy, 0, unconsumedY, ...)
    @Override
    void onNestedScroll(View target,

    int dxConsumed, int dyConsumed,
    int dxUnconsumed, int dyUnconsumed) {
    // Do something if you wish
    }

    View Slide

  81. ACTION_UP
    // Calculate velocity over 1000ms
    mVelocityTracker.computeCurrentVelocity(1000);
    float velY = mVelocityTracker.getYVelocity();
    fling(velY);

    View Slide

  82. ACTION_UP
    float velY = ...;
    if (!dispatchNestedPreFling(0, velY)) {
    // Parent hasn't consumed fling, lets fling
    // and let the parent observe
    dispatchNestedFling(0, velY, ...);
    fling(velY);
    }
    stopNestedScroll();

    View Slide

  83. ACTION_UP
    float velY = ...;
    if (!dispatchNestedPreFling(0, velY)) {
    // Parent hasn't consumed fling, lets fling
    // and let the parent observe
    dispatchNestedFling(0, velY, ...);
    fling(velY);
    }
    stopNestedScroll();

    View Slide

  84. ACTION_UP
    dispatchNestedPreFling(0, velY);
    @Override
    boolean onNestedPreFling(View target, float velX, float velY) {
    // return true to consume the whole fling,
    // false to let the view handle it
    }

    View Slide

  85. ACTION_UP
    float velY = ...;
    if (!dispatchNestedPreFling(0, velY)) {
    // Parent hasn't consumed fling, lets fling
    // and let the parent observe
    dispatchNestedFling(0, velY, ...);
    fling(velY);
    }
    stopNestedScroll();

    View Slide

  86. ACTION_UP
    float velY = ...;
    if (!dispatchNestedPreFling(0, velY)) {
    // Parent hasn't consumed fling, lets fling
    // and let the parent observe
    dispatchNestedFling(0, velY, ...);
    fling(velY);
    }
    stopNestedScroll();

    View Slide

  87. The problem:
    Flinging does not transfer
    left over velocity

    View Slide

  88. The problem:
    Nested flinging does not allow
    interception like scrolling

    View Slide

  89. >
    SECRETS OF THE SUPPORT LIBRARY
    Indirect Nested Scrolling
    APPBARLAYOUT FLINGING
    Work in progress

    View Slide

  90. Nested scrolling

    View Slide

  91. Direct nested scrolling
    User directly touching screen

    View Slide

  92. User not directly touching screen
    Indirect nested scrolling

    View Slide

  93. Settling from fling
    Setting scroll position programmatically
    Scrolling from KeyEvents
    ?

    View Slide

  94. void setNestedIndirectScrollingEnabled(boolean enabled);
    boolean isNestedIndirectScrollingEnabled();
    boolean startNestedIndirectScroll(int axes);
    boolean dispatchNestedIndirectPreScroll(int dx, int dy,

    int[] consumed);
    boolean dispatchNestedIndirectScroll(int dxConsumed,

    int dyConsumed, int dxUnconsumed, int dyUnconsumed);
    boolean hasNestedIndirectScrollingParent();
    void stopNestedIndirectScroll();
    Sending

    View Slide

  95. boolean onStartNestedIndirectScroll(View child, View target,

    int axes);
    void onNestedIndirectScrollAccepted(View child, View target,

    int axes);
    void onNestedIndirectPreScroll(View target, int dx, int dy,

    int[] consumed);
    void onNestedIndirectScroll(View target, int dxConsumed,

    int dyConsumed, int dxUnconsumed, int dyUnconsumed);
    void onStopNestedIndirectScroll(View child);
    Receiving

    View Slide

  96. API is mostly the same as nested scrolling

    View Slide

  97. Fling
    API is mostly the same as nested scrolling
    Works with

    nested scrolling

    View Slide

  98. startNestedScroll(...)
    ACTION_DOWN

    View Slide

  99. dispatchNestedPreScroll(...)
    dispatchNestedScroll(...)
    ACTION_MOVE

    View Slide

  100. ACTION_MOVE

    View Slide

  101. stopNestedScroll()
    ACTION_UP
    fling()
    startNestedIndirectScroll(...)

    View Slide

  102. dispatchNestedIndirectPreScroll(...)
    dispatchNestedIndirectScroll(...)

    View Slide

  103. stopNestedIndirectScroll()

    View Slide

  104. Over and out…
    +ChrisBanes

    @chrisbanes

    View Slide