Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexplainable forced scroll down in RecyclerView after notifyItemMoved() is called

In my app I have a list of items implemented with RecyclerView. Under certain conditions items might be moved to the very end of the list and I need that to be animated. So first I move the items to the end in the data source (ArrayList in my case) and call adapter's notifyItemMoved(oldPosition, dataSet.size() - 1) method. Everything works ok except the case when the element I move to the end (it doesn't actually matter if it's the very end of the list or just a bit lower position) is either the very top in the list or the very first partly visible. In such cases not only moved item is animated but entire list scrolls down with it.

I thought it might be some kind of a mess in my code so I created a clean test application with a RecyclerView component and the result was the same.

I think it's a bug of RecyclerView because it's still very green component and Goggle continues to develop it, although I'm pretty sure I'm not the only one who faced such a problem so I hope somebody has a workaround for that.

Here's code snippets of my Activity, RecyclerView.Adapter and a layout.

Activity

public class RecyclerViewActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recycler_view);

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        recyclerView.setHasFixedSize(true);
        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);

        RecyclerView.Adapter adapter = new RecyclerViewAdapter(recyclerView);
        recyclerView.setAdapter(adapter);
        recyclerView.addItemDecoration(new    SimpleDividerItemDecoration(RecyclerViewActivity.this));
    }
}

Adapter

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
    private ArrayList<String> mDataSet;
    private RecyclerView mRecyclerView;

    public RecyclerViewAdapter(RecyclerView recyclerView) {
        mRecyclerView = recyclerView;

        mDataSet = new ArrayList<String>();
        mDataSet.add("San Francisco");
        mDataSet.add("Los Angeles");
        mDataSet.add("Seattle");
        mDataSet.add("Portland");
        mDataSet.add("Sacramento");
        mDataSet.add("San Diego");
        mDataSet.add("Chicago");
        mDataSet.add("Boston");
        mDataSet.add("New York");
        mDataSet.add("New Jersey");
        mDataSet.add("Washington");
        mDataSet.add("Miami");
        mDataSet.add("New Orlean");
    }

    public RecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_recycler_view_item, parent, false);
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int itemPosition = mRecyclerView.getChildAdapterPosition(v);
                String item = mDataSet.get(itemPosition);
                mDataSet.remove(itemPosition);
                mDataSet.add(item);
                notifyItemMoved(itemPosition, mDataSet.size() - 1);

            }
        });
        return new ViewHolder(view);
    }

    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.itemName.setText(mDataSet.get(position));
    }

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

    public static class ViewHolder extends RecyclerView.ViewHolder {
        protected TextView itemName;

        public ViewHolder(View v) {
            super(v);

            itemName = (TextView) v.findViewById(R.id.recycler_view_item_text);
        }
    }
}

Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="vertical"/>

</LinearLayout>

And here's a video of how it looks: https://youtu.be/kjG4oFE7o0w

1) Portland - ok.

2) Seattle - ok.

3) Los Angleles - ok.

4) San Francisco - NOT OK - entire list is scrolling down with the item.

And further in the video can see examples of the same behavior with the elements which aren't at the top of the list but are the very top visible ones and partly hovered by the list's top border.

like image 288
Alex Berdnikov Avatar asked Jun 07 '15 20:06

Alex Berdnikov


People also ask

How do I scroll to the bottom of my recycler view?

Recyclerview scroll to bottom using scrollToPositon. After setting the adapter, then call the scrollToPosition function to scroll the recycler view to the bottom. recyclerView. setAdapter(mAdapter); recyclerView.

Is RecyclerView scrollable?

To be able to scroll through a vertical list of items that is longer than the screen, you need to add a vertical scrollbar. Inside RecyclerView , add an android:scrollbars attribute set to vertical .

What is the use of onBindViewHolder in android?

onBindViewHolder. Called by RecyclerView to display the data at the specified position. This method should update the contents of the itemView to reflect the item at the given position. Note that unlike android.

What is onCreateViewHolder?

onCreateViewHolder is called when you need a new View. If there is an available Recycled View that can be provided and be bound with new data, then onBindViewHolder is called :) 96. 96. 96.


1 Answers

My temporary solution:

// mLayoutManager is LinearLayoutManager from RecyclerView
int visible = mLayoutManager.findFirstVisibleItemPosition();
int offset = mLayoutManager.getChildAt(visible).getTop();

... remove, add and call notifyItemMoved

mLayoutManager.scrollToPositionWithOffset(visible, offset);
like image 164
Oleksandr Avatar answered Nov 15 '22 05:11

Oleksandr