44

Ok.. I must be overlooking something real simple here, but i think i'm trying to do something fairly basic.. Simply retain the scrollbar position of a ScrollView on orientation change...

Here is the code for my onSaveInstanceState and onRestoreInstanceState.. sView is the container for the ScrollView layout. Within my scrollview is a linearlayout with a lot of textviews.

    @Override 
public void onSaveInstanceState(Bundle outState) 
{
    //---save whatever you need to persist—

    outState.putInt("sViewX",sView.getScrollX());
    outState.putInt("sViewY",sView.getScrollY());

super.onSaveInstanceState(outState);

}

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) 
{
    super.onRestoreInstanceState(savedInstanceState);

    sViewX = savedInstanceState.getInt("sViewX");   
    sViewY = savedInstanceState.getInt("sViewY");

    sView.scrollTo(sViewX, sViewY);

}

If I set a Toast with the values of sViewX and sViewY on the Restore, the values are kept and correct.

Edit: I just tried to do a sView.scrollTo(0,150); in my onCreate.. just to see if that would open the activity at 150px down, and it didn't. I think my issue has to do with the .scrollTo method.

kefs
  • 3,606
  • 4
  • 21
  • 25
  • 1
    For reference, if you use fragments, onRestoreInstanceState has to go in onActivityCreated() method. Also, remember to check for: if (savedInstanceState != null) – Martin Marconcini Jul 16 '13 at 01:35

7 Answers7

129

I figured it out.

Since I'm using setText to TextViews in my onCreate, calling .scrollTo won't work.

So now I'm using the following:

sView.post(new Runnable() {
    @Override
    public void run() {
        sView.scrollTo(sViewX, sViewY);
    } 
});
Vasily Kabunov
  • 6,511
  • 13
  • 49
  • 53
kefs
  • 3,606
  • 4
  • 21
  • 25
  • When i have tried it with out thread it won't make any effect y so? – Kushal Shah Feb 22 '12 at 14:36
  • This actually makes sense, but when I try to apply this to a webview, it does not work. – TacB0sS Sep 26 '13 at 20:43
  • 11
    For anyone still wondering why this works, its because ScrollView can't scroll until its added to layout. post(Runnable) is called when ScrollView is added to view and hence you can scroll. A similar problem is when `View.getWidth()` returns `0` as discussed [here](http://stackoverflow.com/questions/18268915/views-getwidth-and-getheight-returning-0) – Sourabh Jun 21 '15 at 17:40
  • 1
    This didn't work for me either. The reason is that it depends when are you adding your items to the scroll view, since that determines its size. If you call scrollTo(x,y) with y > max height, scroll view will scroll to the end of the current ScrollView. if later on you add more items, you won't be at the end of the ScrollView anymore. I just call this after I finish adding Views to ScrollView – amitfr Aug 27 '15 at 10:17
2

You should NOT start scroll before components are drawn, since scroll does not work until components are not created:

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    scrollView.scrollTo(.... 
ivan.panasiuk
  • 1,239
  • 16
  • 20
1

onRestoreInstanceState() is just to early to scroll the view. That's why posting new Runnable helps, but not always. Sometimes one even have to use postDelayed() to let it work. For Fragment one can use onViewCreated() instead :

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    sViewX = savedInstanceState.getInt("sViewX");   
    sViewY = savedInstanceState.getInt("sViewY");
    sView.scrollTo(sViewX, sViewY);
}
akd005
  • 540
  • 4
  • 13
1

This is working for me

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putInt(SystemGlobal.SCROLL_Y, mRelativeLayoutMain.getTop());
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onRestoreInstanceState(savedInstanceState);
    mRelativeLayoutMain.scrollTo(0, savedInstanceState.getInt(SystemGlobal.SCROLL_Y));
}
1

this code worked for me for dynamically scrolling tabs, maybe it will be useful for u

TabHost tabHost;
int currentActiveTab;
HorizontalScrollView tabsHorizontalScrollView;
//some code ...
tabHost = getTabHost();
tabsHorizontalScrollView = findViewById(R.id.tabsHorizontalScrollView);
currentActiveTab = 8;
//some code ...
tabHost.setCurrentTab(currentActiveTab);
tabHost.getTabWidget().getChildAt(currentActiveTab).post(new Runnable() {
  @Override
  public void run() {
    tabsHorizontalScrollView.scrollTo(tabHost.getTabWidget().getChildAt(currentActiveTab).getLeft(), tabsHorizontalScrollView.getScrollY());
  }
});
0

For MVVMCross:

protected override void OnSaveInstanceState(Bundle outState)
{
    base.OnSaveInstanceState(outState);

    ScrollView sv = FindViewById<ScrollView>(Resource.Id.dispatchScrollView);
    int posY = sv.ScrollY;

    outState.PutInt("scrollY", posY);
}

protected override void OnRestoreInstanceState(Bundle savedInstanceState)
{
    base.OnRestoreInstanceState(savedInstanceState);

    ScrollView sv = FindViewById<ScrollView>(Resource.Id.dispatchScrollView);
    int posY = savedInstanceState.GetInt("scrollY");

    sv.Post(new Runnable(new Action(() => sv.ScrollTo(0, posY))));
}
0

Instead of sending scroll action to next run-loop, you can scroll your view in global layout callback:

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    sView.getViewTreeObserver().addOnGlobalLayoutListener(
        new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                sView.scrollTo(sViewX, sViewY);
            }
        }
    );
}
Kirill
  • 7,580
  • 6
  • 44
  • 95