In my Android Layout, I have a TextView. This TextView is displaying a rather large spannable text and it is able to scroll. Now when the phone is rotated, the View is destroyed and created and I have to setText() the TextView again, resetting the scroll position to the beginning.
I know I can use getScrolly() and scrollTo() to scroll to pixel positions, but due to the change in View widths, lines become longer and a line that was at pixel pos 400 might now be at 250. So this is not very helpful.
I need a way to find the first visible line in a TextView in onDestroy() and then a way to make the TextView scroll to this specific piece of text after the rotation.
Any ideas?
This is an old question, but I landed here when searching for a solution to the same problem, so here is what I came up with. I combined ideas from answers to these three questions:
- Scroll TextView to text position
- Dynamically Modifying Contextual/Long-Press Menu in EditText Based on Position of Long Press
- ScrollView .scrollTo not working? Saving ScrollView position on rotation
I tried to extract only the relevant code from my app, so please forgive any errors. Also note that if you rotate to landscape and back, it may not end in the same position you started. For example, say "Peter" is the first visible word in portrait. When you rotate to landscape, "Peter" is the last word on its line, and the first is "Larry". When you rotate back, "Larry" will be visible.
private static float scrollSpot;
private ScrollView scrollView;
private TextView textView;
protected void onCreate(Bundle savedInstanceState) {
textView = new TextView(this);
textView.setText("Long text here...");
scrollView = new ScrollView(this);
scrollView.addView(textView);
// You may want to wrap this in an if statement that prevents it from
// running at certain times, such as the first time you launch the
// activity with a new intent.
scrollView.post(new Runnable() {
public void run() {
setScrollSpot(scrollSpot);
}
});
// more stuff here, including adding scrollView to your main layout
}
protected void onDestroy() {
scrollSpot = getScrollSpot();
}
/**
* @return an encoded float, where the integer portion is the offset of the
* first character of the first fully visible line, and the decimal
* portion is the percentage of a line that is visible above it.
*/
private float getScrollSpot() {
int y = scrollView.getScrollY();
Layout layout = textView.getLayout();
int topPadding = -layout.getTopPadding();
if (y <= topPadding) {
return (float) (topPadding - y) / textView.getLineHeight();
}
int line = layout.getLineForVertical(y - 1) + 1;
int offset = layout.getLineStart(line);
int above = layout.getLineTop(line) - y;
return offset + (float) above / textView.getLineHeight();
}
private void setScrollSpot(float spot) {
int offset = (int) spot;
int above = (int) ((spot - offset) * textView.getLineHeight());
Layout layout = textView.getLayout();
int line = layout.getLineForOffset(offset);
int y = (line == 0 ? -layout.getTopPadding() : layout.getLineTop(line))
- above;
scrollView.scrollTo(0, y);
}
TextView can save and restore its state for you. If you aren't able to use that, you can disable that and explicitly call the methods:
http://developer.android.com/reference/android/widget/TextView.SavedState.html
http://developer.android.com/reference/android/widget/TextView.html#onSaveInstanceState()
http://developer.android.com/reference/android/widget/TextView.html#onRestoreInstanceState(android.os.Parcelable)