Square layout on GridLayoutManager for RecyclerView

To have the square elements in my RecyclerView, I provide a simple wrapper for my root View element; I use the following SquareRelativeLayout in place of RelativeLayout.

package net.simplyadvanced.widget;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.RelativeLayout;

/** A RelativeLayout that will always be square -- same width and height,
 * where the height is based off the width. */
public class SquareRelativeLayout extends RelativeLayout {

    public SquareRelativeLayout(Context context) {
        super(context);
    }

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

    public SquareRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @TargetApi(VERSION_CODES.LOLLIPOP)
    public SquareRelativeLayout(Context context, AttributeSet attrs,         int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // Set a square layout.
        super.onMeasure(widthMeasureSpec, widthMeasureSpec);
    }

}

Then, in my XML layout for the adapter, I've just referenced the custom view as shown in the following. Though, you can do this programmatically also.

<?xml version="1.0" encoding="utf-8"?>
<net.simplyadvanced.widget.SquareRelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/elementRootView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <!-- More widgets here. -->

</net.simplyadvanced.widget.SquareRelativeLayout>

Note: Depending on which orientation your grid is, then you may want to have the width based off of height (GridLayoutManager.HORIZONTAL) instead of the height being based off the width (GridLayoutManager.VERTICAL).


Constraint layout solves this problem. Use app:layout_constraintDimensionRatio="H,1:1"

recyclerview_grid_layout.xml

<android.support.constraint.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <ImageView
        android:id="@+id/imageview"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintDimensionRatio="H,1:1"
        android:scaleType="centerCrop"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>

</android.support.constraint.ConstraintLayout>

EDIT

Set ImageView width to 0dp. match_parent is now deprecated for ConstraintLayout.


In case someone would like to scale the view differently - this is how you do it:

private static final double WIDTH_RATIO = 3;
private static final double HEIGHT_RATIO = 4;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = (int) (HEIGHT_RATIO / WIDTH_RATIO * widthSize);
    int newHeightSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
    super.onMeasure(widthMeasureSpec, newHeightSpec);
}