问题
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.
回答1:
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));
}
}
回答2:
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.
回答3:
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:
回答4:
I recently made a helper class for this very problem and wrote a blog post about it.
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);
}
}
}
回答5:
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.
回答6:
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>
回答7:
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!
回答8:
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>
来源:https://stackoverflow.com/questions/7058507/fixed-aspect-ratio-view