Skip to content

Commit

Permalink
Support ScrollView.scrollToEnd on Android natively
Browse files Browse the repository at this point in the history
Summary:
This is a followup for #12088 and implements the scrolling to end on Android natively rather than sending a large scroll offset from JS.

This turned out to be an OK amount of code, and some reduction in the amount of JavaScript. The only part I'm not particularly happy about is:

```
// ScrollView always has one child - the scrollable area
int bottom = scrollView.getChildAt(0).getHeight() + scrollView.getPaddingBottom();
```

According to multiple sources (e.g. [this SO answer](http://stackoverflow.com/questions/3609297/android-total-height-of-scrollview)) it is the way to get the total size of the scrollable area, similar to`scrollView.contentSize` on iOS but more ugly and relying on the fact the ScrollView always has a single child (hopefully this won't change in future versions of Android).

An alternative is:

```
View lastChild = scrollLayout.getChildAt(scrollLayout.getChildCount() - 1);
int bottom = lastChild.getBottom() + scrollLayout.getPadd
Closes #12101

Differential Revision: D4481523

Pulled By: mkonicek

fbshipit-source-id: 8c7967a0b9e06890c1e1ea70ad573c6eceb03daf
  • Loading branch information
Martin Konicek authored and facebook-github-bot committed Jan 30, 2017
1 parent 56595bf commit ad8cbb6
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 24 deletions.
6 changes: 0 additions & 6 deletions Libraries/Components/ScrollResponder.js
Original file line number Diff line number Diff line change
Expand Up @@ -415,12 +415,6 @@ var ScrollResponderMixin = {
scrollResponderScrollToEnd: function(
options?: { animated?: boolean },
) {
if (Platform.OS !== 'ios') {
console.warn(
'scrollResponderScrollToEnd is not supported on this platform'
);
return;
}
// Default to true
const animated = (options && options.animated) !== false;
UIManager.dispatchViewManagerCommand(
Expand Down
20 changes: 3 additions & 17 deletions Libraries/Components/ScrollView/ScrollView.js
Original file line number Diff line number Diff line change
Expand Up @@ -410,29 +410,15 @@ const ScrollView = React.createClass({
* Use `scrollToEnd({animated: true})` for smooth animated scrolling,
* `scrollToEnd({animated: false})` for immediate scrolling.
* If no options are passed, `animated` defaults to true.
*
* See `ScrollView#scrollToEnd`.
*/
scrollToEnd: function(
options?: { animated?: boolean },
) {
// Default to true
const animated = (options && options.animated) !== false;
if (Platform.OS === 'ios') {
this.getScrollResponder().scrollResponderScrollToEnd({
animated: animated,
});
} else if (Platform.OS === 'android') {
// On Android scrolling past the end of the ScrollView gets clipped
// - scrolls to the end.
if (this.props.horizontal) {
this.scrollTo({x: 10*1000*1000, animated: animated});
} else {
this.scrollTo({y: 10*1000*1000, animated: animated});
}
} else {
console.warn('scrollToEnd is not supported on this platform');
}
this.getScrollResponder().scrollResponderScrollToEnd({
animated: animated,
});
},

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ public void scrollTo(
scrollView.scrollTo(data.mDestX, data.mDestY, data.mAnimated);
}

@Override
public void scrollToEnd(
RecyclerViewBackedScrollView scrollView,
ReactScrollViewCommandHelper.ScrollToEndCommandData data) {
// Not implemented.
// RecyclerViewBackedScrollView is deprecated and will be removed.
// People should use a standard ScrollView or ListView instead.
}

@Override
public @Nullable
Map getExportedCustomDirectEventTypeConstants() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,20 @@ public void scrollTo(
}
}

@Override
public void scrollToEnd(
ReactHorizontalScrollView scrollView,
ReactScrollViewCommandHelper.ScrollToEndCommandData data) {
// ScrollView always has one child - the scrollable area
int right =
scrollView.getChildAt(0).getWidth() + scrollView.getPaddingRight();

This comment has been minimized.

Copy link
@MaxGraey

MaxGraey Jan 30, 2017

Contributor

What about if we have left padding as well?
I think this solution is more complete:

scrollView.getChildAt(0).getWidth() + scrollView.getPaddingLeft() + scrollView.getPaddingRight();

Or may be just simpler way:

int right = scrollView.getMaxScrollAmount();
if (data.mAnimated) {
scrollView.smoothScrollTo(right, scrollView.getScrollY());
} else {
scrollView.scrollTo(right, scrollView.getScrollY());
}
}

/**
* When set, fills the rest of the scrollview with a color to avoid setting a background and
* creating unnecessary overdraw.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@
public class ReactScrollViewCommandHelper {

public static final int COMMAND_SCROLL_TO = 1;
public static final int COMMAND_SCROLL_TO_END = 2;

public interface ScrollCommandHandler<T> {
void scrollTo(T scrollView, ScrollToCommandData data);
void scrollToEnd(T scrollView, ScrollToEndCommandData data);
}

public static class ScrollToCommandData {
Expand All @@ -42,10 +44,21 @@ public static class ScrollToCommandData {
}
}

public static class ScrollToEndCommandData {

public final boolean mAnimated;

ScrollToEndCommandData(boolean animated) {
mAnimated = animated;
}
}

public static Map<String,Integer> getCommandsMap() {
return MapBuilder.of(
"scrollTo",
COMMAND_SCROLL_TO);
COMMAND_SCROLL_TO,
"scrollToEnd",
COMMAND_SCROLL_TO_END);
}

public static <T> void receiveCommand(
Expand All @@ -64,6 +77,11 @@ public static <T> void receiveCommand(
viewManager.scrollTo(scrollView, new ScrollToCommandData(destX, destY, animated));
return;
}
case COMMAND_SCROLL_TO_END: {
boolean animated = args.getBoolean(0);
viewManager.scrollToEnd(scrollView, new ScrollToEndCommandData(animated));
return;
}
default:
throw new IllegalArgumentException(String.format(
"Unsupported command %d received by %s.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,20 @@ public void scrollTo(
}
}

@Override
public void scrollToEnd(
ReactScrollView scrollView,
ReactScrollViewCommandHelper.ScrollToEndCommandData data) {
// ScrollView always has one child - the scrollable area
int bottom =
scrollView.getChildAt(0).getHeight() + scrollView.getPaddingBottom();
if (data.mAnimated) {
scrollView.smoothScrollTo(scrollView.getScrollX(), bottom);
} else {
scrollView.scrollTo(scrollView.getScrollX(), bottom);
}
}

@Override
public @Nullable Map getExportedCustomDirectEventTypeConstants() {
return createExportedCustomDirectEventTypeConstants();
Expand Down

0 comments on commit ad8cbb6

Please sign in to comment.