Android Tip: Auto column grid layout by RecyclerView

    In the previous post, I have guided how to create grid or list layout by using RecyclerView through config LayoutManager. It's weakness in building grid layout is cannot auto fixed number of columns of grid view in default. So, in this tip, I will present a solution to do this trick by custom, make a subclass of RecyclerView.

Customizing the RecyclerView

   By calculating the column width in the constructor, we'll know max grid layout columns. We also set GridLayoutManager as RecyclerView's layout manager here (to define that it will behave as a GridView):
AutofitRecyclerView.java
package info.devexchanges.autocolumn;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;

public class AutofitRecyclerView extends RecyclerView {
    private GridLayoutManager manager;
    private int columnWidth = -1; //default value

    public AutofitRecyclerView(Context context) {
        super(context);
        init(context, null);
    }

    public AutofitRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public AutofitRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        if (attrs != null) {
            int[] attrsArray = {android.R.attr.columnWidth};
            TypedArray array = context.obtainStyledAttributes(attrs, attrsArray);
            columnWidth = array.getDimensionPixelSize(0, -1);
            array.recycle();
        }

        manager = new GridLayoutManager(getContext(), 1);
        setLayoutManager(manager);
    }

    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        super.onMeasure(widthSpec, heightSpec);
        if (columnWidth > 0) {
            int spanCount = Math.max(1, getMeasuredWidth() / columnWidth);
            manager.setSpanCount(spanCount);
        }
    }
}

Usage in UI (Activity/Fragment)

    Now, declaring an AutofitRecyclerView object in activity layout out (xml file) like this:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <info.devexchanges.autocolumn.AutofitRecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false"
        android:columnWidth="@dimen/column_width"
        android:padding="@dimen/activity_horizontal_margin" />

</LinearLayout>
    There is no special point in the activity Java code, we mustn't set layout manager for AutofitRecyclerView anymore, we have done in it's constructor above:
MainActivity.java
package info.devexchanges.autocolumn;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_auto_fit_recycler_view);
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        recyclerView.setHasFixedSize(true);
        recyclerView.setAdapter(new NumberedAdapter(30));
    }
}
    Customizing the RecyclerView.ViewHolder, handling each grid view item click event in onBindViewHolder(), this is adapter for our RecyclerView:
NumberedAdapter.java
package info.devexchanges.autocolumn;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

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

public class NumberedAdapter extends RecyclerView.Adapter<NumberedAdapter.ViewHolder> {
    private List<String> labels;

    public NumberedAdapter(int count) {
        labels = new ArrayList<>(count);
        for (int i = 0; i < count; ++i) {
            labels.add(String.valueOf(i));
        }
    }

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

    @Override
    public void onBindViewHolder(final ViewHolder holder, final int position) {
        final String label = labels.get(position);
        holder.textView.setText(label);

        //handling item click event
        holder.textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(holder.textView.getContext(), label, Toast.LENGTH_SHORT).show();
            }
        });
    }

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

    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView textView;

        public ViewHolder(View itemView) {
            super(itemView);
            textView = (TextView) itemView.findViewById(R.id.text);
        }
    }
}
    Layout for each grid view item:
layout_item.xml

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="@dimen/item_margin"
    android:background="@drawable/light_blue_background"
    android:gravity="center"
    android:padding="8dp"
    android:textAppearance="?android:attr/textAppearanceMedium" />
    In order to make this RecyclerView compatible with multiple devices, I declared some dimensions value in dimensions resource:
styles.xml
<resources>
    <!-- Default screen margins, per the Android Design guidelines. -->
    <dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">16dp</dimen>
    <dimen name="item_margin">8dp</dimen>
    <dimen name="column_width">80dp</dimen>
</resources>
values-w820dp/styles.xml
<resources>
    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
         (such as screen margins) for screens with more than 820dp of available width. This
         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
    <dimen name="activity_horizontal_margin">64dp</dimen>
    <dimen name="item_margin">14dp</dimen>
    <dimen name="column_width">120dp</dimen>
</resources>
    Running this example, we'll have this output:

When rotating device to landscape mode, number of columns will be increased to fit with new device width:

Running in a 7" tablet (Samsung Galaxy Tab 4):

Clicking at grid view item:

Conclusions

    With a small custom in RecyclerView's constructor, we can make a auto-column grid view easily. Further, to deep understanding this widget, you can visit this tag link to read other posts about it. Thanks for reading!

Share


Previous post
« Prev Post
Next post
Next Post »