简体   繁体   中英

Scrolling position of the CardView items in RecyclerView

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...

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.

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...

  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);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM