StaggeredGridLayoutManager reorders items
Asked Answered
P

4

18

My items has different layouts and sometimes I create a gap or gaps on top of RecyclerView by dragging items in layout. When I scroll back to the top of RecyclerView some items are reordered and gaps get filled by them. Behavior is captured here:

Auto reordering during scrolling to top

Filling gaps is okay. It is also okay when items change position during dragging. Problem is when it comes to scrolling to top. Reordering occurs when scrolling ends - I'm not touching screen.

THIS IS HOW I CREATE RECYCLERVIEW AND LAYOUT MANAGER

    recyclerView = (RecyclerView)layout.findViewById(R.id.recycler_view);

    stagaggeredGridLayoutManager =
        new StaggeredGridLayoutManager(
            getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? 3 : 2, StaggeredGridLayoutManager.VERTICAL);

    recyclerView.setLayoutManager(stagaggeredGridLayoutManager);

    stagaggeredList = getListItemData();

    StatisticsGridViewAdapter rcAdapter = new StatisticsGridViewAdapter(this.getActivity(), stagaggeredList, recyclerView);

    ItemTouchHelper.Callback callback = new StatisticsTouchHelperCallback(rcAdapter);
    touchHelper = new ItemTouchHelper(callback);
    touchHelper.attachToRecyclerView(recyclerView);

    recyclerView.setAdapter(rcAdapter);

ItemTouchHelper class

public class StatisticsTouchHelperCallback extends ItemTouchHelper.Callback {

    private final ItemTouchHelperAdapter touchHelperAdapter;


    @Override
    public boolean isLongPressDragEnabled() {
        return true;
    }

    @Override
    public boolean isItemViewSwipeEnabled() {
        return false;
    }

    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        int dragFlags = ItemTouchHelper.UP   | ItemTouchHelper.DOWN |
            ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;

        int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
        return makeMovementFlags(dragFlags, swipeFlags);
    }

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
        RecyclerView.ViewHolder target) {
        touchHelperAdapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
        return false;
    }

    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        touchHelperAdapter.onItemDismiss(viewHolder.getAdapterPosition());
    }

    public StatisticsTouchHelperCallback(ItemTouchHelperAdapter adapter) {
        touchHelperAdapter = adapter;
    }
}

RecyclerView adapter

public class StatisticsGridViewAdapter extends RecyclerView.Adapter<StatisticsGridItemViewHolder> implements ItemTouchHelperAdapter {

    private List<Statistic> itemList;
    private Context context;

    @Override
    public StatisticsGridItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View layoutView = LayoutInflater.from(parent.getContext()).inflate(itemList.get(viewType).getStatisticType().getStringResourcesId(), null);
        StatisticsGridItemViewHolder rcv = new StatisticsGridItemViewHolder(layoutView, viewType);
        return rcv;
    }

    @Override
    public void onBindViewHolder(final StatisticsGridItemViewHolder holder, int position) {
        holder.getMenu().setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                PopupMenu popup = new PopupMenu(v.getContext(), holder.getMenu());
                popup.getMenuInflater().inflate(R.menu.statistics__widget__popup_menu, popup.getMenu());
                Menu menu = popup.getMenu();
                MenuItem item = menu.add(R.string.statistics__widget__menu_item__pin_to_dashboard);
                //item.setOnMenuItemClickListener()
                popup.show();
            }
        });
    }

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

    @Override
    public boolean onItemMove(int fromPosition, int toPosition) {
        if (fromPosition < toPosition) {
            for (int i = fromPosition; i < toPosition; i++) {
                Collections.swap(itemList, i, i + 1);
            }
        } else {
            for (int i = fromPosition; i > toPosition; i--) {
                Collections.swap(itemList, i, i - 1);
            }
        }
        notifyItemMoved(fromPosition, toPosition);
        return true;
    }

    @Override
    public void onItemDismiss(int position) {
        itemList.remove(position);
        notifyItemRemoved(position);
    }

    @Override
    public int getItemViewType(int position) {return itemList.get(position).getStatisticType().getPosition();
    }

    public StatisticsGridViewAdapter(Context context, List<Statistic> itemList) {
        this.itemList = itemList;
        this.context = context;
    }
}
interface ItemTouchHelperAdapter {

    boolean onItemMove(int fromPosition, int toPosition);

    void onItemDismiss(int position);
}

Right now I use 12 different layouts with same structure. Only height is different to simulate final widgets.

Example of item's 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="wrap_content" >
    <LinearLayout
        android:id="@+id/statistics__widget__main_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/order_tile_final"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:layout_marginTop="2dp"
        android:layout_marginBottom="7dp"
        android:orientation="vertical">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="17dp"
            android:orientation="horizontal">
            <TextView
                android:id="@+id/widget_title"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:text="@string/statistics__widget__forms_filled_title"
                style="@style/tablet_common_font__main_3"
                android:paddingTop="10dp"
                android:layout_weight="1"/>
            <ImageView
                android:id="@+id/widget_menu"
                android:layout_width="wrap_content"
                android:layout_height="23dp"
                android:layout_marginTop="7dp"
                android:paddingTop="5dp"
                android:layout_marginRight="8dp"
                android:src="@drawable/a_menu_dark"/>
        </LinearLayout>
        <TextView
            android:id="@+id/widget_value"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/tablet_common_font__headline_2"
            android:layout_marginLeft="17dp"
            android:layout_marginBottom="6dp"/>
    </LinearLayout>
</LinearLayout>
Pallium answered 31/1, 2017 at 7:46 Comment(5)
@Nikola Anusev Just an improvement remove recyclerView.setHasFixedSize(true); Reason being the items being drawn on the screen are of unequal width and height.Roe
Please, provide the item layout.Thermomotor
I added example of item's layoutPallium
@MichalZhradnkNono3551 did you ever find out the solution? I am kind of facing the same issueKurd
@Kurd I did not and it is not relevant for me anymore. But I believe that you can find libraries for making dashboards like this that might be useful for you. In my case I could not use such thing.Pallium
A
2

I think this is the common behavior of StaggeredGridLayoutManager:

When scroll state is changed to SCROLL_STATE_IDLE, StaggeredGrid will check if there are gaps in the because of full span items. If it finds, it will re-layout and move items to correct positions with animations.

You can stop the gap-filling behavior by changing strategy to GAP_HANDLING_NONE:

stagaggeredGridLayoutManager.setGapStrategy(
        StaggeredGridLayoutManager.GAP_HANDLING_NONE);
Allaallah answered 6/2, 2017 at 2:48 Comment(1)
Thank you for your response. I know about this setting but it wont fix my problem. Expected scenario would be if items were moved only up and down to fill gaps.Pallium
T
0

I have worked on Recyclerview before if i have understood your issue I think the below thing will fix the issue.

@Override
public boolean onItemMove(int fromPosition, int toPosition) {
    if (fromPosition < toPosition) {
        for (int i = fromPosition; i < toPosition; i++) {
            Collections.swap(itemList, i, i + 1);
            notifyItemMoved(i, i+1);
        }
    } else {
        for (int i = fromPosition; i > toPosition; i--) {
            Collections.swap(itemList, i, i - 1);
            notifyItemMoved(i, i-1);
        }
    }

    return true;
}

Let me know if any issues or i am wrong in understanding ur issue.

Best Regds

Tendon answered 8/2, 2017 at 11:19 Comment(1)
This haven't fixed the problem. Items are still getting reordered after I scroll to top.Pallium
U
0

You have to extend the Recycler View to enable the reorder functionality.

Please follow the following link: https://gist.github.com/mohlendo/68b7e2f89d0b1b354abe

And for the full example use the following link: https://github.com/mohlendo/ReorderRecyclerView

Uxmal answered 8/2, 2017 at 14:5 Comment(1)
Example is not working with items that have different layouts.Pallium
R
0

Best Solution is to create Your StaggeredLayoutMenager

class YourStaggeredLayoutManager(count:Int, orientation:Int) : StaggeredGridLayoutManager(count,orientation) {
    override fun onItemsUpdated(
            recyclerView: RecyclerView,
            positionStart: Int,
            itemCount: Int,
            payload: Any?
    ) {}

    override fun onItemsUpdated(recyclerView: RecyclerView, positionStart: Int, itemCount: Int) {}
}
Radiosensitive answered 25/6, 2021 at 10:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.