Comment dois-je procéder pour implémenter un rapport hauteur/largeur fixe View
? J'aimerais avoir des éléments avec un rapport d'aspect de 1: 1 dans un GridView
. Je pense qu'il vaut mieux sous-classer les enfants que les GridView
?
EDIT: Je suppose que cela doit être fait par programme, ce n'est pas un problème. De plus, je ne veux pas limiter la taille, seulement le rapport d'aspect.
J'ai implémenté FixedAspectRatioFrameLayout, donc je peux le réutiliser et avoir n'importe quelle vue hébergée avec un rapport d'aspect fixe:
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));
}
}
Pour les nouveaux utilisateurs, voici une meilleure solution non-code:
Une nouvelle bibliothèque de support appelée ( Bibliothèque de support en pourcentage est disponible dans Android SDK v22 (MinAPI est 7 me pense, pas sûr):
src: Android-developers.blogspot.in
La bibliothèque de prise en charge de pourcentage fournit des dimensions et des marges basées sur un pourcentage et, nouveau dans cette version, la possibilité de définir un rapport hauteur/largeur personnalisé via app: aspectRatio. En ne définissant qu'une seule largeur ou hauteur et en utilisant aspectRatio, PercentFrameLayout ou PercentRelativeLayout ajustera automatiquement l'autre dimension afin que la mise en page utilise un rapport d'aspect défini.
Pour l'inclure, ajoutez ceci à votre build.gradle:
compile 'com.Android.support:percent:23.1.1'
Enveloppez maintenant votre vue (celle qui doit être carrée) avec un 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>
Vous pouvez voir un exemple ici .
Pour ne pas utiliser de solution tierce et compte tenu du fait que PercentFrameLayout et PercentRelativeLayout ont été dépréciés dans 26.0.0, je vous suggère d'envisager d'utiliser ConstraintLayout comme disposition racine pour vos éléments de grille.
Votre item_grid.xml
pourrait ressembler à:
<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>
En conséquence, vous obtenez quelque chose comme ceci:
J'ai récemment créé une classe d'aide pour ce problème et j'ai écrit un blog à ce sujet .
La viande du code est la suivante:
/**
* 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);
}
}
}
J'ai créé une bibliothèque de mise en page en utilisant la réponse de TalL . Sentez-vous libre de l'utiliser.
Ajoutez ceci en haut du fichier
repositories {
maven {
url "http://dl.bintray.com/riteshakya037/maven"
}
}
dependencies {
compile 'com.ritesh:ratiolayout:1.0.0'
}
Définissez l'espace de noms "app" sur la vue racine dans votre mise en page
xmlns:app="http://schemas.Android.com/apk/res-auto"
Inclure cette bibliothèque dans votre mise en page
<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">
Avec l'introduction de ConstraintLayout vous n'avez pas besoin d'écrire une seule ligne de code ou d'utiliser des tiers ou de vous fier à PercentFrameLayout
qui étaient obsolètes dans 26.0.0.
Voici l'exemple de la façon de conserver le rapport d'aspect 1: 1 pour votre mise en page en utilisant 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>
J'ai utilisé et aimé l'implémentation de ImageView
de Jake Wharton (devrait aller de même pour les autres vues), d'autres pourraient en profiter aussi:
AspectRatioImageView.Java - ImageView qui respecte un rapport hauteur/largeur appliqué à une mesure spécifique
Heureusement qu'il est déjà stylable en XML.
Remplacez simplement onSizeChanged
et calculez le ratio à cet endroit.
La formule du rapport hauteur/largeur est:
newHeight = original_height / original_width x new_width
cela vous donnerait quelque chose comme ça:
@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));
}
j'espère que cela t'aides!
L'ExoPlayer de Google est livré avec un AspectRatioFrameLayout
que vous utilisez comme ceci:
<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>
Ensuite, vous devez définir le rapport d'aspect par programme:
aspectRatioFrameLayout.setAspectRatio(16f/9f)
Notez que vous pouvez également définir le mode de redimensionnement par programme avec setResizeMode
.
Comme vous n'allez évidemment pas récupérer l'intégralité de la bibliothèque ExoPlayer pour cette classe unique, vous pouvez simplement copier-coller le fichier de GitHub dans votre projet (c'est Open source):
N'oubliez pas de récupérer l'attribut resize_mode
aussi:
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>