Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

StaggeredGridLayoutManager reorders items

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>
like image 889
Michal Zhradnk Nono3551 Avatar asked Jan 31 '17 07:01

Michal Zhradnk Nono3551


1 Answers

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);
like image 164
Yoni Gross Avatar answered Nov 08 '22 04:11

Yoni Gross