Slide 1

Slide 1 text

Advanced Scrolling Techniques on Android

Slide 2

Slide 2 text

@cyrilmottier

Slide 3

Slide 3 text

capitainetrain.com • @capitainetrain captaintrain.com @captaintrain

Slide 4

Slide 4 text

capitainetrain.com • @capitainetrain captaintrain.com @captaintrain

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

Joe, Max, and Kevin are in a boat…

Slide 7

Slide 7 text

Joe, Max, and Kevin are designing an app…

Slide 8

Slide 8 text

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.

Slide 9

Slide 9 text

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)

Slide 10

Slide 10 text

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,

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

Scrolling is essential

Slide 14

Slide 14 text

Scrolling is essential Most important gesture on mobile

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

Fling /flɪŋ/ A type of scrolling that occurs when a user drags and lifts her finger quickly. It is also known as ‘inertia scrolling’.

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

iOS UIScrollView UITextView UITableView UICollectionView UIView

Slide 21

Slide 21 text

How to listen to scrolling events?

Slide 22

Slide 22 text

Listening to scrolls

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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!

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

aView.setOnScrollChangeListener(new OnScrollChangeListener() {
 @Override
 public void onScrollChange(View v, int scrollX, int scrollY, 
 int oldScrollX, int oldScrollY) { 
 Log.d(TAG, "(" + scrollX + ", " + scrollY + ")"); 
 }
 });

Slide 31

Slide 31 text

aView instanceof ScrollView

Slide 32

Slide 32 text

aView instanceof ScrollView scroll(0, 1) scroll(0, 12) scroll(0, 23) scroll(0, 46) scroll(0, 67) scroll(0, 69) scroll(0, 70) _

Slide 33

Slide 33 text

aView instanceof WebView

Slide 34

Slide 34 text

aView instanceof WebView scroll(0, 1) scroll(0, 9) scroll(0, 12) scroll(0, 22) scroll(0, 28) scroll(0, 34) scroll(0, 36) _

Slide 35

Slide 35 text

aView instanceof ListView

Slide 36

Slide 36 text

aView instanceof ListView scroll(0, 0) scroll(0, 0) scroll(0, 0) scroll(0, 0) scroll(0, -5) scroll(0, -1) scroll(0, 0) _

Slide 37

Slide 37 text

aView instanceof DrawerLayout

Slide 38

Slide 38 text

aView instanceof DrawerLayout _

Slide 39

Slide 39 text

aView instanceof MapView

Slide 40

Slide 40 text

aView instanceof MapView _

Slide 41

Slide 41 text

Scrolling on Android is a mess

Slide 42

Slide 42 text

Scrolling on Android is complicated Scrolling on Android

Slide 43

Slide 43 text

Scrolling is complicated

Slide 44

Slide 44 text

vs iOS Android

Slide 45

Slide 45 text

vs Consistency Customisation vs

Slide 46

Slide 46 text

5 behaviours

Slide 47

Slide 47 text

Initialisation 5 behaviours

Slide 48

Slide 48 text

Movement tracking Initialisation 5 behaviours

Slide 49

Slide 49 text

Inertia scrolling Movement Initialisation 5 behaviours

Slide 50

Slide 50 text

Movement Inertia scrolling Scrollbars drawing Initialisation 5 behaviours

Slide 51

Slide 51 text

Movement Inertia scrolling Scrollbars drawing Edges feedback Initialisation 5 behaviours

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

Initialisation @Override
 public boolean shouldDelayChildPressedState() {
 return true;
 } Prevents the pressed state from appearing when the user is actually trying to scroll the content.

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

Edges feedback

Slide 76

Slide 76 text

Edges feedback EdgeEffect

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

What about nesting scroll containers ?

Slide 87

Slide 87 text

Simple and elementary solution…

Slide 88

Slide 88 text

Simple and elementary solution… Don’t do it!

Slide 89

Slide 89 text

Simple and elementary solution… One axis, one scroll

Slide 90

Slide 90 text

Bezel scrolling DrawerLayout | ViewPager

Slide 91

Slide 91 text

Bezel scrolling DrawerLayout | ViewPager

Slide 92

Slide 92 text

Bezel scrolling DrawerLayout | ViewPager

Slide 93

Slide 93 text

Boundary scrolling ViewPager

Slide 94

Slide 94 text

Boundary scrolling ViewPager

Slide 95

Slide 95 text

View.canScrollVertically(int) canScrollHorizontally(int)

Slide 96

Slide 96 text

Nested scrolling

Slide 97

Slide 97 text

Nested scrolling

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

Thank you! @cyrilmottier cyrilmottier.com

Slide 100

Slide 100 text

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