StaggeredGridLayoutManager and moving items
Asked Answered
S

8

28

I have created a very simple project, displaying 28 images with StaggeredGridLayoutManager by recyclerview. but as I scroll the recyclerview it moves items for example from left to right or swap the column of left and right.

codes:

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

import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;


public class MainActivity extends Activity {


    String mImageDir;
    private RecyclerView mRecyclerView;
    private StaggeredGridLayoutManager mLayoutManager;



    MyRecyclerAdapter myRecyclerAdapter;
    List<ImageModel> mImageList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);   
        mRecyclerView = (RecyclerView)findViewById(R.id.recyclerview_rootview);
        mLayoutManager = new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL);     
        mLayoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);        
        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.setHasFixedSize(false);
        mImageList = new ArrayList<ImageModel>();
        for (int i = 1; i < 29 ; i++) {
            ImageModel img = new ImageModel();
            img.setTitle("Image No " + i);
            int drawableResourceId = this.getResources().getIdentifier("image"+String.valueOf(i), "drawable", this.getPackageName());
            img.setResId(drawableResourceId);
            mImageList.add(img);

        }
        myRecyclerAdapter = new MyRecyclerAdapter(MainActivity.this,mImageList);        
        mRecyclerView.setAdapter(myRecyclerAdapter);

    }

} 

And the adapter:

public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.ViewHolder> {


    private List<ImageModel> mItems;
    Context mContext;

    public MyRecyclerAdapter(Context context,List<ImageModel> objects) {
        mContext = context;
        mItems = objects;

    }

    static class ViewHolder extends RecyclerView.ViewHolder{
        public  ImageView mImageView;
        public  TextView mTextView;
        public View rootView;
        public ViewHolder(View itemView) {
            super(itemView);
            rootView = itemView;
            mImageView =(ImageView)itemView.findViewById(R.id.image);
            mTextView =(TextView)itemView.findViewById(R.id.title);
        }
    }

    @Override
    public int getItemCount() {

        return mItems.size();
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {

        ImageModel item = mItems.get(position); 
        Picasso.with(mContext).load(item.getResId()).into(holder.mImageView);
        holder.mTextView.setText(item.getTitle());
    }


    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int arg1) {
        LayoutInflater inflater =    
                (LayoutInflater) mContext.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
        View convertView = inflater.inflate(R.layout.item, parent, false);
        return new ViewHolder(convertView);

    }

}

and a sample moving item:

http://i.imgur.com/FUapm2K.gif?1

if you play (scroll up and down) you can discover more interesting animation :-)

How to prevent that and having stable layout like an ordinary listview?

Edit

@Override
public void onBindViewHolder(ViewHolder holder, int position) {

    ImageModel item = mItems.get(position);
    RelativeLayout.LayoutParams rlp = (RelativeLayout.LayoutParams)holder.mImageView.getLayoutParams();
    float ratio = item.getHeight()/item.getWidth();
    rlp.height = (int)(rlp.width * ratio);
    holder.mImageView.setLayoutParams(rlp);
    Picasso.with(mContext).load(item.getResId()).into(holder.mImageView);
    holder.mTextView.setText(item.getTitle());
}
Swinge answered 24/12, 2014 at 12:32 Comment(1)
What did you end up doing? I have the same problem and answer by @yigit sounds good, but a code sample would immensely help. Where do you store this data and how to restore it? What unique ID do you use?Fellmonger
H
22

This is happening because SGLM does not keep any w/h information about the views. So each time a View is rebound, it gets the place holder size first and then the final size when the image is loaded.

Loading the actual image w/ different size (than place holder) triggers a new layout request, and during that layout request, SGLM detects that there will be gaps in the UI (or some item w/ higher position appears below an item w/ lower position) thus re-orders items w/ an animation.

You can avoid it by setting your place holder image to the dimensions of the actual image. If you don't have it ahead of time, you can save them after the image is loaded for the first-time and use it in the next onBind call.

Haplo answered 31/12, 2014 at 9:38 Comment(14)
I have tried using getViewType() but I still have the same issue maybe I am doing something wrong, do you know any sample code of SGLM?Swinge
This has nothing to do w/ getViewType(). You need to save image dimensions locally so that, next time you rebind the view, you can set the correct dimensions on the ImageView to avoid resizing.Haplo
Your using Picasso so why don't you use a place holder image. That way if you use one that's the Dane size as your downloaded images you shouldn't get jumpy animation.Bombshell
No, you are getting the dimensions from the image view, which does not have the final image dimensions anyways so that does not help. For each image, when it is loaded, you need to get the dimensions from the ImageView and save them so that next time you reload the image, you can set them on the ImageView.Haplo
For now I am just loading images from my drawable folder, so item.getHeight() and item.getWidth() have correct dimension of my images, now what should I do ? any sample code of SGLM ?Swinge
I suggest to put a "loading" item at the bottom, that will trigger loading the extra images, and only get their resolution, which is how you will get to know the dimensions you want to use for the views.Stephan
I tried it and still jumps around when scrolling up. I don't think it's that easy actually. It's not just the sizes of the thumbnails, but it has to remember the placement of the all cards above it and not just the ones that come into view, because the cards are unevenly aligned it has no idea what is coming before. Same thing happens when scrolling down but that feels naturally as the page expands, not when going up though. It's much more complex than it seems.Fellmonger
Truman, do you have a sample app that reproduces the issue? If your views don't resize and your adapter contents do not change, it should never jump.Haplo
I have to isolate it since the project is huge and proprietary. Here is the snippet in case you spot something I'm missing? pastie.org/10268417. I'm storing the sizes in onCreateViewHolder where they are available. Then restoring them in onBindViewHolder if found.Fellmonger
@Haplo you are right. I removed images from my staggered view so it can just render variable sized text views and no jumping. Still figuring the best way to handle remote images, placeholder resizing, bind time, etc.Fellmonger
BTW, just checked your adapter. Your layout listener won't record correct values because at the time view is laid out, you don't have real bitmap anyways. If the URL does not have the dimensions, you have to handle it in your image loading logic.Haplo
Here have a look at my answer with github project: linkEssex
Note that SGLM will always try to fill rows, so if you have variable-span items, when scrolling up after a reflow (the spans are flushed), it will always mess up in cases where there are gaps because spans are row-wrapped.Deadening
Thanks for the process explanation in such details.Epergne
P
20

What worked for me was to disable all animation on the recycle view when using StaggeredGridLayoutManager.

mRecyclerView.setItemAnimator(null);

You can create your own animator if you only want to restrict moving animations and keep the add and remove item animations.

Passus answered 2/3, 2016 at 13:14 Comment(0)
C
18

You can try this

    StaggeredGridLayoutManager manager = new StaggeredGridLayoutManager(2, OrientationHelper.VERTICAL);
    manager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
    mRecyclerView.setLayoutManager(manager);

after you set this, you'll find there is a blank at the top when you scroll to the top. continue to set this

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

It's work for me, I get this way somewhere. I hope it can help you!

Chrystal answered 24/10, 2016 at 8:5 Comment(0)
B
5

Change this line

mLayoutManager.setGapStrategy(
        StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS); 

to this line

mLayoutManager.setGapStrategy(
        StaggeredGridLayoutManager.GAP_HANDLING_NONE);           
Box answered 30/12, 2014 at 2:8 Comment(1)
No luck when I am downloading images from internet, still a lot of buggy animations occurs. Have you implemented anything with StaggeredGridLayoutManagerSwinge
J
5

Add the following line at the end of the method: holder.mImageView.requestLayout();

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    ...
    holder.mImageView.requestLayout();
}

This fixed the issue for me.

Jenn answered 27/4, 2016 at 11:39 Comment(1)
You save my time. thanks. please use itemLayout.requestLyout(), it will workMoneychanger
O
1

Maybe this is a more efficient way:

mBrandRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            if(newState == RecyclerView.SCROLL_STATE_IDLE){
                mBrandRecyclerView.invalidateItemDecorations();
            }
        }
    });

For more information: https://github.com/ibosong/CommonItemDecoration

Ozalid answered 1/4, 2017 at 7:6 Comment(0)
C
0

First set gap strategy like following code :

mLayoutManager = new StaggeredGridLayoutManager(SPAN_COUNT, StaggeredGridLayoutManager.VERTICAL);
                            mLayoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE);

and then add your item to mItems and then use:

mAdapter.notifyItemInserted(mItems.size() - 1);

this method is better than using:

mAdapter.notifyDataSetChanged();
Coif answered 17/8, 2018 at 6:43 Comment(1)
setGapStrategy to GAP_HANDLING_NONE make the item list look messy when scrolling it sometimes, I would not recommend it.Arlon
H
0

Set your recyclerview's height fixed and item height and width wrap-content for staggered-layout-manager

Hippo answered 14/11, 2018 at 10:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.