Question

I have a ListView inside of a Fragment attached to a CursorAdapter. The Fragment has setRetainInstance(true). Under the Fragment's onCreate() method, I instantiate the CursorAdapter (storing it in variable adapter). I then call listView.setAdapter(adapter) under my Fragment's onCreateView method. The Cursor in the CursorAdapter is loaded by a CursorLoader. Inside my LoaderCallbacks' onLoadFinished(), I call adapter.swapCursor(cursor).

In sum: Everything seems to be in place such that the ListView should not scroll to top when changing between tabs and back. But it still does! Could I be missing something?

Here's some code.

Fragment

public class Home extends Fragment{
    ...
    private HomeFeedAdapter adapter; // HomeFeedAdapter extends CursorAdapter
    private AutoQuery autoQuery;     // AutoQuery extends LoaderCallbacks<Cursor>
    ...                              // (See inner class, at the end)

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);

        if(adapter == null)
            adapter = new HomeFeedAdapter(getActivity(), null);
        if(autoQuery == null)
            autoQuery = new AutoQuery();
        getLoaderManager().initLoader(LOADER_INITIAL, null, autoQuery);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        // Layout
        View v = inflater.inflate(R.layout.home, container, false); 
        l = (ListView) v.findViewById(R.id.listview);
        l.setAdapter(adapter);

        return v;
    }

    private class AutoQuery implements LoaderCallbacks<Cursor>{

        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {

            ...    
            return new CursorLoader(getActivity(), uri,
                    null, null, null, null);
        }

        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
            adapter.swapCursor(cursor);
        }

        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
            adapter.swapCursor(null);
        }

    }

}

Activity

public class MainActivity extends SherlockFragmentActivity {

    ...

    private class TabsListener implements ActionBar.TabListener {
        private Fragment fragment;
        private String tag;

        public TabsListener(Fragment fragment, String tag) {
            this.fragment = fragment;
            this.tag = tag; 
        }

        @Override
        public void onTabReselected(Tab tab, FragmentTransaction ft) {
            // Do nothing
        }

        @Override
        public void onTabSelected(Tab tab, FragmentTransaction ft) {
            ft.replace(R.id.fragment_container, fragment, tag);
        }

        @Override
        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
            ft.remove(fragment);
        }

    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {    
        super.onCreate(savedInstanceState);

        // Layout
        getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
        getSupportActionBar().setDisplayHomeAsUpEnabled(false);
        getSupportActionBar().setHomeButtonEnabled(false);
        getSupportActionBar().setDisplayShowTitleEnabled(false);
        setContentView(R.layout.activity_main);

        // Loads fragment
        Fragment fHome, fActivity, fProfile;
        if((fHome = getSupportFragmentManager().findFragmentByTag(HOME)) == null) fHome = new Home();
        if((fActivity = getSupportFragmentManager().findFragmentByTag(ACTIVITY)) == null) fActivity = new FriendsList();
        if((fProfile = getSupportFragmentManager().findFragmentByTag(PROFILE)) == null) fProfile = new Profile();       

        ActionBar.Tab tab;
        tab = getSupportActionBar().newTab();
        tab.setTabListener(new TabsListener(
            fHome,
            HOME
            ));
        getSupportActionBar().addTab(tab, false);

        tab = getSupportActionBar().newTab();
        tab.setTabListener(new TabsListener(
            fActivity,
            ACTIVITY
            ));
        getSupportActionBar().addTab(tab, false);

        tab = getSupportActionBar().newTab();
        tab.setTabListener(new TabsListener(
            fProfile,
            PROFILE
            ));
        getSupportActionBar().addTab(tab, false);

        ...

    }

}
Was it helpful?

Solution

With Greg Giacovelli's help in leading me in the right direction, I've found a solution to my problem.

I'll begin with a disclaimer that I don't quite understand how ListView positions are saved. My ListView instance is recreated every time that my Fragment's onCreateView() is called. This happens when the screen rotates, for example. And yet, in the specific case of screen rotations, even though onCreateView() is called and thus the ListView is reinstantiated, the ListView's state is nonetheless restored. So if the ListView is being recreated, something else must be telling it what its position previously was... and I don't know what that is. I think that it's attributable to the machinery of setRetainInstance(true).

But let's look at my main issue: Why did the ListView scroll to top between tab changes? As Greg suggested, it was because I was re-adding the Fragment with replace(), and thus destroying my Fragment and re-creating every time the user flipped to another tab and back.

My solution was to check if the tab was already added; if so, then not add it; else, add it. Then, when the user clicks out of a tab, I simply detach the fragment (not remove it), and attach a new one. This way the unselected tab's Fragment is still alive, though detached.

// Tabs listener
private class TabsListener implements ActionBar.TabListener {
    private Fragment fragment;
    private String tag;

    public TabsListener(Fragment fragment, String tag) {
        this.fragment = fragment;
        this.tag = tag; 
    }

    @Override
    public void onTabReselected(Tab tab, FragmentTransaction ft) {
        if(fragment instanceof ScrollableToTop) ((ScrollableToTop) fragment).scrollToTop();
    }

    @Override
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        if(!fragment.isAdded()){
            ft.add(R.id.fragment_container, fragment, tag);
        }
        ft.attach(fragment);
    }

    @Override
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        ft.detach(fragment);
    }

}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top