Increase cell width in a RecyclerView with GridLayoutManager
Asked Answered
M

2

8

I have a RecyclerView with a GridLayoutManager with spanCount=2 and a Vertical orientation.

My items are correctly displayed as the image below:

Now I need to add an animation that when a click on one of the items, let's say number "3", that item increases its width and push the item next to it (in this example, number "4") partially outside the parent/screen.

Visually, it would be something like this:

To expand the item I am setting the visibility to VISIBLE to a view inside the item and to collapse it, set the visibility to GONE.

At the moment, I am able to show and hide that view, but it only takes the space of the item, it does increase the width pushing the item next to it.

So my questions are:

  • Is this possible to use the default GridLayoutManager for this?
  • What would be a good approach to achieve this?
Midtown answered 20/11, 2018 at 10:57 Comment(0)
B
5

It is possible to useGridLayoutManager as follows:

  • Define the GridLayoutManager with two spans.
  • Set up an item layout that incorporates the clickable view and the view that will be set to VISIBLE and GONE.
  • In the adapter, when, say, the left view is clicked, set the expandable view to VISIBLE, increase the size of the itemView by the width of the expandable view. Translate the view to the right by the amount of the expansion to accommodate the increased size of the left view.
  • Do some housekeeping to make sure that recycled views are reset.

Here is an example. I assume that only the left view can be clicked. The general process is the same for the right view if if it can also be clicked, but the details differ in what needs to be translated, etc.

This example does not retained information of the status of view that are involved in an expansion, so if you expand a view then scroll down and back up, the view may be reset.

enter image description here

MainActivity.java

public class MainActivity extends AppCompatActivity {  
    private LinearLayoutManager mLayoutManager;  
    private RecyclerViewAdapter mAdapter;  
    private List<String> mItems = new ArrayList<>();  
    private RecyclerView mRecycler;  

    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

        for (int i = 0; i < 100; i++) {  
            mItems.add(i + 1 + "");  
        }  

        mLayoutManager = new GridLayoutManager(this, 2);  
        mRecycler = findViewById(R.id.recyclerView);  
        mAdapter = new RecyclerViewAdapter(mItems);  
        mRecycler.setLayoutManager(mLayoutManager);  
        mRecycler.setAdapter(mAdapter);  
    }  
}

RecyclerViewAdapter.java
This demo adapter assumes that there are an even number of items to display. For simplicity, null checks have not been coded.

class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
    implements View.OnClickListener {
    private final List<String> mItems;
    private RecyclerView mRecyclerView;

    RecyclerViewAdapter(List<String> items) {
        mItems = items;
    }

    @Override
    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
        mRecyclerView = recyclerView;
    }

    @Override
    public @NonNull
    RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false);
        return new ItemViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        ItemViewHolder vh = (ItemViewHolder) holder;

        vh.mItemTextView.setText(mItems.get(position));

        // Only allow clicks on left items which corresponds to even positions.
        vh.mItemTextView.setOnClickListener((position % 2 == 0) ? this : null);

        // Reset translation and expansion if viewholder is reused.
        vh.itemView.setTranslationX(0);
        if (vh.mExpansion != 0) {
            vh.itemView.getLayoutParams().width -= vh.mExpansion;
            vh.mExpansion = 0;
            vh.mGoneView.setVisibility(View.GONE);
        }

        int bgColor = (position % 2 == 0)
            ? android.R.color.holo_blue_light
            : android.R.color.holo_green_light;
        vh.mItemTextView.setBackgroundColor(vh.itemView.getContext().getResources().getColor(bgColor));
    }

    @Override
    public int getItemCount() {
        return (mItems == null) ? 0 : mItems.size();
    }

    @Override
    public int getItemViewType(int position) {
        return TYPE_ITEM;
    }

    @Override
    public void onClick(View v) {
        ItemViewHolder vh = (ItemViewHolder) mRecyclerView.findContainingViewHolder(v);
        View itemView = vh.itemView;

        // Get the child to the right. This child will be translated to the right to make room
        // for the expanded left view.
        View rightChild = mRecyclerView.getChildAt(findRightChildPos(vh.itemView));
        if (vh.mGoneView.getVisibility() == View.GONE) {
            // Reveal the "GONE" view, expand the itemView and translate the right-hand view.
            vh.mGoneView.setVisibility(View.VISIBLE);
            int translation = vh.mGoneView.getLayoutParams().width;
            itemView.getLayoutParams().width = itemView.getWidth() + translation;

            // Works with "GONE" view of fixed width. Make adjustments if width is variable.
            rightChild.setTranslationX(translation);
            vh.mExpansion = translation;
        } else { // View is expanded.
            // Undo the expansion changes.
            vh.mGoneView.setVisibility(View.GONE);
            itemView.getLayoutParams().width = itemView.getWidth() - vh.mExpansion;
            vh.mExpansion = 0;
            rightChild.setTranslationX(0);
        }
    }

    // Find the child to the right of a view within the RecyclerView.
    private int findRightChildPos(View view) {
        for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
            if (mRecyclerView.getChildAt(i) == view) {
                return i + 1;
            }
        }
        return RecyclerView.NO_POSITION;
    }

    static class ItemViewHolder extends RecyclerView.ViewHolder {
        final TextView mItemTextView;
        final View mGoneView;
        int mExpansion;

        ItemViewHolder(View item) {
            super(item);
            mItemTextView = item.findViewById(R.id.textView);
            mGoneView = item.findViewById(R.id.expandingView);
        }
    }

    private final static int TYPE_ITEM = 1;
}

recycler_item.xml

<LinearLayout 
    android:id="@+id/item"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="8dp"
    android:clickable="true"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center"
        android:textSize="48sp"
        tools:background="@android:color/holo_blue_light"
        tools:text="1" />

    <View
        android:id="@+id/expandingView"
        android:layout_width="100dp"
        android:layout_height="match_parent"
        android:background="@android:color/holo_purple"
        android:visibility="gone" />

    <View
        android:layout_width="10dp"
        android:layout_height="match_parent"
        android:background="@android:color/holo_red_light" />
</LinearLayout>
Berthold answered 25/11, 2018 at 19:42 Comment(2)
Thank you, that is a great starting point. I just have one problem. If the expandable view is on the right, if I change the width of the itemView like itemView.getLayoutParams().width = itemView.getWidth() + translation;, the width will increase to the right, instead of growing to the left? Is that true?Midtown
@Midtown The view will grow to the right. You will then need to do some translations to the left to make the itemView shows completely. The itemView in the left-hand column will also need to shift to the left.Berthold
W
0

I see how to implement this using HorizontalScrollView/RecyclerView with LinearLayoutManager. Instead of using GridLayoutManager with two columns you can use LinearLayoutManager with such views:

__|HorizontalScrollView/RecyclerView| | [Firts view] [Second view] | |---------------------------------|

Then it's trivial to implement expanded views.

In other case you'll need custom layout manager, which can scroll horizontally and vertically.

Wiencke answered 23/11, 2018 at 9:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.