Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android: Keep ScrollView content visible after edits #11000

Closed
wants to merge 8 commits into from

Conversation

rigdern
Copy link
Contributor

@rigdern rigdern commented Nov 17, 2016

Suppose that the user is scrolled to the bottom of a ScrollView. Next, the ScrollView's content is edited such that the height of the content changes and the current scroll position is larger than the new height of the content. Consequently, the user sees a blank ScrollView. As soon as the user interacts with the ScrollView, the ScrollView will jump to its max scroll position.

This change improves this scenario by ensuring that the user is never staring at a blank ScrollView when the ScrollView has content in it. It does this by moving the ScrollView to its max scroll position when the scroll position after an edit is larger than the max scroll position of the ScrollView.

Here are some pictures to illustrate how this PR improves the scenario described above:

image

image

Test plan (required)

Verified ScrollView keeps the content visible after an edit in a test app. Also, my team uses this change in our app.

Adam Comella
Microsoft Corp.

@facebook-github-bot
Copy link
Contributor

By analyzing the blame information on this pull request, we identified @astreet and @janicduplessis to be potential reviewers.

@facebook-github-bot facebook-github-bot added GH Review: review-needed CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. labels Nov 17, 2016
Copy link
Contributor

@astreet astreet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for fixing this! Just some small changes before landing.

Style nit: we don't prepend functions/private vars with this.

@@ -98,6 +101,8 @@ public ReactScrollView(ReactContext context, @Nullable FpsListener fpsListener)
} else {
mScroller = null;
}

this.setOnHierarchyChangeListener(this);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: don't prefix with this

int currentScrollY = getScrollY();
int thisHeight = this.getMeasuredHeight();

int maxScrollY = Math.max(0, contentHeight - thisHeight);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also need to factor in padding (see this from line 337):

int scrollRange = Math.max(
           0,
           getChildAt(0).getHeight() - (getHeight() - getPaddingBottom() - getPaddingTop()));

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(please extract this into a private helper)

this.onContentLayoutChange(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom);
}

private void onContentLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just inline this above? Or create a inner class to make things more obvious that onLayoutChange refers to the content view


}

private void setContentView(int expectedChildCount) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: seems like we can just put part of this logic in onChildViewAdded and the rest in onChildViewRemoved. There are already assertions in android code that a scrollview can only have 1 child so I'm not really worried about that.

@mkonicek
Copy link
Contributor

Thanks for fixing this! I'll let @astreet review.

@rigdern rigdern force-pushed the rigdern/androidAutoScroll branch from a2c2973 to 511188d Compare November 23, 2016 02:29
@rigdern
Copy link
Contributor Author

rigdern commented Nov 23, 2016

@astreet Thanks for the feedback. I pushed changes to address your comments and rebased my branch on master.

I'd like to point out one possible concern with the refactoring of the max scroll y logic into a helper. I noticed that one place that calls it used to use getHeight while the other place used getMeasuredHeight. Through reading the docs and testing, I think getHeight will work okay in the onLayoutChange method (it used to use getMeasuredHeight). However, I wanted to point this out in case you have any knowledge in this area.

@astreet
Copy link
Contributor

astreet commented Nov 23, 2016

I think getHeight is more correct iirc, since getMeasuredHeight just returns how big the view wants to be, not how big it actually draws.

getChildAt(0).getHeight() - (getHeight() - getPaddingBottom() - getPaddingTop()));
if (scrollY >= scrollRange) {
int scrollRange = getMaxScrollY();
if (scrollY >= getMaxScrollY()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you just update this to use scrollRange?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@astreet I'm not sure what you mean. Can you elaborate?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just meant you're calling getMaxScrollY twice when you already have the result :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@astreet Oops 😊 . I made this change, rebased, and pushed my branch.

}

/**
* Called when a mContentView's layout has changed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment that we use this to fix scroll position if it's too large when the content resizes?

Copy link
Contributor

@astreet astreet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fixes, just two small things before landing

@rigdern rigdern force-pushed the rigdern/androidAutoScroll branch from 4f5bb1e to ea5bd49 Compare November 28, 2016 20:57
import android.widget.OverScroller;
import android.widget.ScrollView;

import com.facebook.react.bridge.ReactContext;
import com.facebook.common.logging.FLog;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll need to add this as a dependency in the buck file... that's why the builds are failing. Or just remove this and the FLog statement below, I don't think they're that useful ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@astreet Thanks for diagnosing my build failure. I removed the logging.

I verified the problematic build step is now passing locally by running these commands (got them from the circleci log):

buck fetch ReactAndroid/src/main/java/com/facebook/react/shell/
buck build ReactAndroid/src/main/java/com/facebook/react/shell/

Is there an easy way to do a full local build with buck so I can be more confident my build will succeed on the CI server? I build with gradle locally so I didn't catch this error.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I recall correctly you can build UIExplorer with buck by running buck build uiexplorer

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(might need to buck fetch uiexplorer too)

@astreet
Copy link
Contributor

astreet commented Nov 29, 2016

(Also, I appreciate you sticking with this, sorry it's taking so long)

Adam Comella added 8 commits November 29, 2016 08:23
Suppose that the user is scrolled to the bottom of a ScrollView. Next, the ScrollView's content is edited such that the height of the content changes and the current scroll position is larger than the new height of the content. Consequently, the user sees a blank ScrollView. As soon as the user interacts with the ScrollView, the ScrollView will jump to its max scroll position.

This change improves this scenario by ensuring that the user is never staring at a blank ScrollView when the ScrollView has content in it. It does this by moving the ScrollView to its max scroll position when the scroll position after an edit is larger than the max scroll position of the ScrollView.
Android's ScrollView already asserts there's only 1 child which is why we can make this assumption.
Also, refactored this logic into a helper.
@astreet
Copy link
Contributor

astreet commented Nov 30, 2016

@facebook-github-bot shipit

Thanks!!

@facebook-github-bot
Copy link
Contributor

@astreet has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@rigdern rigdern deleted the rigdern/androidAutoScroll branch December 5, 2016 21:31
robclouth pushed a commit to robclouth/react-native that referenced this pull request Dec 7, 2016
Summary:
Suppose that the user is scrolled to the bottom of a ScrollView. Next, the ScrollView's content is edited such that the height of the content changes and the current scroll position is larger than the new height of the content. Consequently, the user sees a blank ScrollView. As soon as the user interacts with the ScrollView, the ScrollView will jump to its max scroll position.

This change improves this scenario by ensuring that the user is never staring at a blank ScrollView when the ScrollView has content in it. It does this by moving the ScrollView to its max scroll position when the scroll position after an edit is larger than the max scroll position of the ScrollView.

Here are some pictures to illustrate how this PR improves the scenario described above:

![image](https://cloud.githubusercontent.com/assets/199935/20408839/0e731774-accc-11e6-9f0a-3d77198645e9.png)

![image](https://cloud.githubusercontent.com/assets/199935/20408844/12877bb6-accc-11e6-8fe2-1c1bb26569cc.png)

**Test plan (require
Closes facebook#11000

Differential Revision: D4250792

Pulled By: astreet

fbshipit-source-id: 940fff6282ad29c796726f68b4519cbdabbfe554
DanielMSchmidt pushed a commit to DanielMSchmidt/react-native that referenced this pull request Jan 4, 2017
Summary:
Suppose that the user is scrolled to the bottom of a ScrollView. Next, the ScrollView's content is edited such that the height of the content changes and the current scroll position is larger than the new height of the content. Consequently, the user sees a blank ScrollView. As soon as the user interacts with the ScrollView, the ScrollView will jump to its max scroll position.

This change improves this scenario by ensuring that the user is never staring at a blank ScrollView when the ScrollView has content in it. It does this by moving the ScrollView to its max scroll position when the scroll position after an edit is larger than the max scroll position of the ScrollView.

Here are some pictures to illustrate how this PR improves the scenario described above:

![image](https://cloud.githubusercontent.com/assets/199935/20408839/0e731774-accc-11e6-9f0a-3d77198645e9.png)

![image](https://cloud.githubusercontent.com/assets/199935/20408844/12877bb6-accc-11e6-8fe2-1c1bb26569cc.png)

**Test plan (require
Closes facebook#11000

Differential Revision: D4250792

Pulled By: astreet

fbshipit-source-id: 940fff6282ad29c796726f68b4519cbdabbfe554
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants