1

After almost a day of struggling with this method to save and retrieve the position of the last viewed item, I finally found a solution that gives me the desired output. After opening one of my items on the way back to the RecyclerView my app scrolls to that item but with a small bug... I am using 2 methods onPause() and onResume() and this is my implementation:

@Override
protected void onPause() {
    super.onPause();
    lastFirstVisiblePosition = ((LinearLayoutManager)
            mRecyclerView.getLayoutManager()).findLastVisibleItemPosition();
    Log.d("position", String.valueOf(lastFirstVisiblePosition));
}

@Override
protected void onResume() {
    super.onResume();

    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            mRecyclerView.scrollToPosition(lastFirstVisiblePosition);
        }
    }, 200);

}
  1. Since I am a beginner I am not sure if using the Handler in this purpose is a smart idea?
  2. Because my cards/items are not the same sizes I am not able to return to the position of the last viewed item as long as the item is not in a certain position meaning as long as the item below is not on the screen... Is there any way to consider the dimension of my card and according to that set the position?

one more thing to add I had tried onSaveInstanceState() and onRestoreInstanceState() methods but unsuccessfully... One of the implementations:

@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);

    lastFirstVisiblePosition = ((LinearLayoutManager)
            mRecyclerView.getLayoutManager()).findLastVisibleItemPosition();
    outState.putString("position", String.valueOf(lastFirstVisiblePosition));
    Log.d("currentPos", String.valueOf(outState));

}

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

    String position = savedInstanceState.getString("position");
    mRecyclerView.scrollToPosition(Integer.parseInt(position));
}

in this way, I was able to get the current position but not to restore it...

Zain
  • 37,492
  • 7
  • 60
  • 84
Marko Malbasic
  • 149
  • 1
  • 10

3 Answers3

0

Create a variable private static int displayedposition = 0;

Now for the position of your RecyclerView in your Activity.

myRecyclerView.setOnScrollListener(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);

            LinearLayoutManager llm = (LinearLayoutManager) myRecyclerView.getLayoutManager();
            displayedposition = llm.findLastVisibleItemPosition();

        }
    });

Modify your onPause() method to add these lines..

LinearLayoutManager llm = (LinearLayoutManager) mRecyclerView.getLayoutManager();
llm.scrollToPositionWithOffset(displayedposition , youList.size());

Try findLastCompletelyVisibleItemPosition() if findLastVisibleItemPosition() doesn't seem to work..

It will work.. Try and Revert Back.

amit
  • 709
  • 6
  • 17
  • This youList.size() refers to what? – Marko Malbasic Mar 24 '20 at 14:59
  • the size of your RecyclerView – amit Mar 24 '20 at 15:06
  • refer this link for more details https://stackoverflow.com/questions/36568168/how-to-save-scroll-position-of-recyclerview-in-android – amit Mar 24 '20 at 15:15
  • your recyclerview is your list. – amit Mar 24 '20 at 15:58
  • Yes, but I am a bit confused now... Do I need to create that list and to initialize it? – Marko Malbasic Mar 24 '20 at 16:01
  • to make things simpler i suggest you to please refer to the question link i mentioned earlier, the answer which has 23 upvotes suggests to use `onRestoreInstanceState`... it states The LayoutManager supports `onRestoreInstanceState` out of the box so there is no need to save scroll positions etc. The built in method already saves pixel perfect positions. – amit Mar 24 '20 at 16:02
  • you have mentioned in your question that you have unsuccesfully used `onRestoreInstanceState()`.. could you edit your question and show your code what you tried?? – amit Mar 24 '20 at 16:05
  • I have created RecyclerView inside mainActivity and I have initialized it. Then I have created a ViewHolder class that extends RecyclerView for my items as well as xml file for CardView... So now I am wondering how should I get that list? – Marko Malbasic Mar 24 '20 at 16:08
  • I have edited my post you can check what I had tried earlier... Thanks! – Marko Malbasic Mar 24 '20 at 16:25
  • see my answer below... its similar to what you have coded, i guess... – amit Mar 24 '20 at 16:48
0

I am herewith showing my code which might help you.. My App link on Play Store to refer to..

MainActivity.java

package com.amitabh.dhamma_jaagran;

import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CollapsingToolbarLayout;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import com.bumptech.glide.Glide;


public class MainActivity extends AppCompatActivity  {

private RecyclerView recyclerView;
private AlbumsAdapter adapter;

private AlbumsAdapter.AlbumsAdapterListener listener;
private List<Album> albumList;
private long backPressedTime = 0;    // used by onBackPressed()  
private static TextView footerText;

String currentVersion;
View parentLayout;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Toolbar toolbar = findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    initCollapsingToolbar();
    footerText = findViewById(R.id.activity_main_footer);
    parentLayout = findViewById(android.R.id.content);

    recyclerView = findViewById(R.id.recycler_view);

    //  recyclerView.setHasFixedSize(true);

    albumList = new ArrayList<>();
    adapter = new AlbumsAdapter(this, albumList, listener);

    RecyclerView.LayoutManager mLayoutManager = new GridLayoutManager(this, 2);

    ((GridLayoutManager) mLayoutManager).setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
        @Override
        public int getSpanSize(int position) {
            return (position % 15 == 0 ? 2 : 1);
        }
    });



    recyclerView.setLayoutManager(mLayoutManager);
    recyclerView.setBackgroundColor(getResources().getColor(R.color.white));
 //   recyclerView.setBackgroundResource(R.drawable.white_background);
      recyclerView.setItemAnimator(new DefaultItemAnimator());
    recyclerView.setAdapter(adapter);

    // row click listener
    recyclerView.addOnItemTouchListener(new RecyclerTouchListener(getApplicationContext(), recyclerView, new RecyclerTouchListener.ClickListener() {

        @Override
        public void onClick(View view, int position) {
            Album album = adapter.getAlbumList().get(position);

            String text = album.getName();

          //  Toast.makeText(getApplicationContext(), text + " is selected", Toast.LENGTH_SHORT).show();
            if (text == "तेरापंथ प्रबोध") {
                view.getContext().startActivity(new Intent(view.getContext(), TerapanthPrabodhTabLayoutViewPager.class));
            }

            if (text == "संवत्सरी प्रतिक्रमण") {
                view.getContext().startActivity(new Intent(view.getContext(), MangalStuti.class));
            }

        }

        @Override
        public void onLongClick(View view, int position) {

        }
    }));

    prepareAlbums();

    try {
        Glide.with(this).load(R.drawable.cover2).into((ImageView) findViewById(R.id.backdrop));
    } catch (Exception e) {
        e.printStackTrace();
    }           
}

/**
 * Initializing collapsing toolbar
 * Will show and hide the toolbar title on scroll
 */
private void initCollapsingToolbar() {
    final CollapsingToolbarLayout collapsingToolbar =
            findViewById(R.id.collapsing_toolbar);
    collapsingToolbar.setTitle(" ");
    AppBarLayout appBarLayout = findViewById(R.id.appbar);
    appBarLayout.setExpanded(true);

    // hiding & showing the title when toolbar expanded & collapsed
    appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
        boolean isShow = false;
        int scrollRange = -1;

        @Override
        public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
            if (scrollRange == -1) {
                scrollRange = appBarLayout.getTotalScrollRange();
            }
            if (scrollRange + verticalOffset == 0) {
                collapsingToolbar.setTitle(getString(R.string.app_name));
                isShow = true;
            } else if (isShow) {
                collapsingToolbar.setTitle(" ");
                isShow = false;
            }
        }
    });
}

/**
 * Adding few albums for testing
 */
private void prepareAlbums() {
    int[] covers = new int[]{

            R.drawable.album000,
            R.drawable.album01,

    };

    Album a = new Album("तेरापंथ प्रबोध", "Terapanth Prabodh", covers[0]);
    albumList.add(a);

     a = new Album("मंगल स्तुति", "Mangal Stuti", covers[1]);
    albumList.add(a);

    adapter.notifyDataSetChanged();
}
}

Album.java

package com.amitabh.dhamma_jaagran;

public class Album
{
private String name;
private String numOfSongs;
private int thumbnail;

public Album() {}

public Album(String name, String numOfSongs, int thumbnail)
{
this.name = name;
this.numOfSongs = numOfSongs;
this.thumbnail = thumbnail;
}

public String getName()
{
 return this.name;
}

public String getNumOfSongs()
{
return this.numOfSongs;
}

public int getThumbnail()
{
 return this.thumbnail;
}

public void setName(String paramString)
{
 this.name = name;
}

public void setNumOfSongs(String paramString)
{
 this.numOfSongs = numOfSongs;
}

public void setThumbnail(int paramInt)
{
 this.thumbnail = thumbnail;
}
}

AlbumsAdapter.java

package com.amitabh.dhamma_jaagran;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import java.util.ArrayList;
import java.util.List;



public class AlbumsAdapter extends RecyclerView.Adapter<AlbumsAdapter.MyViewHolder> {

private Context mContext;
private List<Album> albumList;
private AlbumsAdapterListener listener;
private int lastPosition = -1;

public class MyViewHolder extends RecyclerView.ViewHolder {
    public TextView title, count;
    public ImageView thumbnail, overflow;
    public CardView cardView;

    public MyViewHolder(View view) {
        super(view);
        title =  view.findViewById(R.id.title);
        count = view.findViewById(R.id.count);
        thumbnail =  view.findViewById(R.id.thumbnail);
        overflow =  view.findViewById(R.id.overflow);
        cardView =  view.findViewById(R.id.card_view);
    }
}


public AlbumsAdapter(Context mContext, List<Album> albumList, AlbumsAdapterListener 
listener) {
    this.mContext = mContext;
    this.albumList = albumList;
    this.listener = listener;
}

@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View itemView = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.album_card, parent, false);

    return new MyViewHolder(itemView);
}

@Override
public void onViewDetachedFromWindow(@NonNull AlbumsAdapter.MyViewHolder holder) {
    super.onViewDetachedFromWindow(holder);
    holder.itemView.clearAnimation();
}

@Override
public void onBindViewHolder(final MyViewHolder holder, final int position) {
    Album album = albumList.get(position);
    holder.title.setText(album.getName());
    holder.count.setText(album.getNumOfSongs());

    /*loading album cover using Glide library*/
    Glide.with(mContext).load(album.getThumbnail()).into(holder.thumbnail);

    Animation animation = AnimationUtils.loadAnimation(mContext,
            (position > lastPosition) ? R.anim.up_from_bottom
                    : R.anim.down_from_top);
    holder.itemView.startAnimation(animation);
    lastPosition = position;

}


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


public interface AlbumsAdapterListener {
    void onAddToFavoriteSelected(int position);

    void onPlayNextSelected(int position);

    void onCardSelected(int position, ImageView thumbnail);
}
public List<Album> getAlbumList(){
    return albumList;
}

public void filter(ArrayList<Album> newList)
{
    albumList=new ArrayList<>();
    albumList.addAll(newList);
    notifyDataSetChanged();
}

}

To keep things simple i have not used any instances to be saved or scollToPositon.. but it scrolls to the position where it was previously when a user returns to the MainActivity..

This answer is similar to what you have tried i guess..

I have created RecyclerView inside mainActivity and I have initialized it. Then I have created a ViewHolder class that extends RecyclerView for my items as well as xml file for CardView...

amit
  • 709
  • 6
  • 17
  • Thank you so much, my friend, for this, I really appreciate it. I guess it will help me! – Marko Malbasic Mar 24 '20 at 17:15
  • Please consider _explaining_ how your code works instead of spoonfeeding code. – Edric Mar 25 '20 at 09:39
  • considering that the questioner is fairly new, i have answered in the most simplistic way and with regards to explantion, i have tried to follow the same pattern as the questioner.. i.e "To create a RecyclerView inside mainActivity and initialize it. Then create a, Adapter and ViewHolder class that extends RecyclerView for the items.." – amit Mar 25 '20 at 16:11
0
  1. Since I am a beginner I am not sure if using the Handler in this purpose is a smart idea?

The handler is not the smartest way indeed because the 200 msec may not sufficient to load the entire data of the RecyclerView, but the reason that you need to use a handler is that you don't know when the RecyclerView is loaded with the data in order to scroll to a certain position just after you know that.

There is a bit better way for your handler, but it function the same as yours as it still suffers the problem of the 200 msec.

mRecyclerView.postDelayed(new Runnable() {
    @Override
    public void run() {
        mRecyclerView.scrollToPosition(lastFirstVisiblePosition);
        mRecyclerView.removeCallbacks(this);
    }
}, 200);

The reason that it's better because the handler is coupled to your RecyclerView

But the solution that I recommend to use instead of using a Hanlder is to add a listener to your LinearLayoutManager that is triggered when the list of the RecyclerView is populated by the adapter, typically when the mAdapter.notifyDataSetChanged() is over. And to do that:

First

Create a custom LinearLayoutManager and add a listener interface that is triggered whenever the onLayoutCompleted() is invoked.

public class LayoutCompletionLinearLayoutManager extends LinearLayoutManager {

    public void setCallback(OnLayoutCompleteCallback callback) {
        mCallback = callback;
    }

    private OnLayoutCompleteCallback mCallback = null;

    public LayoutCompletionLinearLayoutManager(Context context) {
        super(context);
    }

    public LayoutCompletionLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    public LayoutCompletionLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public void onLayoutCompleted(RecyclerView.State state) {
        super.onLayoutCompleted(state);
        if (mCallback != null)
            mCallback.onLayoutComplete();
    }

    public interface OnLayoutCompleteCallback {
        void onLayoutComplete();
    }
}

Second

Use the callback when you build your RecyclerView LayoutManager.

final LayoutCompletionLinearLayoutManager layoutMgr = 
    new LayoutCompletionLinearLayoutManager(getApplicationContext(), 
        LinearLayoutManager.HORIZONTAL, false); // change orientation according to your RecyclerView

layoutMgr.setCallback(new LayoutCompletionLinearLayoutManager.OnLayoutCompleteCallback() {
    @Override
    public void onLayoutComplete() {
        mRecyclerView.scrollToPosition(lastFirstVisiblePosition);
        layoutMgr.setCallback(null);
    }
});

mRecyclerView.setLayoutManager(layoutMgr);
  1. Because my cards/items are not the same sizes I am not able to return to the position of the last viewed item as long as the item is not in a certain position meaning as long as the item below is not on the screen... Is there any way to consider the dimension of my card and according to that set the position?

You need to use the LinearLayoutManager scrollToPositionWithOffset() with some offset according to the dimension of your CardView instead of scrollToPosition()

((LayoutCompletionLinearLayoutManager) mRecyclerView.getLayoutManager())
    .scrollToPositionWithOffset(lastFirstVisiblePosition, offset);
Zain
  • 37,492
  • 7
  • 60
  • 84
  • thanks for this, especially for the first part coz I can better understand the handler. But regarding my second question... I can not initiate scrollToPositionWithOffset() to RecyclerView, just to LayoutManager – Marko Malbasic Mar 24 '20 at 17:29
  • You are an amazing man! Let me try to implement all that you have shown me and I will let you know does it work for me – Marko Malbasic Mar 24 '20 at 17:43
  • unfortunately, it didn't work for me. Anyway, big respect for your help! – Marko Malbasic Mar 25 '20 at 07:20