ImageView height relative to width - android

I have this layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
style="#style/AppTheme.Widgets.Box"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="match_parent"
android:layout_height="2dp"
android:scaleType="centerCrop"
android:background="#eee"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:gravity="center"/>
</LinearLayout>
So, I am loading remote images into ImageView from web. I know the dimensions of the image so i know width:height ratio. Now I need to somehow apply this ration when I am initializing my layout so it doesnt jump like crazy later in the app.

For such case I've created a custom ImageView that maintains it's height relative to width. It has the custom attribute 'height_ratio' that's multiplied by width to get height:
DynamicHeightImageView.java:
/**
* An {#link android.widget.ImageView} layout that maintains a consistent width to height aspect ratio.
*/
public class DynamicHeightImageView extends ImageView {
private float mHeightRatio;
public DynamicHeightImageView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.DynamicHeightImageView, 0, 0);
try {
mHeightRatio = ta.getFloat(R.styleable.DynamicHeightImageView_height_ratio, 0.0f);
} finally {
ta.recycle();
}
}
public DynamicHeightImageView(Context context) {
super(context);
}
public void setHeightRatio(float ratio) {
if (ratio != mHeightRatio) {
mHeightRatio = ratio;
requestLayout();
}
}
public double getHeightRatio() {
return mHeightRatio;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mHeightRatio > 0.0f) {
// set the image views size
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = (int) (width * mHeightRatio);
setMeasuredDimension(width, height);
}
else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DynamicHeightImageView">
<attr name="height_ratio" format="float"/>
</declare-styleable>
</resources>
Usage:
<com.melnykov.android.views.DynamicHeightImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
custom:height_ratio="0.6"/>

If you use CENTER_INSIDE as scale type, the image will be scaled so that the aspect ratio is preserved and the image fits into the "frame" of the image view that you have defined with layout_width and layout_height. But are you sure that you want your image 2dp high at maximum?

Since you know the dimensions of your image. You can set the image view's dimension programmatically converting pixels into dp
Refer this Convert Pixels to DP

As an improvement (as I see it) to makovkastar's answer:
I've tried to make the whole thing a little more simple, wrote it in Kotlin and made it so that the height and width aspects are set as integers as to be more precise by using fractions instead of floating point numbers:
/**
* An [android.widget.ImageView] layout that maintains a consistent width to height aspect ratio.
*/
class DynamicHeightImageView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ImageView(context, attrs, defStyleAttr) {
private var heightFactor : Int
private var widthFactor : Int
init {
context.theme.obtainStyledAttributes(attrs, R.styleable.DynamicHeightImageView, 0, 0).apply {
heightFactor = getInt(R.styleable.DynamicHeightImageView_heightFactor, 1)
widthFactor = getInt(R.styleable.DynamicHeightImageView_widthFactor, 1)
}
}
/**
* Adjusts the [getHeight] relative to the [getWidth], according to [heightFactor] and [widthFactor].
*/
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
layoutParams.height = (width.toFloat() * heightFactor / widthFactor).toInt()
}
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DynamicHeightImageView">
<attr name="heightFactor" format="integer"/>
<attr name="widthFactor" format="integer"/>
</declare-styleable>
</resources>

Related

larger width image to thumbnail

Url image having width 900*346, we are using same image for displaying detail image and thumbnail image.Thumbnail image is displaying in Gridview with 2 columns and 200dp height but image is stretching.Is there any way to display larger width image with out stretch. Thanks in advance.
GridView Xml:
<?xml version="1.0" encoding="utf-8"?>
<GridView
android:id="#+id/grid_img"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:horizontalSpacing="#dimen/portfolio_grid_space"
android:verticalSpacing="#dimen/portfolio_grid_space"
android:layout_marginTop="#dimen/portfolio_grid_space"
android:layout_marginBottom="#dimen/portfolio_grid_space"
android:numColumns="2"/>
Gridview Adapter xml:
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/img"
android:scaleType="fitCenter"
android:layout_width="match_parent" android:layout_height="200dp">
</ImageView>
Give in the xml file of your layout android:scaleType="fitXY"
P.S : this applies to when the image is set with android:src="..." rather than android:background="..." as backgrounds are set by default to stretch and fit to the View.
If I do not misunderstand, you want to keep image width/height ratio while stretching the image. You can put the below lines in your xml ImageView:
android:adjustViewBounds="true"
android:scaleType="centerCrop"
You can play around with scaleType to get your desired image.
IF you are using device below API 17, you can custom ImageView:
public class ScaleAspectFillImageView extends ImageView {
public ScaleAspectFillImageView(Context context) {
super(context);
}
public ScaleAspectFillImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScaleAspectFillImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// If API level <= 17,
// Only need custom measurement for image whose size is smaller than imageView size.
// http://developer.android.com/reference/android/widget/ImageView.html#setAdjustViewBounds(boolean)
if (android.os.Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
float viewWidth = MeasureSpec.getSize(widthMeasureSpec);
float viewHeight = MeasureSpec.getSize(heightMeasureSpec);
// Calculate imageView.height based on imageView.width and drawable aspect ratio using scale-aspect-fill mode.
Drawable drawable = getDrawable();
if (drawable != null) {
float drawableWidth = drawable.getIntrinsicWidth();
float drawableHeight = drawable.getIntrinsicHeight();
// Only need custom measurement for image whose size is smaller than imageView size.
// Avoid divide by zero error.
boolean needCustomMeasurement = drawableWidth < viewWidth || drawableHeight < viewHeight;
if (needCustomMeasurement && drawableHeight > 0) {
float drawableAspectRatio = drawableWidth / drawableHeight;
// imageView.width and drawable aspect ratio must be greater than zero.
if (drawableAspectRatio > 0 && viewWidth > 0) {
float targetHeight = viewWidth / drawableAspectRatio;
setMeasuredDimension((int) viewWidth, (int) targetHeight);
return; // Already set the measured dimension.
}
}
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}

Full height square element in android

I would like to have a square (same width as height) GridView fill the full height of the screen in landscape orientation. The Gridview is a chessboard (8 by 8 squares) with the xml:
<com.example.jens.jchess2.view.MyGridView
android:id="#+id/chessboard"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="0dp"
android:numColumns="8"
android:verticalSpacing="0dp"
android:horizontalSpacing="0dp">
</com.example.jens.jchess2.view.MyGridView>
and the elements of the grid are:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/square"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#000080"
android:orientation="vertical"
android:padding="0pt">
<ImageView
android:id="#+id/square_background"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:padding="0pt" />
<ImageView
android:id="#+id/piece"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:padding="0pt" />
</FrameLayout>
, where the ImageViews correspond to the squares and pieces (both from png images) of the board.
In the custom MyGridView I override onMeasure as follows:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = getContext().getResources().getDisplayMetrics().heightPixels;
if (width > height) {
super.onMeasure(
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
);
} else {
super.onMeasure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY)
);
}
}
which gives me a square GridView in both portrait and landscape orientation. In portrait mode it fills the full width and everything is fine. In landscape mode however it extends below the screen because the height (=width) of the GridView/board is too large. It is too large by the height of the toolbar and the height of the statusbar. How can I get the proper size for the GridView, i.e. screen height minus status bar height minus toolbar height?
Start with two versions of your layout file:
/res/layout/grid.xml
...
<!-- full width -->
<com.example.MyGridView
android:layout_width="match_parent"
android:layout_height="wrap_content"
...
/>
...
/res/layout-land/grid.xml
...
<!-- full height -->
<com.example.MyGridView
android:layout_width="wrap_content"
android:layout_height="match_parent"
...
/>
...
You probably already have something like this.
Now in your onMeasure() override, the match_parent dimension will have a MeasureSpec mode of EXACTLY and the wrap_content dimension will have a MeasureSpec mode of AT_MOST. You can use this to achieve your desired layout.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.AT_MOST) {
// portrait
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
} else if (heightMode == MeasureSpec.EXACTLY && widthMode == MeasureSpec.AT_MOST) {
// landscape
super.onMeasure(heightMeasureSpec, heightMeasureSpec);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
EDIT: I found out that both modes can be AT_MOST depending on the ViewGroup container. Please see my other answer for updated measuring code.
Ah. Now I see that this is for a game.
Sometimes it's better to have layouts and child views, but in most cases with game boards you are better off creating a single View subclass that represents the game view.
For instance, what if your users say they want the ability to pinch-zoom into one quadrant of the game board? You can't do that with a GridView.
I whipped up a simple app to show you how this can work. I simplified the onMeasure() code I posted before, and instead of a GridView, a single View subclass renders the game board.
The MainActivity simply sets up the content view.
/res/layout/activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="com.example.gameboard.MainActivity">
<com.example.gameboard.GameBoardView
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
/res/layout-land/activity_main.xml:
Notice match_parent and wrap_content are switched for width and height.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="com.example.gameboard.MainActivity">
<com.example.gameboard.GameBoardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true" />
</RelativeLayout>
GameBoardView.java:
public class GameBoardView extends View {
private Paint mPaint;
public GameBoardView(Context context) {
super(context);
}
public GameBoardView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public GameBoardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public GameBoardView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int size = Math.min(width, height);
int sizeMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
super.onMeasure(sizeMeasureSpec, sizeMeasureSpec);
mPaint = new Paint();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int w = getWidth() / 8;
int h = getHeight() / 8;
for (int row = 0; row < 8; row++) {
for (int col = 0; col < 8; col++) {
// choose black or white depending on the square
mPaint.setColor((row + col) % 2 == 0 ? 0xFFFFFFFF : 0xFF000000);
canvas.drawRect(w * col, h * row, w * (col + 1), h * (row + 1), mPaint);
}
}
}
}
Here I'm just drawing the squares right in the view. Now, if I were making a chess game, I would also create a Drawable subclass that would take the game model and render it. Having a separate Drawable for rendering the game makes it easy to scale to the correct size. For example, your Drawable could render at a fixed constant size, then be scaled by the View subclass to fit. The View subclass would function mostly as a controller, interpreting touch events and updating the game model.

How to scale ImageView width

This is my layout file
<LinearLayout ...
<ImageView
android:id="#+id/feed_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:adjustViewBounds="true"
android:contentDescription="#string/image_content_description" />
But ImageView width does not match parent width. (width is image source width)
Image Source loaded Lazy-loading from url.
How to scale the width of image view regadless image source ?
I want
Width = match(or fill) parent.
Height = auto-scaled
You can achieve this in two ways:
If your image is larger than the ImageView you can use xml only
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="fitCenter"
android:adjustViewBounds="true"/>
If your image is smaller than the ImageView
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="fitXY"
android:adjustViewBounds="true"/>
With the second option you have to measure the width and height of the image and set the height of the ImageView in your code, according to the actual width of the image view (same ratio).
You can use android:scaletype="fitXY" property of imageview
2.default Android will scale your image down to fit the ImageView, maintaining the aspect ratio. However, make sure you're setting the image to the ImageView using android:src="..." rather than android:background="...". src= makes it scale the image maintaining aspect ratio, but background= makes it scale and distort the image to make it fit exactly to the size of the ImageView. (You can use a background and a source at the same time though, which can be useful for things like displaying a frame around the main image, using just one ImageView.)
If you want to fit the image in view use android:scaleType="fitXY"
You can achieve this with this, create new AspectRatioImageView that extends imageView:
public class AspectRatioImageView extends ImageView {
public AspectRatioImageView(Context context) {
super(context);
}
public AspectRatioImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AspectRatioImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Drawable drw = getDrawable();
if (null == drw || drw.getIntrinsicWidth() <= 0) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} else {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = width * drw.getIntrinsicHeight() / drw.getIntrinsicWidth();
setMeasuredDimension(width, height);
}
}
}
And then in your layout xml use :
<my.app.AspectRatioImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/ar_imageview"/>
<ImageView
android:id="#+id/idImage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="fitXY"/>
calculate the height according to the aspect ratio that you want
Display display = getActivity().getWindowManager().getDefaultDisplay();
int height = (display.getWidth() * 9) /16; // in this case aspect ratio 16:9
ImageView image = (ImageView) findViewById(R.id.idImage);
image.getLayoutParams().height = height;

Fixed aspect ratio View

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.
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));
}
}
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.
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:
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);
}
}
}
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>
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.
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!
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>
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.
For your question, set android:layout_width="match_parent" and use app:layout_constraintDimensionRatio="H,1:1" in your view.
I have used in this way.
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/imageView_home_one"
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,4:3"
android:layout_marginTop="#dimen/_5sdp"
android:visibility="gone"
tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>

Android: why is there no maxHeight for a View?

View's have a minHeight but somehow are lacking a maxHeight:
What I'm trying to achieve is having some items (views) filling up a ScrollView. When there are 1..3 items I want to display them directly. Meaning the ScrollView has the height of either 1, 2 or 3 items.
When there are 4 or more items I want the ScrollView to stop expanding (thus a maxHeight) and start providing scrolling.
However, there is unfortunately no way to set a maxHeight. So I probably have to set my ScrollView height programmatically to either WRAP_CONTENT when there are 1..3 items and set the height to 3*sizeOf(View) when there are 4 or more items.
Can anyone explain why there is no maxHeight provided, when there is already a minHeight?
(BTW: some views, like ImageView have a maxHeight implemented.)
None of these solutions worked for what I needed which was a ScrollView set to wrap_content but having a maxHeight so it would stop expanding after a certain point and start scrolling. I just simply overrode the onMeasure method in ScrollView.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(300, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
This might not work in all situations, but it certainly gives me the results needed for my layout. And it also addresses the comment by madhu.
If some layout present below the scrollview then this trick wont work – madhu Mar 5 at 4:36
In order to create a ScrollView or ListView with a maxHeight you just need to create a Transparent LinearLayout around it with a height of what you want the maxHeight to be. You then set the ScrollView's Height to wrap_content. This creates a ScrollView that appears to grow until its height is equal to the parent LinearLayout.
This worked for me to make it customizable in xml:
MaxHeightScrollView.java:
public class MaxHeightScrollView extends ScrollView {
private int maxHeight;
private final int defaultHeight = 200;
public MaxHeightScrollView(Context context) {
super(context);
}
public MaxHeightScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
if (!isInEditMode()) {
init(context, attrs);
}
}
public MaxHeightScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (!isInEditMode()) {
init(context, attrs);
}
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public MaxHeightScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
if (!isInEditMode()) {
init(context, attrs);
}
}
private void init(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView);
//200 is a defualt value
maxHeight = styledAttrs.getDimensionPixelSize(R.styleable.MaxHeightScrollView_maxHeight, defaultHeight);
styledAttrs.recycle();
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
attr.xml
<declare-styleable name="MaxHeightScrollView">
<attr name="maxHeight" format="dimension" />
</declare-styleable>
example layout
<blah.blah.MaxHeightScrollView android:layout_weight="1"
app:maxHeight="90dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<EditText android:id="#+id/commentField"
android:hint="Say Something"
android:background="#FFFFFF"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:gravity="center_vertical"
android:maxLines="500"
android:minHeight="36dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</blah.blah.MaxHeightScrollView>
(I know this does not directly answer the question but might be helpful to others looking for maxHeight functionality)
ConstraintLayout offers maximum height for its children via
app:layout_constraintHeight_max="300dp"
app:layout_constrainedHeight="true"
or
app:layout_constraintWidth_max="300dp"
app:layout_constrainedWidth="true"
Sample usage here.
I would have commented on whizzle's answer if I could, but thought it useful to note that in order for me to solve this problem in the context of multi-window mode in Android N, I needed to change the code slightly to this:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(MeasureSpec.getSize(heightMeasureSpec) > maxHeight) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
This allows for the layout to resize to be smaller than the max height, but also prevent it from being larger than the max height. I used this is a layout class that Overrides RelativeLayout and this allowed me to create a custom dialog with a ScrollView as the child of MaxHeightRelativeLayout that does not expand the full height of the screen and also shrinks to fit within the smallest widow size in multi-window for Android N.
As mentioned above, ConstraintLayout offers maximum height for its children via:
app:layout_constraintHeight_max="300dp"
app:layout_constrainedHeight="true"
Besides, if maximum height for one ConstraintLayout's child is uncertain until App running, there still has a way to make this child automatically adapt a mutable height no matter where it was placed in the vertical chain.
For example, we need to show a bottom dialog with a mutable header TextView, a mutable ScrollView and a mutable footer TextView. The dialog's max height is 320dp,when total height not reach 320dp ScrollView act as wrap_content, when total height exceed ScrollView act as "maxHeight=320dp - header height - footer height".
We can achieve this just through xml layout file:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="320dp">
<TextView
android:id="#+id/tv_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/black_10"
android:gravity="center"
android:padding="10dp"
app:layout_constraintBottom_toTopOf="#id/scroll_view"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1"
app:layout_constraintVertical_chainStyle="packed"
tools:text="header" />
<ScrollView
android:id="#+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/black_30"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toTopOf="#id/tv_footer"
app:layout_constraintHeight_max="300dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/tv_header">
<LinearLayout
android:id="#+id/ll_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/tv_sub1"
android:layout_width="match_parent"
android:layout_height="160dp"
android:gravity="center"
android:textColor="#color/orange_light"
tools:text="sub1" />
<TextView
android:id="#+id/tv_sub2"
android:layout_width="match_parent"
android:layout_height="160dp"
android:gravity="center"
android:textColor="#color/orange_light"
tools:text="sub2" />
</LinearLayout>
</ScrollView>
<TextView
android:id="#+id/tv_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/black_50"
android:gravity="center"
android:padding="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/scroll_view"
tools:text="footer" />
</android.support.constraint.ConstraintLayout>
Most import code is short:
app:layout_constraintVertical_bias="1"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constrainedHeight="true"
Horizontal maxWidth usage is quite the same.
There is no way to set maxHeight. But you can set the Height.
To do that you will need to discovery the height of each item of you scrollView. After that just set your scrollView height to numberOfItens * heightOfItem.
To discovery the height of an item do that:
View item = adapter.getView(0, null, scrollView);
item.measure(0, 0);
int heightOfItem = item.getMeasuredHeight();
To set the height do that:
// if the scrollView already has a layoutParams:
scrollView.getLayoutParams().height = heightOfItem * numberOfItens;
// or
// if the layoutParams is null, then create a new one.
scrollView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, heightOfItem * numberOfItens));
Wrap your ScrollView around your a plainLinearLayout with layout_height="max_height", this will do a perfect job. In fact, I have this code in production from last 5 years with zero issues.
<LinearLayout
android:id="#+id/subsParent"
android:layout_width="match_parent"
android:layout_height="150dp"
android:gravity="bottom|center_horizontal"
android:orientation="vertical">
<ScrollView
android:id="#+id/subsScroll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginEnd="15dp"
android:layout_marginStart="15dp">
<TextView
android:id="#+id/subsTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/longText"
android:visibility="visible" />
</ScrollView>
</LinearLayout>
My MaxHeightScrollView custom view
public class MaxHeightScrollView extends ScrollView {
private int maxHeight;
public MaxHeightScrollView(Context context) {
this(context, null);
}
public MaxHeightScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MaxHeightScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
TypedArray styledAttrs =
context.obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView);
try {
maxHeight = styledAttrs.getDimensionPixelSize(R.styleable.MaxHeightScrollView_mhs_maxHeight, 0);
} finally {
styledAttrs.recycle();
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (maxHeight > 0) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
style.xml
<declare-styleable name="MaxHeightScrollView">
<attr name="mhs_maxHeight" format="dimension" />
</declare-styleable>
Using
<....MaxHeightScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:mhs_maxHeight="100dp"
>
...
</....MaxHeightScrollView>
I have an answer here:
https://stackoverflow.com/a/29178364/1148784
Just create a new class extending ScrollView and override it's onMeasure method.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (maxHeight > 0){
int hSize = MeasureSpec.getSize(heightMeasureSpec);
int hMode = MeasureSpec.getMode(heightMeasureSpec);
switch (hMode){
case MeasureSpec.AT_MOST:
heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(hSize, maxHeight), MeasureSpec.AT_MOST);
break;
case MeasureSpec.UNSPECIFIED:
heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
break;
case MeasureSpec.EXACTLY:
heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(hSize, maxHeight), MeasureSpec.EXACTLY);
break;
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
In case anyone needs it:
app:layout_constraintHeight_max="300dp"
It forces the View (that is inside a ConstraintLayout) to be 300dp as a max height. For those who want to do this programmatically, it goes like this:
val totalScreenHeight = displayMetrics.heightPixels
val layoutParams: ConstraintLayout.LayoutParams = viewThatIsInsideAConstraintLayout.layoutParams as ConstraintLayout.LayoutParams
layoutParams.matchConstraintMaxHeight = totalScreenHeight/2
viewThatIsInsideAConstraintLayout.layoutParams = layoutParams
Have you tried using the layout_weight value? If you set one it to a value greater than 0, it will stretch that view into the remaining space available.
If you had multiple views that needed to be stretched, then the value will become a weight between them.
So if you had two views both set to a layout_weight value of 1, then they would both stretch to fill in the space but they would both stretch to an equal amount of space. If you set one of them to the value of 2, then it would stretch twice as much as the other view.
Some more info here listed under Linear Layout.
i think u can set the heiht at runtime for 1 item just scrollView.setHeight(200px), for 2 items scrollView.setheight(400px) for 3 or more scrollView.setHeight(600px)
As we know devices running android can have different screen sizes. As we further know views should adjust dynamically and become the space which is appropriate.
If you set a max height you maybe force the view not to get enough space or take to less space. I know that sometimes it seems to be practically to set a max height. But if the resolution will ever change dramatically, and it will!, then the view, which has a max height, will look not appropriate.
i think there is no proper way to exactly do the layout you want. i would recommend you to think over your layout using layout managers and relative mechanisms. i don't know what you're trying to achieve but it sounds a little strange for me that a list should only show three items and then the user has to scroll.
btw. minHeight is not guaranteed (and maybe shouldn't exist either). it can have some benefit to force items to be visible while other relative items get smaller.
If anyone is considering using exact value for LayoutParams e.g.
setLayoutParams(new LayoutParams(Y, X );
Do remember to take into account the density of the device display otherwise you might get very odd behaviour on different devices. E.g:
Display display = getWindowManager().getDefaultDisplay();
DisplayMetrics d = new DisplayMetrics();
display.getMetrics(d);
setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, (int)(50*d.density) ));
First get the item height in pixels
View rowItem = adapter.getView(0, null, scrollView);
rowItem.measure(0, 0);
int heightOfItem = rowItem.getMeasuredHeight();
then simply
Display display = getWindowManager().getDefaultDisplay();
DisplayMetrics displayMetrics = new DisplayMetrics();
display.getMetrics(displayMetrics);
scrollView.getLayoutParams().height = (int)((heightOfItem * 3)*displayMetrics .density);
if you guys want to make a non-overflow scrollview or listview, just but it on a RelativeLayout with a topview and bottomview on top and bottom for it:
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="#+id/topview"
android:layout_below="#+id/bottomview" >
I used a custom ScrollView made in Kotlin which uses maxHeight. Example of use:
<com.antena3.atresplayer.tv.ui.widget.ScrollViewWithMaxHeight
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="100dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</com.antena3.atresplayer.tv.ui.widget.ScrollViewWithMaxHeight>
Here is the code of ScrollViewWidthMaxHeight:
import android.content.Context
import android.util.AttributeSet
import android.widget.ScrollView
import timber.log.Timber
class ScrollViewWithMaxHeight #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ScrollView(context, attrs, defStyleAttr) {
companion object {
var WITHOUT_MAX_HEIGHT_VALUE = -1
}
private var maxHeight = WITHOUT_MAX_HEIGHT_VALUE
init {
val a = context.obtainStyledAttributes(
attrs, R.styleable.ScrollViewWithMaxHeight,
defStyleAttr, 0
)
try {
maxHeight = a.getDimension(
R.styleable.ScrollViewWithMaxHeight_android_maxHeight,
WITHOUT_MAX_HEIGHT_VALUE.toFloat()
).toInt()
} finally {
a.recycle()
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var heightMeasure = heightMeasureSpec
try {
var heightSize = MeasureSpec.getSize(heightMeasureSpec)
if (maxHeight != WITHOUT_MAX_HEIGHT_VALUE) {
heightSize = maxHeight
heightMeasure = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.AT_MOST)
} else {
heightMeasure = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.UNSPECIFIED)
}
layoutParams.height = heightSize
} catch (e: Exception) {
Timber.e(e, "Error forcing height")
} finally {
super.onMeasure(widthMeasureSpec, heightMeasure)
}
}
fun setMaxHeight(maxHeight: Int) {
this.maxHeight = maxHeight
}
}
which needs also this declaration in values/attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ScrollViewWithMaxHeight">
<attr name="android:maxHeight" />
</declare-styleable>
</resources>

Categories

Resources