Fixed aspect ratio View

AndroidViewAspect RatioViewgroup

Android Problem Overview


How would I go implementing a fixed aspect ratio View? I'd like to have items with 1:1 aspect ratio in a GridView. I think it's better to subclass the children than the GridView?

EDIT: I assume this needs to be done programmatically, that's no problem. Also, I don't want to limit the size, only the aspect ratio.

Android Solutions


Solution 1 - Android

I implemented FixedAspectRatioFrameLayout, so I can reuse it and have any hosted view be with fixed aspect ratio:

public class FixedAspectRatioFrameLayout extends FrameLayout
{
	private int mAspectRatioWidth;
	private int mAspectRatioHeight;
	
	public FixedAspectRatioFrameLayout(Context context)
	{
		super(context);
	}
	
	public FixedAspectRatioFrameLayout(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		
		init(context, attrs);
	}
	
	public FixedAspectRatioFrameLayout(Context context, AttributeSet attrs, int defStyle)
	{
		super(context, attrs, defStyle);
		
		init(context, attrs);
	}
	
	private void init(Context context, AttributeSet attrs)
	{
		TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FixedAspectRatioFrameLayout);

		mAspectRatioWidth = a.getInt(R.styleable.FixedAspectRatioFrameLayout_aspectRatioWidth, 4);
		mAspectRatioHeight = a.getInt(R.styleable.FixedAspectRatioFrameLayout_aspectRatioHeight, 3);
	    
	    a.recycle();
	}
	// **overrides**
	
	@Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
	{
		int originalWidth = MeasureSpec.getSize(widthMeasureSpec);
		
		int originalHeight = MeasureSpec.getSize(heightMeasureSpec);

		int calculatedHeight = originalWidth * mAspectRatioHeight / mAspectRatioWidth;
		
		int finalWidth, finalHeight;
		
		if (calculatedHeight > originalHeight)
		{
			finalWidth = originalHeight * mAspectRatioWidth / mAspectRatioHeight; 
			finalHeight = originalHeight;
		}
		else
		{
			finalWidth = originalWidth;
			finalHeight = calculatedHeight;
		}
		
		super.onMeasure(
				MeasureSpec.makeMeasureSpec(finalWidth, MeasureSpec.EXACTLY), 
				MeasureSpec.makeMeasureSpec(finalHeight, MeasureSpec.EXACTLY));
	}
}

Solution 2 - Android

For new users, here's a better non-code solution :

A new support library called Percent Support Library is available in Android SDK v22 (MinAPI is 7 me thinks, not sure) :

> src : android-developers.blogspot.in

> The Percent Support Library provides percentage based dimensions and margins and, new to this release, the ability to set a custom aspect ratio via app:aspectRatio. By setting only a single width or height and using aspectRatio, the PercentFrameLayout or PercentRelativeLayout will automatically adjust the other dimension so that the layout uses a set aspect ratio.

To include add this to your build.gradle :

compile 'com.android.support:percent:23.1.1'

Now wrap your view (the one that needs to be square) with a PercentRelativeLayout / PercentFrameLayout :

<android.support.percent.PercentRelativeLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content">
     <ImageView
         app:layout_aspectRatio="100%"
         app:layout_widthPercent="100%"/>
 </android.support.percent.PercentRelativeLayout>

You can see an example here.

Solution 3 - Android

To not use third-party solution and considering the fact that both PercentFrameLayout and PercentRelativeLayout were deprecated in 26.0.0, I'd suggest you to consider using ConstraintLayout as a root layout for your grid items.

Your item_grid.xml might look like:

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

    <ImageView
        android:id="@+id/imageview_item"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:scaleType="centerCrop"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintDimensionRatio="H,1:1" />

</android.support.constraint.ConstraintLayout>

As a result you get something like this:

Fixed ratio sized grid items

Solution 4 - Android

I recently made a helper class for this very problem and wrote a [blog post about it][1].

The meat of the code is as follows:

/**
 * Measure with a specific aspect ratio<br />
 * <br />
 * @param widthMeasureSpec The width <tt>MeasureSpec</tt> passed in your <tt>View.onMeasure()</tt> method
 * @param heightMeasureSpec The height <tt>MeasureSpec</tt> passed in your <tt>View.onMeasure()</tt> method
 * @param aspectRatio The aspect ratio to calculate measurements in respect to 
 */
public void measure(int widthMeasureSpec, int heightMeasureSpec, double aspectRatio) {
    int widthMode = MeasureSpec.getMode( widthMeasureSpec );
    int widthSize = widthMode == MeasureSpec.UNSPECIFIED ? Integer.MAX_VALUE : MeasureSpec.getSize( widthMeasureSpec );
    int heightMode = MeasureSpec.getMode( heightMeasureSpec );
    int heightSize = heightMode == MeasureSpec.UNSPECIFIED ? Integer.MAX_VALUE : MeasureSpec.getSize( heightMeasureSpec );
     
    if ( heightMode == MeasureSpec.EXACTLY && widthMode == MeasureSpec.EXACTLY ) {
        /* 
         * Possibility 1: Both width and height fixed
         */
        measuredWidth = widthSize;
        measuredHeight = heightSize;
         
    } else if ( heightMode == MeasureSpec.EXACTLY ) {
        /*
         * Possibility 2: Width dynamic, height fixed
         */
        measuredWidth = (int) Math.min( widthSize, heightSize * aspectRatio );
        measuredHeight = (int) (measuredWidth / aspectRatio);
         
    } else if ( widthMode == MeasureSpec.EXACTLY ) {
        /*
         * Possibility 3: Width fixed, height dynamic
         */
        measuredHeight = (int) Math.min( heightSize, widthSize / aspectRatio );
        measuredWidth = (int) (measuredHeight * aspectRatio);
         
    } else {
        /* 
         * Possibility 4: Both width and height dynamic
         */
        if ( widthSize > heightSize * aspectRatio ) {
            measuredHeight = heightSize;
            measuredWidth = (int)( measuredHeight * aspectRatio );
        } else {
            measuredWidth = widthSize;
            measuredHeight = (int) (measuredWidth / aspectRatio);
        }
         
    }
}

[1]: http://www.buzzingandroid.com/2012/11/easy-measuring-of-custom-views-with-specific-aspect-ratio/ "Easy measuring of custom Views with specific aspect ratio"

Solution 5 - Android

I created a layout library using TalL's answer. Feel free to use it.

RatioLayouts

Installation

Add this to the top of the file

repositories {
    maven {
        url  "http://dl.bintray.com/riteshakya037/maven" 
    }
}
    

dependencies {
    compile 'com.ritesh:ratiolayout:1.0.0'
}

Usage

Define 'app' namespace on root view in your layout

xmlns:app="http://schemas.android.com/apk/res-auto"

Include this library in your layout

<com.ritesh.ratiolayout.RatioRelativeLayout
        android:id="@+id/activity_main_ratio_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:fixed_attribute="WIDTH" // Fix one side of the layout 
        app:horizontal_ratio="2" // ratio of 2:3
        app:vertical_ratio="3">

Update

With introduction of ConstraintLayout you don't have to write either a single line of code or use third-parties or rely on PercentFrameLayout which were deprecated in 26.0.0.

Here's the example of how to keep 1:1 aspect ratio for your layout using ConstraintLayout:

<android.support.constraint.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginEnd="0dp"
        android:layout_marginStart="0dp"
        android:layout_marginTop="0dp"
        android:background="@android:color/black"
        app:layout_constraintDimensionRatio="H,1:1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </LinearLayout>

</android.support.constraint.ConstraintLayout>

Solution 6 - Android

I've used and liked Jake Wharton's implementation of ImageView (should go similarly for other views), others might enjoy it too:

AspectRatioImageView.java - ImageView that respects an aspect ratio applied to a specific measurement

Nice thing it's styleable in xml already.

Solution 7 - Android

Simply override onSizeChanged and calculate ratio there.

Formula for aspect ratio is:

newHeight =  original_height / original_width x new_width

this would give you something like that:

 @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
	super.onSizeChanged(w, h, oldw, oldh);
    
    //3:5 ratio
	float RATIO = 5/3;
	setLayoutParams(new LayoutParams((int)RATIO * w, w));
	
}

hope this helps!

Solution 8 - Android

The ExoPlayer from Google comes with an AspectRatioFrameLayout that you use like this:

<com.google.android.exoplayer2.ui.AspectRatioFrameLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:resize_mode="fixed_width">
    <!-- https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.html#RESIZE_MODE_FIXED_WIDTH -->

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</com.google.android.exoplayer2.ui.AspectRatioFrameLayout>

Then you must set the aspect ratio programmatically:

aspectRatioFrameLayout.setAspectRatio(16f/9f)

Note that you can also set the resize mode programmatically with setResizeMode.


Since you are obviously not going to grab the whole ExoPlayer library for this single class, you can simply copy-paste the file from GitHub to your project (it's open source):

https://github.com/google/ExoPlayer/blob/release-v2/library/ui/src/main/java/com/google/android/exoplayer2/ui/AspectRatioFrameLayout.java

Don't forget to grab the attribute resize_mode too:

https://github.com/google/ExoPlayer/blob/release-v2/library/ui/src/main/res/values/attrs.xml#L18-L25

<attr name="resize_mode" format="enum">
  <enum name="fit" value="0"/>
  <enum name="fixed_width" value="1"/>
  <enum name="fixed_height" value="2"/>
  <enum name="fill" value="3"/>
  <enum name="zoom" value="4"/>
</attr>

Solution 9 - Android

You may find third-party libraries. Instead of using them, use constraint layout. Below code sets the aspect ratio of ImageView as 16:9 regardless of the screen size and orientation.

<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:scaleType="fitXY"
        android:src="@drawable/mat3"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0"
        app:layout_constraintDimensionRatio="H,16:9"/>

</androidx.constraintlayout.widget.ConstraintLayout>

app:layout_constraintDimensionRatio="H,16:9". Here, height is set with respect to the width of the layout.

Portrait activity

Landscape activity

For your question, set android:layout_width="match_parent" and use app:layout_constraintDimensionRatio="H,1:1" in your view.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJaniView Question on Stackoverflow
Solution 1 - AndroidTalLView Answer on Stackoverflow
Solution 2 - AndroidShahiMView Answer on Stackoverflow
Solution 3 - AndroidEugene BrusovView Answer on Stackoverflow
Solution 4 - AndroidJesperBView Answer on Stackoverflow
Solution 5 - AndroidRitesh ShakyaView Answer on Stackoverflow
Solution 6 - AndroidCzechnologyView Answer on Stackoverflow
Solution 7 - AndroidJmorvanView Answer on Stackoverflow
Solution 8 - AndroidAlbert Vila CalvoView Answer on Stackoverflow
Solution 9 - AndroidTejas ManeView Answer on Stackoverflow