Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RecyclerView save position on device orientation change

In my App I have a RecyclerView connected with an adapter but everytime my device changes from landscape to portrait or something else, the view get reloaded and I'll get to the top again. I want to save my scroll position to the last item I saw.

Here is what I tried:

I have a Fragment that extends from another fragment the state should be save within the MainPageFragment. I tried to save the last position loaded and put it into the savedState, it gets correctly retrieved but If I call "scrollToPosition" nothing happens.

package com.pr0.pr0grammreloaded.fragment;

/**
 * Created by Dominik on 22.02.2015.
 */
import android.app.Activity;
import android.app.FragmentManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.pr0.pr0grammreloaded.MainActivity;
import com.pr0.pr0grammreloaded.R;
import com.pr0.pr0grammreloaded.config.FilterConfig;
import com.pr0.pr0grammreloaded.util.FixedRecyclerView;
import com.pr0.pr0grammreloaded.util.MainPageSaveParcelable;
import com.pr0.pr0grammreloaded.util.MyLayoutManager;
import com.pr0.pr0grammreloaded.util.RecyclerViewDelegate;

import java.util.ArrayList;

import uk.co.senab.actionbarpulltorefresh.extras.actionbarsherlock.PullToRefreshLayout;
import uk.co.senab.actionbarpulltorefresh.library.ActionBarPullToRefresh;
import uk.co.senab.actionbarpulltorefresh.library.listeners.OnRefreshListener;
import uk.co.senab.actionbarpulltorefresh.library.viewdelegates.AbsListViewDelegate;

/**
 * Created by Dominik on 17.01.2015.
 */
public class MainPageFragment extends Pr0MainPageFragment {


    /**
     * The fragment argument representing the section number for this
     * fragment.
     */
    private static final String ARG_SECTION_NUMBER = "section_number";

    /**
     * Returns a new instance of this fragment for the given section
     * number.
     */
    public static MainPageFragment newInstance(FilterConfig config) {
        MainPageFragment fragment = new MainPageFragment();
        fragment.config = config;
        return fragment;
    }

    public MainPageFragment() {

    }

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

        if(savedInstanceState != null){
            MainPageSaveParcelable saveParcelable = savedInstanceState.getParcelable("save");
            View rootView = inflater.inflate(R.layout.mainpage_fragment, container, false);

            recyclerView = (RecyclerView) rootView.findViewById(R.id.list);
            adapter = new ItemAdapter();
            itemList = saveParcelable.itemList;
            config = saveParcelable.config;
            // use better layout manager, maybe write our own?
            recyclerView.setLayoutManager(new GridLayoutManager(MainActivity.getActivity(), 3));
            recyclerView.setAdapter(adapter);
            restorePositon = saveParcelable.position;

            mPullToRefreshLayout = (PullToRefreshLayout) rootView.findViewById(R.id.ptr_layout);

            // Now setup the PullToRefreshLayout
            ActionBarPullToRefresh.from(getActivity())
                    // Mark All Children as pullable
                    .allChildrenArePullable()
                            // Set a OnRefreshListener
                    .listener(new OnRefreshListener() {
                        @Override
                        public void onRefreshStarted(View view) {
                            if (!isBlocked()) {
                                FragmentManager fragmentManager = getFragmentManager();
                                fragmentManager.beginTransaction()
                                        .replace(R.id.container, MainPageFragment.newInstance(MainActivity.filterConfig))
                                        .commit();
                            }
                        }


                    })
                            // Finally commit the setup to our PullToRefreshLayout
                    .useViewDelegate(FixedRecyclerView.class, new RecyclerViewDelegate())
                    .setup(mPullToRefreshLayout);

            loadFeed();
//            recyclerView.scrollToPosition(saveParcelable.);
            return rootView;
        }else {
            View rootView = inflater.inflate(R.layout.mainpage_fragment, container, false);

            recyclerView = (RecyclerView) rootView.findViewById(R.id.list);

            adapter = new ItemAdapter();
            itemList = new ArrayList<>();
            // use better layout manager, maybe write our own?
            recyclerView.setLayoutManager(new GridLayoutManager(MainActivity.getActivity(), 3));
            recyclerView.setAdapter(adapter);

            mPullToRefreshLayout = (PullToRefreshLayout) rootView.findViewById(R.id.ptr_layout);

            // Now setup the PullToRefreshLayout
            ActionBarPullToRefresh.from(getActivity())
                    // Mark All Children as pullable
                    .allChildrenArePullable()
                            // Set a OnRefreshListener
                    .listener(new OnRefreshListener() {
                        @Override
                        public void onRefreshStarted(View view) {
                            if (!isBlocked()) {
                                FragmentManager fragmentManager = getFragmentManager();
                                fragmentManager.beginTransaction()
                                        .replace(R.id.container, MainPageFragment.newInstance(MainActivity.filterConfig))
                                        .commit();
                            }
                        }


                    })
                            // Finally commit the setup to our PullToRefreshLayout
                    .useViewDelegate(FixedRecyclerView.class, new RecyclerViewDelegate())
                    .setup(mPullToRefreshLayout);

            loadFeed();
            return rootView;
        }
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
    }

    @Override
    public void onSaveInstanceState(Bundle state){
        super.onSaveInstanceState(state);
        restorePositon = lastPositon;
        MainPageSaveParcelable saveParcelable = new MainPageSaveParcelable(itemList,firstChunk,config,restorePositon);
        state.putParcelable("save",saveParcelable);
    }


}

Pr0MainPageFragment

package com.pr0.pr0grammreloaded.fragment;

import android.app.Fragment;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.pr0.pr0grammreloaded.MainActivity;
import com.pr0.pr0grammreloaded.R;
import com.pr0.pr0grammreloaded.api.Api;
import com.pr0.pr0grammreloaded.api.Feed;
import com.pr0.pr0grammreloaded.api.InstantDeserializer;
import com.pr0.pr0grammreloaded.config.FilterConfig;
import com.squareup.picasso.Picasso;

import org.joda.time.Instant;

import java.util.ArrayList;
import java.util.List;

import retrofit.RestAdapter;
import retrofit.converter.GsonConverter;
import rx.functions.Action1;
import uk.co.senab.actionbarpulltorefresh.extras.actionbarsherlock.PullToRefreshLayout;

import static rx.android.observables.AndroidObservable.bindActivity;

/**
 * Created by Dominik on 22.02.2015.
 */
public class Pr0MainPageFragment extends Fragment {

    protected ItemAdapter adapter;
    protected boolean firstChunk = true;
    protected List<Feed.Item> itemList;
    protected FilterConfig config;
    private boolean blockLoading = false;
    protected PullToRefreshLayout mPullToRefreshLayout;
    protected int restorePositon = 0;
    protected int lastPositon = 0;
    protected RecyclerView recyclerView;
    protected boolean isRestore = false;

    /**
     * Loads the feed from pr0gramm. This should be put into some kind of service
     * that is injected into our activities.
     */
    protected void loadFeed() {
        if(!blockLoading) {
            blockLoading = true;
            Log.e("Pr0","Blocked loading!");
            Gson gson = new GsonBuilder()
                    .registerTypeAdapter(Instant.class, new InstantDeserializer())
                    .create();

            Api api = new RestAdapter.Builder()
                    .setEndpoint("http://pr0gramm.com")
                    .setConverter(new GsonConverter(gson))
                    .setLogLevel(RestAdapter.LogLevel.BASIC)
                    .build()
                    .create(Api.class);

            // perform api request in the background and call
            // back to the main thread on finish

            if (firstChunk) {
                bindActivity(MainActivity.getActivity(), api.itemsGet(config.getFlag(), 1)).subscribe(new Action1<Feed>() {
                    @Override
                    public void call(Feed feed) {
                        // we are now back in the main thread
                        firstChunk = false;
                        handleFeedResponse(feed);
                    }
                });
            } else {
                //Log.e("Pr0", "Loading after ID : " + itemList.get(0).getId());
                for(int x = 0; x < itemList.size();x ++){
                    Log.e("Pr0", "POS: " + x + ", ID : " + itemList.get(x).getId());
                }
                bindActivity(MainActivity.getActivity(), api.olderGet(itemList.get(itemList.size() - 1).getPromoted(), config.getFlag(), 1)).subscribe(new Action1<Feed>() {
                    @Override
                    public void call(Feed feed) {
                        // we are now back in the main thread
                        handleFeedResponse(feed);
                    }
                });
            }
        }
    }

    /**
     * Display the elements from the feed
     *
     * @param feed The feed to display
     */
    private void handleFeedResponse(Feed feed) {
        // display feed now.
        //Log.i("MainActivity", "Number of items: " + feed.getItems().size());
        adapter.addItems(feed.getItems());
        mPullToRefreshLayout.setRefreshComplete();
        restorePostion();
    }


    protected class ItemAdapter extends RecyclerView.Adapter<ItemView> {


        ItemAdapter() {
            setHasStableIds(true);
        }

        @Override
        public ItemView onCreateViewHolder(ViewGroup viewGroup, int i) {
            LayoutInflater inflater = LayoutInflater.from(MainActivity.getActivity());
            View view = inflater.inflate(R.layout.item_view, viewGroup, false);
            return new ItemView(view);
        }

        @Override
        public void onBindViewHolder(ItemView itemView, int i) {
           // Log.e("Pr0","load id : " + i);
            //Log.e("Pr0",String.valueOf(itemList.get(i).getId()));
            String url = "http://thumb.pr0gramm.com/" + itemList.get(i).getThumb();
            Picasso.with(getActivity())
                    .setIndicatorsEnabled(true);
            Picasso.with(getActivity())
                    .load(url)
                    .into(itemView.image);
            lastPositon = i;
            Log.w("Pr0","last positon : " + i);
            if(i > itemList.size() - 5){
                //Log.e("Pr0","SIZE : " + itemList.size());
                //Log.e("Pr0","End Reached Load After ID : " + itemList.get(0).getId());
                loadFeed();
            }
        }

        @Override
        public int getItemCount() {
            return itemList.size();
        }

        public void addItems(List<Feed.Item> itemsToAdd) {

            int oldCount = itemList.size();
            itemList.addAll(itemsToAdd);
            notifyItemRangeInserted(oldCount, itemList.size());
           /*
            for(Feed.Item item : itemsToAdd) {
                //Log.e("Pr0","Added image ID : " + item.getId());
                int oldCount = itemList.size();
                itemList.add(item);
                notifyItemRangeInserted(oldCount, itemList.size());
            }
            */
            blockLoading = false;
            Log.e("Pr0","Unblocked Loading");
            //
        }

        @Override
        public long getItemId(int position) {
            return itemList.get(position).getId();
        }
    }

    /**
     * View holder for a view in the list of items
     */
    private class ItemView extends RecyclerView.ViewHolder {
        final ImageView image;

        public ItemView(View itemView) {
            super(itemView);
            image = (ImageView) itemView.findViewById(R.id.image);
        }
    }

    public boolean isBlocked(){
        return this.blockLoading;
    }

    public void reset(){
        itemList.clear();
        firstChunk = true;
    }

    public void restorePostion(){
        Log.w("Pr0", "Restore : " + restorePositon);
        recyclerView.scrollToPosition(restorePositon);
    }
}

And here is my Parcelable

package com.pr0.pr0grammreloaded.util;

import android.os.Parcel;
import android.os.Parcelable;

import com.pr0.pr0grammreloaded.account.User;
import com.pr0.pr0grammreloaded.api.Feed;
import com.pr0.pr0grammreloaded.config.FilterConfig;

import java.util.List;

/**
 * Created by Dominik on 27.02.2015.
 */
public class MainPageSaveParcelable implements Parcelable{

    public boolean firstChunk;
    public List<Feed.Item> itemList;
    public FilterConfig config;
    public int position;

    public MainPageSaveParcelable(List<Feed.Item> itemList, boolean firstChunk, FilterConfig config, int position){
        this.firstChunk = firstChunk;
        this.itemList = itemList;
        this.config = config;
        this.position = position;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeList(itemList);
        dest.writeValue(config);
        dest.writeValue(firstChunk);
        dest.writeInt(position);
    }

    /** Static field used to regenerate object, individually or as arrays */
    public static final Parcelable.Creator<MainPageSaveParcelable> CREATOR = new Parcelable.Creator<MainPageSaveParcelable>() {
        public MainPageSaveParcelable createFromParcel(Parcel pc) {
            return new MainPageSaveParcelable(pc);
        }
        public MainPageSaveParcelable[] newArray(int size) {
            return new MainPageSaveParcelable[size];
        }
    };

/**Ctor from Parcel, reads back fields IN THE ORDER they were written */
    public MainPageSaveParcelable(Parcel pc){
        pc.readList(itemList,List.class.getClassLoader());
        config        =  (FilterConfig) pc.readValue(FilterConfig.class.getClassLoader());
        firstChunk      = (boolean) pc.readValue(Boolean.class.getClassLoader());
        position = pc.readInt();
    }
}
like image 645
Dominik Louven Avatar asked Feb 27 '15 23:02

Dominik Louven


1 Answers

you can use scroll listener and save first items or RecyclerView state on scrolling in SharedPreference or whatever you prefer

define these variables as private variables in your activity or fragment

    int firstCompleteVisibleItemPosition;
            int firstVisibleItemPosition;
            Parcelable recyclerViewState;

use recyclerview on scroll listener

  recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);


                firstCompleteVisibleItemPosition = recyclerView.getLayoutManager().findFirstCompletelyVisibleItemPosition();
                firstVisibleItemPosition = recyclerView.getLayoutManager().findFirstVisibleItemPosition();
                recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState();


        }
    });

and if orientation changes you can start your recyclerview by last position u saved by this method

linearLayoutManager.scrollToPositionWithOffset(firstVisibleItemPosition, 0);

or by last state of recyclerview

rv.getLayoutManager().onRestoreInstanceState(recyclerViewState);
like image 188
Amin Avatar answered Oct 16 '22 04:10

Amin