ImageView in ListView that expands maximum width - android

I have a ListView whose items are ImageViews, for the sake of discussion. I want the image to scale to the width of the ListView's rows, and maintain it's aspect ratio. here's a simplified version of the ListView's item layout,
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:padding="4dp" >
<FrameLayout
android:id="#+id/page_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="visible" >
<ImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:src="#drawable/some_image" />
</FrameLayout>
</FrameLayout>
</LinearLayout>
the ImageView's width is match_parent, and all ancestors of it are the same. Layout designer shows that the ImageView's dimensions span the entire width, but the image inside does not.
The image is scaling correctly. The problem is that the height of the list view row is such that the width can't scale to the right size. If I use fitXY, it does scale the width, but then the aspect ratio is incorrect.
Any ideas?

I couldn't find any way to accomplish this without creating a custom view. in a nutshell, the custom view sets it's own dimensions to the real (not scaled) dimensions of the drawable behind it.
public class ResizableImageView extends ImageView {
public static interface OnSizeChangedListener {
void onSizeChanged(int width, int height);
}
private OnSizeChangedListener onSizeChangedListener = null;
public ResizableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = width * getDrawable().getIntrinsicHeight() / getDrawable().getIntrinsicWidth();
setMeasuredDimension(width, height);
if (onSizeChangedListener != null) {
onSizeChangedListener.onSizeChanged(width, height);
}
}
public void setOnSizeChangedListener(OnSizeChangedListener listener) {
this.onSizeChangedListener = listener;
}
}
In my case I needed to obtain the resized value in order to size the other views, hence the listener interface.

If what you want is to maintain the aspect ratio, according to the Android API Guide, I think that you have to change android:scaleType="fitCenter" for android:scaleType="centerInside" or maybe android:scaleType="centerCrop" as fitCenter doesn't maintain the aspect ratio of the image.

Related

Custom Square Layout not working as expected

I stumbled across this problem when working with custom Square Layout : by extending the Layout and overriding its onMeasure() method to make the dimensions = smaller of the two (height or width).
Following is the custom Layout code :
public class CustomSquareLayout extends RelativeLayout{
public CustomSquareLayout(Context context) {
super(context);
}
public CustomSquareLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomSquareLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public CustomSquareLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//Width is smaller
if(widthMeasureSpec < heightMeasureSpec)
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
//Height is smaller
else
super.onMeasure(heightMeasureSpec, heightMeasureSpec);
}
}
The custom Square Layout works fine, until in cases where the custom layout goes out of bound of the screen. What should have automatically adjusted to screen dimensions though, doesn't happen. As seen below, the CustomSquareLayout actually extends below the screen (invisible). What I expect is for the onMeasure to handle this, and give appropriate measurements. But that is not the case. Note of interest here is that even thought the CustomSquareLayout behaves weirdly, its child layouts all fall under a Square shaped layout that is always placed on the Left hand side.
<!-- XML for above image -->
<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"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="300dp"
android:text="Below is the Square Layout"
android:gravity="center"
android:id="#+id/text"
/>
<com.app.application.CustomSquareLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#id/text"
android:background="#color/colorAccent" #PINK
android:layout_centerInParent="true"
android:id="#+id/square"
android:padding="16dp"
>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true" #Note this
android:background="#color/colorPrimaryDark" #BLUE
>
</RelativeLayout>
</com.app.application.CustomSquareLayout>
</RelativeLayout>
Normal case : (Textview is in Top)
Following are few links I referenced:
Custom Square LinearLayout. How?
Simple way to do dynamic but square layout
Hope to find a solution to this, using onMeasure or any other function when extending the layout (so that even if some extends the Custom Layout, the Square property remains)
Edit 1 : For further clarification, the expected result for 1st case is shown
Edit 2 : I gave a preference to onMeasure() or such functions as the need is for the layout specs (dimensions) to be decided earlier (before rendering). Otherwise changing the dimensions after the component loads is simple, but is not requested.
You can force a square view by checking for "squareness" after layout. Add the following code to onCreate().
final View squareView = findViewById(R.id.square);
squareView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
squareView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
if (squareView.getWidth() != squareView.getHeight()) {
int squareSize = Math.min(squareView.getWidth(), squareView.getHeight());
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) squareView.getLayoutParams();
lp.width = squareSize;
lp.height = squareSize;
squareView.requestLayout();
}
}
});
This will force a remeasurement and layout of the square view with a specified size that replaces MATCH_PARENT. Not incredibly elegant, but it works.
You can also add a PreDraw listener to your custom view.
onPreDraw
boolean onPreDraw ()
Callback method to be invoked when the view tree is about to be drawn. At this point, all views in the tree have been measured and given a frame. Clients can use this to adjust their scroll bounds or even to request a new layout before drawing occurs.
Return true to proceed with the current drawing pass, or false to cancel.
Add a call to an initialization method in each constructor in the custom view:
private void init() {
this.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
if (getWidth() != getHeight()) {
int squareSize = Math.min(getWidth(), getHeight());
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) getLayoutParams();
lp.width = squareSize;
lp.height = squareSize;
requestLayout();
return false;
}
return true;
}
});
}
The XML can look like the following:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="300dp"
android:gravity="center"
android:text="Below is the Square Layout" />
<com.example.squareview.CustomSquareLayout
android:id="#+id/square"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#id/text"
android:background="#color/colorAccent"
android:padding="16dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:background="#color/colorPrimaryDark" />
</com.example.squareview.CustomSquareLayout>
</RelativeLayout>
There is a difference between the view's measured width and the view's width (same for height). onMeasure is only setting the view's measured dimensions. There is still a different part of the drawing process that constrains the view's actual dimensions so that they don't go outside the parent.
If I add this code:
final View square = findViewById(R.id.square);
square.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
System.out.println("measured width: " + square.getMeasuredWidth());
System.out.println("measured height: " + square.getMeasuredHeight());
System.out.println("actual width: " + square.getWidth());
System.out.println("actual height: " + square.getHeight());
}
});
I see this in the logs:
09-05 10:19:25.768 4591 4591 I System.out: measured width: 579
09-05 10:19:25.768 4591 4591 I System.out: measured height: 579
09-05 10:19:25.768 4591 4591 I System.out: actual width: 768
09-05 10:19:25.768 4591 4591 I System.out: actual height: 579
How to solve it by creating a custom view? I don't know; I never learned. But I do know how to solve it without having to write any Java code at all: use ConstraintLayout.
ConstraintLayout supports the idea that children should be able to set their dimensions using an aspect ratio, so you can simply use a ratio of 1 and get a square child. Here's my updated layout (the key piece is the app:layout_constraintDimensionRatio attr):
<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="match_parent">
<TextView
android:id="#+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="300dp"
android:gravity="center"
android:text="Below is the Square Layout"
app:layout_constraintTop_toTopOf="parent"/>
<RelativeLayout
android:id="#+id/square"
android:layout_width="match_parent"
android:layout_height="0dp"
android:padding="16dp"
android:background="#color/colorAccent"
app:layout_constraintTop_toBottomOf="#+id/text"
app:layout_constraintDimensionRatio="1">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:background="#color/colorPrimaryDark">
</RelativeLayout>
</RelativeLayout>
</android.support.constraint.ConstraintLayout>
And screenshots:
You cannot compare the two measure specs, as they are not simply a size. You can see a very good explanation in this answer. This answer is for a custom view, but measure specs are the same. You need to get the mode and the size to compute final sizes, and compare the end results for both dimensions.
In the second example you shared, the right question is this one (third answer). Is written for Xamarin in C#, but is easy to understand.
The case that is failing for you is because you're finding an AT_MOST mode (when the view is hitting the bottom of the screen), that's why comparisons are failing in this case.
That should be the final method (can contain typos, I have been unable to test it:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width, height;
switch (widthMode) {
case MeasureSpec.EXACTLY:
width = widthSize;
break;
case MeasureSpec.AT_MOST:
width = Math.min(widthSize, heightSize);
break;
default:
width = 100;
break;
}
switch (heightMode) {
case MeasureSpec.EXACTLY:
height = heightSize;
break;
case MeasureSpec.AT_MOST:
height = Math.min(widthSize, heightSize);
break;
default:
height = 100;
break;
}
var size = Math.min(width, height);
var newMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
super.onMeasure(newMeasureSpec, newMeasureSpec);
}
I expect the end result to be roughly like this (maybe centered, but this dimensions):
Notice that this is a made up image done with Gimp.
try this. You can use on measure method to make a custom view. Check the link below for more details.
http://codecops.blogspot.in/2017/06/how-to-make-responsive-imageview-in.html

Get width of CardView in Adapter.onBindViewHolder

I have a list of posts and most of them are pictures (simply put it is posts just like G+ or FB apps). Each post entry has an image aspect ratio, so I can set image height based on it's width even before image was loaded from server, so card layout wouldn't change on load.
The problem is layout_width="match_parent" set for both card and post image. When I get width of cardview it is zero. So i can't calculate height.
For now the only solution I see is to take width of parent container (RecyclerView) and deduct all paddings, but it doesn't look like a good solution.
Is there any other way to do it?
Here is an example of adapter code
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
....
int width = holder.itemView.getWidth();
....
//do some calculations
}
Layouts (without irrelevant parts)
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="#+id/card"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="blocksDescendants"
android:foreground="?android:attr/selectableItemBackground"
card_view:cardBackgroundColor="#ffffff"
card_view:cardCornerRadius="3dp">
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include
android:id="#+id/includedPost"
layout="#layout/post_details" />
</RelativeLayout>
</android.support.v7.widget.CardView>
includedPost:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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/postImage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/commenterImage"
android:minHeight="120dp"
android:scaleType="centerCrop" />
</RelativeLayout>
when onBind or onViewAttachedToWindow is called, the child is not measured yet so you cannot get the width. Even if these calls were made after child is measured, what you are trying to do would not be a good practice because changing height will require a new measurement.
If you are using LinearLayoutManager, it will give the full width to the child (expect RecyclerView padding and child's margins). It is not great but OK to derive your height from there.
Another (more flexible) approach here is to create a custom ImageView that keeps your aspect ratio. When onBind is called, you'll set the desired aspect ratio of your custom ImageView.
When on measure is called, it will measure depending on your aspect ratio.
class MyImageView extends ImageVIew {
....
private float mScale = 1f;
public void setScale(int scale) {
mScale = scale;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
setMeasuredDimension(width, width * mScale);
}
}
So in your onBind method, you call setScale on the ImageView depending on your w/h ratio.
I have not tested but this approach should work as desired.
You can use Picasso Transformation to achieve this.
FitToTargetViewTransformation class:
import android.graphics.Bitmap;
import android.view.View;
import com.squareup.picasso.Transformation;
/**
* Picasso Transformation class to fit image to target View size
*/
public class FitToTargetViewTransformation implements Transformation {
private View view;
public FitToTargetViewTransformation(View view) {
this.view = view;
}
#Override
public Bitmap transform(Bitmap source) {
int targetWidth = view.getWidth();
double aspectRatio = (double) source.getHeight() / (double) source.getWidth();
int targetHeight = (int) (targetWidth * aspectRatio);
if (source.getHeight() >= source.getWidth()) {
return source;
}
Bitmap result = Bitmap.createScaledBitmap(source, targetWidth, targetHeight, false);
if (result != source) {
// Same bitmap is returned if sizes are the same
source.recycle();
}
return result;
}
#Override
public String key() {
return "transformation" + " desiredWidth";
}
}
And somewhere in onBindViewHolder you do something like this:
Picasso.with(context)
.load(AVATAR_ENDPOINT)
.transform(new FitToTargetViewTransformation(feedViewHolder.icAvatar))
.into(feedViewHolder.icAvatar);

imageview height as the wrap_content of the shown image

I want the width of an ImageView to be set by the parent and the height should be aspect proportional. The reasons for this is that next is shown a TextView that I want to place just under the ImageView.
I can get image to show correctly using
android:layout_width="fill_parent"
android:layout_height="fill_parent"
however the ImageView height become the parent height which is much larger than that of the shown stretched image. One idea was to make parent smaller vertically, but.. there I don't yet know the stretched image size.
The following doesn't work because a small image is not filled up horizontally.
android:layout_width="fill_parent"
android:layout_height="wrap_content"
Messing around with
android:layout_height="wrap_content"
for the RelativeLayout surrounding it all does not help. Also tried FrameLayout and LinearLayout and failed.
Any ideas?
You have to set adjustViewBounds to true.
imageView.setAdjustViewBounds(true);
There is two case if your actual image size is equal or grater than your ImageView width and heigh then you can use adjustViewBounds property and if your actual image size is less than ImageView width and height than use scaleType property to shown image in ImageView based on your requirement.
1.Actual image size is equal or grater than ImageView required width and height.
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:src="#drawable/ic_launcher"/>
2.Actual image size is less than ImageView required width and height.
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="fitXY"
android:src="#drawable/ic_launcher"/>
So I have had the same issue more than once and looking through the existing stackoverflow answers have realised that no answer gives a perfect explanation for the real confusion regarding a solution to this problem. So here you go:
API 17+
imageView.setAdjustViewBounds(true);
OR (in XML)
android:adjustViewBounds="true"
solves the issue, it works no matter the actual size of the image resource, i.e. it will scale your image up or down to the desired size you have in your layout.
Below API 17
Below API 17 android:adjustViewBounds="true" will only work for shrinking an image, not growing, i.e. if the actual height of the image source is smaller than the dimensions you are trying to achieve in your layout, wrap_content will use that smaller height and not scale 'up' (enlarge) the image as you desire.
And so for API 17 and lower, you have no choice but to use a custom ImageView to achieve this behaviour. You could either write a custom ImageView yourself or use a library, that has already done that job.
Using a library
There is probably more than one library that fixes this issue, one of them is:
compile 'com.inthecheesefactory.thecheeselibrary:adjustable-imageview:1.0.0'
which is used like this:
<com.inthecheesefactory.thecheeselibrary.widget.AdjustableImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:src="#drawable/your_drawable"/>
Using a custom View
alternatively to using an existing library, you could write a custom view yourself, e.g.:
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.ImageView;
public class ScalableImageView extends ImageView {
boolean adjustViewBounds;
public ScalableImageView(Context context) {
super(context);
}
public ScalableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScalableImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public void setAdjustViewBounds(boolean adjustViewBounds) {
this.adjustViewBounds = adjustViewBounds;
super.setAdjustViewBounds(adjustViewBounds);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Drawable drawable = getDrawable();
if (drawable == null) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
if (adjustViewBounds) {
int drawableWidth = drawable.getIntrinsicWidth();
int drawableHeight = drawable.getIntrinsicHeight();
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (heightMode == MeasureSpec.EXACTLY && widthMode != MeasureSpec.EXACTLY) {
int height = heightSize;
int width = height * drawableWidth / drawableHeight;
if (isInScrollingContainer())
setMeasuredDimension(width, height);
else
setMeasuredDimension(Math.min(width, widthSize), Math.min(height, heightSize));
} else if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) {
int width = widthSize;
int height = width * drawableHeight / drawableWidth;
if (isInScrollingContainer())
setMeasuredDimension(width, height);
else
setMeasuredDimension(Math.min(width, widthSize), Math.min(height, heightSize));
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
private boolean isInScrollingContainer() {
ViewParent parent = getParent();
while (parent != null && parent instanceof ViewGroup) {
if (((ViewGroup) parent).shouldDelayChildPressedState()) {
return true;
}
parent = parent.getParent();
}
return false;
}
}
... which you would use as follows (XML):
<com.YOUR_PACKE_NAME.ScalableImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:src="#drawable/your_drawable" />
This was the only way I could get it working, to have the second ImageView in the center and smaller than the first ImageView, and the TextView under the second ImageView.
Unfortunately, it uses fixed "200dp" image size on the second ImageView, so it does not look the same on different sized devices.
It also destroys my ViewFlipper, since any Layout I tried around the second ImageView and the TextView makes them move or resize.
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<ImageView
android:id="#+id/imageview1"
android:contentDescription="#string/desc"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="#drawable/background" />
<ImageView
android:id="#+id/imageview2"
android:layout_centerInParent="true"
android:contentDescription="#string/desc"
android:layout_height="200dp"
android:layout_width="200dp"
android:adjustViewBounds="true"
android:src="#drawable/image2" />
<TextView
android:id="#+id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="#id/imageview2"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:gravity="center"
android:textSize="26sp"
android:textColor="#333"
android:background="#fff"
android:text="this is the text under the image right here"
/>
</RelativeLayout>

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;

Android ImageView: Fit Width

I download image and set it as a screen background dynamically using Imageview. I have tried ScaleType, to scale the image.
If image height is larger than width then ScaleTypes fitStart, fitEnd and fitCenter don't work. Android scale down the photo and fit it based on the height, but I see some extra blank space as part of the width.
I want to scale down the photo based on the width so that it fits the width and I don't care if there's some extra blank space as part of the height or if height is too long it is fine if it's going out of the view(if that's possible?).
ScaleType.XY scale the photo and fit everything in the ImageView and doesn't care about image height/weight ratio.
<ImageView
android:id="#+id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="fitStart"
/>
I ended up using this code:
<ImageView
android:id="#+id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:src="#drawable/name"
/>
Make sure you set the image using src instead of background.
android:adjustViewBounds="true" does the job!
This elegant solution found here will work like a charm for you.
Basically you just have to create a small class that extends ImageView and simply override the onMeasure method to adjust the width and height as you want. Here it scales to fit width by using:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = width * getDrawable().getIntrinsicHeight() / getDrawable().getIntrinsicWidth();
setMeasuredDimension(width, height);
}
You would use this special ImageView like this:
<your.activity.package.AspectRatioImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="center_vertical"
android:src="#drawable/test" />
There is a simple method if you simply want to fill the width of screen and have height proportional (and know the ratio of the image) you can do this in OnCreate():
setContentView(R.layout.truppview_activity);
trupImage = (ImageView) findViewById(R.id.trupImage);
Display display = getWindowManager().getDefaultDisplay();
DisplayMetrics outMetrics = new DisplayMetrics();
display.getMetrics(outMetrics);
float scWidth = outMetrics.widthPixels;
trupImage.getLayoutParams().width = (int) scWidth;
trupImage.getLayoutParams().height = (int) (scWidth * 0.6f);
Where 0.6 is the ratio adjustment (you could also calc automatically of course if you know the w & h of image).
PS:
Here is the XML side:
<ImageView
android:id="#+id/trupImage"
android:layout_width="match_parent"
android:background="#color/orange"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="fitCenter" />
if you want to fit the image whole parent block
it will stretch the image
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
And if you want to fit image whole parent with original ratio of image
it will crop out some part of image
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
This works for me.
Create a class extends ImageView
package com.yourdomain.utils;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
public class ResizableImageView extends ImageView {
public ResizableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Drawable d = getDrawable();
if (d != null) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = (int) Math.ceil((float) width * (float) d.getIntrinsicHeight() / (float) d.getIntrinsicWidth());
setMeasuredDimension(width, height);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
In xml use the following instead of ImageView
<com.yourdomain.utils.ResizableImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="#drawable/test" />
I think you cant do it only with XML, you need to resize yourself, the bitmap
Display display = getWindowManager().getDefaultDisplay();
int width = display.getWidth();
try {
((ImageView) findViewById(R.id.background))
.setImageBitmap(ShrinkBitmap(width));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
then
private Bitmap ShrinkBitmap(int width)
throws FileNotFoundException {
BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
bmpFactoryOptions.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.img, bmpFactoryOptions);
int widthRatio = (int) android.util.FloatMath
.ceil(bmpFactoryOptions.outWidth / (float) width);
bmpFactoryOptions.inSampleSize = widthRatio;
if (bmpFactoryOptions.inSampleSize <= 0)
bmpFactoryOptions.inSampleSize = 0;
bmpFactoryOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
bmpFactoryOptions.inJustDecodeBounds = false;
bitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.img, bmpFactoryOptions);
return bitmap;
}
And the layout
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/background"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

Categories

Resources