I have a GridView. The data of GridView is request from a server.
Here is the item layout in GridView:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/analysis_micon_bg"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingBottom="#dimen/half_activity_vertical_margin"
android:paddingLeft="#dimen/half_activity_horizontal_margin"
android:paddingRight="#dimen/half_activity_horizontal_margin"
android:paddingTop="#dimen/half_activity_vertical_margin" >
<ImageView
android:id="#+id/ranking_prod_pic"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:contentDescription="#string/app_name"
android:scaleType="centerCrop" />
<TextView
android:id="#+id/ranking_rank_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/ranking_prod_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/ranking_prod_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
I request data from server, get image url and load image to Bitmap
public static Bitmap loadBitmapFromInputStream(InputStream is) {
return BitmapFactory.decodeStream(is);
}
public static Bitmap loadBitmapFromHttpUrl(String url) {
try {
return loadBitmapFromInputStream((InputStream) (new URL(url).getContent()));
} catch (Exception e) {
Log.e(TAG, e.getMessage());
return null;
}
}
and there is the code of getView(int position, View convertView, ViewGroup parent) method in adapter
Bitmap bitmap = BitmapUtil.loadBitmapFromHttpUrl(product.getHttpUrl());
prodImg.setImageBitmap(bitmap);
The image size is 210*210. I run my application on my Nexus 4. The image does fill ImageView width, but the ImageView height does not scale. ImageView does not show the whole image.
How do I solve this problem?
Without using any custom classes or libraries:
<ImageView
android:id="#id/img"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="fitCenter" />
scaleType="fitCenter" (default when omitted)
will make it as wide as the parent allows and up/down-scale as needed keeping aspect ratio.
scaleType="centerInside"
if the intrinsic width of src is smaller than parent widthwill center the image horizontally
if the intrinsic width of src is larger than parent widthwill make it as wide as the parent allows and down-scale keeping aspect ratio.
It doesn't matter if you use android:src or ImageView.setImage* methods and the key is probably the adjustViewBounds.
I like answer of arnefm but he made a small mistake (see comments) which I will try to correct:
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
/**
* ImageView that keeps aspect ratio when scaled
*/
public class ScaleImageView extends ImageView {
public ScaleImageView(Context context) {
super(context);
}
public ScaleImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ScaleImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
try {
Drawable drawable = getDrawable();
if (drawable == null) {
setMeasuredDimension(0, 0);
} else {
int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
if (measuredHeight == 0 && measuredWidth == 0) { //Height and width set to wrap_content
setMeasuredDimension(measuredWidth, measuredHeight);
} else if (measuredHeight == 0) { //Height set to wrap_content
int width = measuredWidth;
int height = width * drawable.getIntrinsicHeight() / drawable.getIntrinsicWidth();
setMeasuredDimension(width, height);
} else if (measuredWidth == 0){ //Width set to wrap_content
int height = measuredHeight;
int width = height * drawable.getIntrinsicWidth() / drawable.getIntrinsicHeight();
setMeasuredDimension(width, height);
} else { //Width and height are explicitly set (either to match_parent or to exact value)
setMeasuredDimension(measuredWidth, measuredHeight);
}
}
} catch (Exception e) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
Thus your ImageView will be scaled properly and will have no dimension problems if (for instance) put inside of ScrollView
I had a similar problem once. I solved it by making a custom ImageView.
public class CustomImageView extends ImageView
Then override the onMeasure method of the imageview. I did something like this I believe:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
try {
Drawable drawable = getDrawable();
if (drawable == null) {
setMeasuredDimension(0, 0);
} else {
float imageSideRatio = (float)drawable.getIntrinsicWidth() / (float)drawable.getIntrinsicHeight();
float viewSideRatio = (float)MeasureSpec.getSize(widthMeasureSpec) / (float)MeasureSpec.getSize(heightMeasureSpec);
if (imageSideRatio >= viewSideRatio) {
// Image is wider than the display (ratio)
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = (int)(width / imageSideRatio);
setMeasuredDimension(width, height);
} else {
// Image is taller than the display (ratio)
int height = MeasureSpec.getSize(heightMeasureSpec);
int width = (int)(height * imageSideRatio);
setMeasuredDimension(width, height);
}
}
} catch (Exception e) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
This will stretch the image to fit the screen while maintaining the aspect ratio.
Use android:scaleType="centerCrop".
FOR IMAGE VIEW (set these parameters)
android:layout_width = "match_parent"
android:layout_height = "wrap_content"
android:scaleType = "fitCenter"
android:adjustViewBounds = "true"
Now whatever the size of the image is there, it's width will match the parent and height will be according to match the ratio. I have tested this and I am 100% sure.
we want this -->
not this -->
// Results will be:
Image width -> stretched as match parent
Image height -> according to image width (maximum to aspect ratio)
// like the first one
I did something similar to the above and then banged my head against the wall for a few hours because it did not work inside a RelativeLayout. I ended up with the following code:
package com.example;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
public class ScaledImageView extends ImageView {
public ScaledImageView(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
final Drawable d = getDrawable();
if (d != null) {
int width;
int height;
if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
height = MeasureSpec.getSize(heightMeasureSpec);
width = (int) Math.ceil(height * (float) d.getIntrinsicWidth() / d.getIntrinsicHeight());
} else {
width = MeasureSpec.getSize(widthMeasureSpec);
height = (int) Math.ceil(width * (float) d.getIntrinsicHeight() / d.getIntrinsicWidth());
}
setMeasuredDimension(width, height);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
And then to prevent RelativeLayout from ignoring the measured dimension I did this:
<FrameLayout
android:id="#+id/image_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="#+id/something">
<com.example.ScaledImageView
android:id="#+id/image"
android:layout_width="wrap_content"
android:layout_height="150dp"/>
</FrameLayout>
This will not be applicable if you set image as background in ImageView, need to set at src(android:src).
Thanks.
Yo don't need any java code. You just have to :
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="centerCrop" />
The key is in the match parent for width and height
To create an image with width equals screen width, and height proportionally set according to aspect ratio, do the following.
Glide.with(context).load(url).asBitmap().into(new SimpleTarget<Bitmap>() {
#Override
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
// creating the image that maintain aspect ratio with width of image is set to screenwidth.
int width = imageView.getMeasuredWidth();
int diw = resource.getWidth();
if (diw > 0) {
int height = 0;
height = width * resource.getHeight() / diw;
resource = Bitmap.createScaledBitmap(resource, width, height, false);
}
imageView.setImageBitmap(resource);
}
});
Hope this helps.
Use these properties in ImageView to keep aspect ratio:
android:adjustViewBounds="true"
android:scaleType="fitXY"
You can try to do what you're doing by manually loading the images, but I would very very strongly recommend taking a look at Universal Image Loader.
I recently integrated it into my project and I have to say its fantastic. Does all the worrying about making things asynchronous, resizing, caching images for you. It's really easy to integrate and set up. Within 5 minutes you can probably get it doing what you want.
Example code:
//ImageLoader config
DisplayImageOptions displayimageOptions = new DisplayImageOptions.Builder().showStubImage(R.drawable.downloadplaceholder).cacheInMemory().cacheOnDisc().showImageOnFail(R.drawable.loading).build();
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext()).
defaultDisplayImageOptions(displayimageOptions).memoryCache(new WeakMemoryCache()).discCache(new UnlimitedDiscCache(cacheDir)).build();
if (ImageLoader.getInstance().isInited()) {
ImageLoader.getInstance().destroy();
}
ImageLoader.getInstance().init(config);
imageLoadingListener = new ImageLoadingListener() {
#Override
public void onLoadingStarted(String s, View view) {
}
#Override
public void onLoadingFailed(String s, View view, FailReason failReason) {
ImageView imageView = (ImageView) view;
imageView.setImageResource(R.drawable.android);
Log.i("Failed to Load " + s, failReason.toString());
}
#Override
public void onLoadingComplete(String s, View view, Bitmap bitmap) {
}
#Override
public void onLoadingCancelled(String s, View view) {
}
};
//Imageloader usage
ImageView imageView = new ImageView(getApplicationContext());
if (orientation == 1) {
imageView.setLayoutParams(new LinearLayout.LayoutParams(width / 6, width / 6));
} else {
imageView.setLayoutParams(new LinearLayout.LayoutParams(height / 6, height / 6));
}
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageLoader.displayImage(SERVER_HOSTNAME + "demos" + demo.getPathRoot() + demo.getRootName() + ".png", imageView, imageLoadingListener);
This can lazy load the images, fit them correctly to the size of the imageView showing a placeholder image while it loads, and showing a default icon if loading fails and caching the resources.
-- I should also add that this current config keeps the image aspect ratio, hence applicable to your original question
Try this: it solved the problem for me
android:adjustViewBounds="true"
android:scaleType="fitXY"
Just use UniversalImageLoader and set
DisplayImageOptions.Builder()
.imageScaleType(ImageScaleType.EXACTLY_STRETCHED)
.build();
and no scale settings on ImageView
try with this simple line... add this line in your xml code in image view tag with out adding any dependency
android:scaleType="fitXY"
use android:ScaleType="fitXY" im ImageView xml
I had a similar issue, I found the reason for this is because you need to calculate the dp. Android studio is calculating the ImageView when you load it from the drawable, but when you are using another method, like loading from bitmap the dp is not automatically accounted for,
Here is my xml
<ImageView
android:id="#+id/imageViewer"
android:layout_width="match_parent"
android:layout_height="match_parent"//dp is not automaticly updated, when loading from a other source
android:scaleType="fitCenter"
tools:srcCompat="#drawable/a8" />
I'm using Kotlin, and loading drawable from an asset file, here's how I calculate this
val d = Drawable.createFromStream(assets.open("imageData/${imageName}.png"), null)
bitHeight = d.minimumHeight//get the image height
imageViewer.layoutParams.height = (bitHeight * resources.displayMetrics.density).toInt()//set the height
imageViewer.setImageDrawable(d)//set the image from the drawable
imageViewer.requestLayout()//here I apply it to the layout
Related
How should I go about allocating fixed space for adaptive ad banners?
My XML adaptive banner
<FrameLayout
android:id="#+id/ad_view_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_alignParentBottom="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
Inside my Fragment getting adaptive ad banner dimensions
private val adSize: AdSize
get() {
val outMetrics = Resources.getSystem().displayMetrics;
val density = outMetrics.density
var adWidthPixels = adContainerView.width.toFloat()
if (adWidthPixels == 0f) {
adWidthPixels = outMetrics.widthPixels.toFloat()
}
val adWidth = (adWidthPixels / density).toInt()
return AdSize.getCurrentOrientationBannerAdSizeWithWidth(context, adWidth)
}
I would like to follow Googles guidlines and have an allocated fixed space reserved for my banner, so that if my banner "lags" for a second screen on mobile stays the same.
How should I go about it, whats the proper way?
when starting the app you can get the adaptive banner height straight away by calling something like adSize.getHeight(). This will give you banner height in dps.
You then convert that heigth dps in px and set this height to your ad frame layout programatically by calling setLayoutParams with acquired height in pixels.
This way you dont need to wait for the ad to load to apply proper height and risk ad overlapping policy violation or conent shifting (which would happen if you depend on wrap_content)
If you have a more complex behavior, like I do, you can use the following implementation. I have a ConstraintLayout that splits my screen in two parts in portrait mode. The banner is within one of these two parts and the ratio between them is not fixed but depending on some logic. So this implementation also works with that requirement, as it overwrites onMeasure to determine the best size depending on the available width.
public class AdBanner extends FrameLayout
{
private AdView mAdView;
private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
public AdBanner(Context context)
{
super(context);
}
public AdBanner(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public AdBanner(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
if (!isShowBanner())
{
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.AT_MOST));
return;
}
int width = MeasureSpec.getSize(widthMeasureSpec);
if (width <= 0)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// That's where we determine the most accurate banner format.
AdSize adSize = AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(getContext(), getDpWidth(width));
int height = adSize.getHeightInPixels(getContext());
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode != MeasureSpec.UNSPECIFIED)
{
height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec));
}
setMeasuredDimension(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.getMode(heightMeasureSpec)));
}
protected int getDpWidth(int width)
{
Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
display.getMetrics(mDisplayMetrics);
return (int) (width / mDisplayMetrics.density);
}
protected boolean isShowBanner()
{
// Do your checks here, like whether the user payed for ad removement.
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
if (!isShowBanner())
{
return;
}
int width = r - l;
if (width <= 0)
{
return;
}
AdSize adSize = AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(getContext(), getDpWidth(width));
// Prevent the ad from beeing added with each layout cicle,
// by checking, whether or not available size actually changed the format of the banner
if (mAdView == null || !adSize.equals(mAdView.getAdSize()))
{
removeAllViews();
mAdView = new AdView(getContext());
mAdView.setAdSize(adSize);
((GameApplication) getContext().getApplicationContext()).androidInjector().getAdService().loadBannerAd(getRootActivity(this), mAdView);
this.addView(mAdView);
}
mAdView.layout(0, 0, width, b - t);
}
}
I am using recyclerview with imageviews in each cell.Each imageview loads images from the web and can be square or with more width than height or more height than width i.e any sizes.I am going to display a placeholder for each image while it loads in the background(with a progressbar).But the problem is the dimension of the images is unknown and I want to size the placeholders exactly the size of the images like the 9gag app in which the placeholders are exactly the size of the images while loading in the bacground.How do I achieve this in android ?I don't want to use wrap-content(produces a jarring effect after the image has been downloaded) or a specific height to the imageviews(crops the images).I am using UIL and currently planning to switch to Fresco or Picassa.
In case that your placeholder is just filled of some color, you can easily emulate a color drawable with exactly the same size.
/**
* The Color Drawable which has a given size.
*/
public class SizableColorDrawable extends ColorDrawable {
int mWidth = -1;
int mHeight = -1;
public SizableColorDrawable(int color, int width, int height) {
super(color);
mWidth = width;
mHeight = height;
}
#Override public int getIntrinsicWidth() {
return mWidth;
}
#Override public int getIntrinsicHeight() {
return mHeight;
}
}
To use it with Picasso:
Picasso.with(context).load(url).placeholder(new SizableColorDrawable(color, width, height)).into(imageView);
Now some tips on the ImageView:
public class DynamicImageView extends ImageView {
#Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Drawable drawable = getDrawable();
if (drawable == null) {
super.onMeasure(widthSpecureSpec, heightMeasureSpec);
return;
}
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int reqWidth = drawable.getIntrinsicWidth();
int reqHeight = drawable.getIntrinsicHeight();
// Now you have the measured width and height,
// also the image's width and height,
// calculate your expected width and height;
setMeasuredDimension(targetWidth, targetHeight);
}
}
Hope this will help someone..
If you use fresco, you need to pass the width and height of your image from the web server, then you set the layout params of your drawee view with that width and height.
Like this:
RelativeLayout.LayoutParams draweeParams =
new RelativeLayout.LayoutParams(desiredImageWidthPixel,
desiredImageHeightPixel);
yourDraweeView.setLayoutParams(draweeParams);
From width and height of the image that you pass from your web server, you can calculate/resize proportionally the view as you need. Where desiredImageWidthPixel is the calculated image width that you want to show in yourDraweeView and desiredImageHeightPixel is the calculated image height that you want to show in yourDraweeView.
Don't forget to call
yourDraweeView.getHierarchy().setActualImageScaleType(ScalingUtils.ScaleType.FIT_XY);
To make yourDraweeView match the actual params that you set before.
Hope this helps
You can feed image dimensions along with the image url. (I assume you are getting image list from a source, like a JSON file or something.) And resize the ImageView holder inside the RecyclerView according to their dimension and then fire the image downloading process.
I have a VideoView which is set up like this:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/player"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<VideoView
android:id="#+id/video"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:layout_centerVertical="true" />
<ProgressBar
android:id="#+id/loader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true" />
</RelativeLayout>
But the VideoView matches the width of the parent container, and then the height is set according to the aspect ratio of the loaded movie.
I would like to do just the opposite, I want the VideoView to match the height of the parent while keeping the aspect ratio intact, the video will be clipped on the sides.
I managed to stretch the VideoView to fill the parent but then the aspect ratio is not kept.
Another thing is, I'm adding MediaController to the VideoView like this:
MediaController controllers = new MediaController(this) {
#Override
public void hide() {
if (state != State.Hidden) {
this.show();
}
else {
super.hide();
}
}
};
controllers.setAnchorView(videoView);
videoView.setMediaController(controllers);
videoView.setOnPreparedListener(new OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mediaPlayer) {
controllers.show();
}
});
This works great, and the controllers always stay on, but the height of the controllers is not being taken into account when calculating where to place the video (since it's vertically centered).
My two questions then are:
How do I make the VideoView match the height of the parent yet keep the aspect ratio?
How do I make the VideoView take into account the height of it's controllers?
Thanks.
You should extends from the built-in video view.
Call setVideoSize before video view is shown, you can get video size from thumbnail extracted from video.
So that, when video view's onMeasure is called, both mVideoWidth & mVideoHeight are > 0.
If you want to account the height of controllers, you can do it yourself in the onMeasure method.
Hope will help.
public class MyVideoView extends VideoView {
private int mVideoWidth;
private int mVideoHeight;
public MyVideoView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyVideoView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public MyVideoView(Context context) {
super(context);
}
public void setVideoSize(int width, int height) {
mVideoWidth = width;
mVideoHeight = height;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Log.i("###", "onMeasure");
int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
if (mVideoWidth > 0 && mVideoHeight > 0) {
if (mVideoWidth * height > width * mVideoHeight) {
// Log.i("###", "image too tall, correcting");
height = width * mVideoHeight / mVideoWidth;
} else if (mVideoWidth * height < width * mVideoHeight) {
// Log.i("###", "image too wide, correcting");
width = height * mVideoWidth / mVideoHeight;
} else {
// Log.i("###", "aspect ratio is correct: " +
// width+"/"+height+"="+
// mVideoWidth+"/"+mVideoHeight);
}
}
// Log.i("###", "setting size: " + width + 'x' + height);
setMeasuredDimension(width, height);
}
}
I solved this problem with layout. It seems that it worked fine when it was pinned to the corners but it caused the video to skew. To test I changed my relative layout's background to #990000 to see the red poking through.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/relative_parent"
android:background="#000000">
<VideoView
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:focusable="false"
android:focusableInTouchMode="false"
android:id="#+id/videoView" />
</RelativeLayout>
Regarding question 1, I am surprised no one has mentioned the possible use of the MediaPlayer's scaling mode.
https://developer.android.com/reference/android/media/MediaPlayer.html#setVideoScalingMode(int)
It has 2 modes. Both of them always fill the view area. To get it to fill the space while preserving the aspect ratio, thus cropping the long side, you need to switch to the second mode, VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING. That solves one part of the problem. The other part is to change VideoView's measuring behavior, just as some of the other answers demonstrate. This is the way I did it, mostly out of laziness and not familiar with the metadata API's that the others use, you are welcome to use this method or one of the other methods to fix the size of the view. The blanket catch ensures safety when this is called before mMediaPlayer exists, as it may be called many times, and also falls back to old behavior should the field name ever change.
class FixedSizeVideoView : VideoView {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
// rather than shrink down to fit, stay at the size requested by layout params. Let the scaling mode
// of the media player shine through. If the scaling mode on the media player is set to the one
// with cropping, you can make a player similar to AVLayerVideoGravityResizeAspectFill on iOS
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
try {
val mpField = VideoView::class.java.getDeclaredField("mMediaPlayer")
mpField.isAccessible = true
val mediaPlayer: MediaPlayer = mpField.get(this) as MediaPlayer
val width = View.getDefaultSize(mediaPlayer.videoWidth, widthMeasureSpec)
val height = View.getDefaultSize(mediaPlayer.videoHeight, heightMeasureSpec)
setMeasuredDimension(width, height)
}
catch (ex: Exception) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
}
}
So using this class in the layout, you just change the scaling mode on the media Player wherever you have a chance. Such as:
video.setOnPreparedListener { mp: MediaPlayer ->
mp.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING)
mp.isLooping = true
mp.setScreenOnWhilePlaying(false)
}
video.start()
public class MyVideoView extends VideoView {
private int mVideoWidth;
private int mVideoHeight;
public MyVideoView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyVideoView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public MyVideoView(Context context) {
super(context);
}
#Override
public void setVideoURI(Uri uri) {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(this.getContext(), uri);
mVideoWidth = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
mVideoHeight = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
super.setVideoURI(uri);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Log.i("###", "onMeasure");
int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
if (mVideoWidth > 0 && mVideoHeight > 0) {
if (mVideoWidth * height > width * mVideoHeight) {
// Log.i("###", "image too tall, correcting");
height = width * mVideoHeight / mVideoWidth;
} else if (mVideoWidth * height < width * mVideoHeight) {
// Log.i("###", "image too wide, correcting");
width = height * mVideoWidth / mVideoHeight;
} else {
// Log.i("###", "aspect ratio is correct: " +
// width+"/"+height+"="+
// mVideoWidth+"/"+mVideoHeight);
}
}
// Log.i("###", "setting size: " + width + 'x' + height);
setMeasuredDimension(width, height);
}
}
Quick and efficient fix:
No need to create a custom view extending from VideoView. Just set a value big enough to android:layout_width. This will set the widthSpecMode of the video view to View.MeasureSpec.AT_MOST and then the onMeasure() method of VideoView will auto-adjust its width keeping the ratio.
<VideoView
android:id="#+id/video"
android:layout_width="2000dp"
android:layout_height="match_parent" />
Using ConstraintLayout we can achieve this, refer below xml code.
When layout_width and layout_height are 0dp, the size and position of the VideoView are calculated dynamically based on the other constraints. The layout_constraintDimensionRatio attribute indicates that when the app calculates the size of the VideoView, the ratio of the width to the height should be 3:4. This constraint keeps the aspect ratio of the video the same and prevents the view from being stretched too far in either direction (depending on how the device is rotated).
Change layout_constraintDimensionRatio value depending on requirement Portrait/Landscape.
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<VideoView
android:id="#+id/videoView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="3:4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
For the first time a question answered my issue instead of answers!!
My issue was that I had a white space under the video on full screen. I was setting the layout_height to match_parent. The solution was to set it to wrap_content and give the parent a black background. That, and having the VideoView centered vertically in its parent.
I wrote this as a comment but then thought someone might have the same
issue I had, so here it is as an answer also.
I've tried a lot of solutions, while my video was always in 1000*1000 format, so I've created an easy solution for people who know their aspect ratio. First create a VideoView in a RelativeLayout like this:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/video_holder"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:clipToPadding="false">
<VideoView
android:id="#+id/videoView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true" />
</RelativeLayout>
Then before you load the video change the height and with programmatically like this:
int i = videoView.getHeight() > videoView.getWidth() ? videoView.getHeight() : videoView.getWidth();
video_holder.setLayoutParams(new ConstraintLayout.LayoutParams(i, i));
Of course this only works with 1:1 aspect ratio's but you could just use your aspect ratio to change either the height or the width.
Jobbert's answer in Kotlin, in case anyone needs it:
val max = if (videoView.height > videoView.width) videoView.height else videoView.width
videoView.layoutParams = ConstraintLayout.LayoutParams(max, max)
I have been looking for ways to display video in aspect fill in VideoView but after trying many solutions, none of them seems to work.
So I implemented the following approach and it's working for me:
Code:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// getting screen size
((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int width = displayMetrics.widthPixels;
int height = displayMetrics.heightPixels;
double videoSizeRatio = (double) mVideoHeight / mVideoWidth;
double screenSizeRatio = (double) height / width;
if (mVideoWidth > 0 && mVideoHeight > 0) {
if (videoSizeRatio > screenSizeRatio) { // screen is wider than video width
height = (int) (videoSizeRatio * width);
} else if (videoSizeRatio < screenSizeRatio) {
width = (int) (height / videoSizeRatio);
}
}
setMeasuredDimension(width, height);
}
Layout:
<YourCustomizedVideoView
android:id="#+id/videoView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
/>
The best way to do so is
Set width of videoview to max
I was facing the same issue.
I just set the Width to 999px
and Height to match parent
It works.
We get a perfect view like Tik tok or Instagram reels
Just put your VideoView inside the RelativeLayout and set the desired size for that relative layout. like below code,
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="300dp">
<VideoView
android:id="#+id/videoView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true" />
</RelativeLayout>
It will work.
You just need to put Videoview widget in RelativeLayout (changes in xml file only)
here is reference answer link
I'm trying to create a method for resizing multi-line text in a TextView such that it fits within the bounds (both the X and Y dimensions) of the TextView.
At present, I have something, but all it does is resize the text such that just the first letter/character of the text fills the dimensions of the TextView (i.e. only the first letter is viewable, and it's huge). I need it to fit all the lines of the text within the bounds of the TextView.
Here is what I have so far:
public static void autoScaleTextViewTextToHeight(TextView tv)
{
final float initSize = tv.getTextSize();
//get the width of the view's back image (unscaled)....
float minViewHeight;
if(tv.getBackground()!=null)
{
minViewHeight = tv.getBackground().getIntrinsicHeight();
}
else
{
minViewHeight = 10f;//some min.
}
final float maxViewHeight = tv.getHeight() - (tv.getPaddingBottom()+tv.getPaddingTop())-12;// -12 just to be sure
final String s = tv.getText().toString();
//System.out.println(""+tv.getPaddingTop()+"/"+tv.getPaddingBottom());
if(minViewHeight >0 && maxViewHeight >2)
{
Rect currentBounds = new Rect();
tv.getPaint().getTextBounds(s, 0, s.length(), currentBounds);
//System.out.println(""+initSize);
//System.out.println(""+maxViewHeight);
//System.out.println(""+(currentBounds.height()));
float resultingSize = 1;
while(currentBounds.height() < maxViewHeight)
{
resultingSize ++;
tv.setTextSize(resultingSize);
tv.getPaint().getTextBounds(s, 0, s.length(), currentBounds);
//System.out.println(""+(currentBounds.height()+tv.getPaddingBottom()+tv.getPaddingTop()));
//System.out.println("Resulting: "+resultingSize);
}
if(currentBounds.height()>=maxViewHeight)
{
//just to be sure, reduce the value
tv.setTextSize(resultingSize-1);
}
}
}
I think the problem is in the use of tv.getPaint().getTextBounds(...). It always returns small numbers for the text bounds... small relative to the tv.getWidth() and tv.getHeight() values... even if the text size is far larger than the width or height of the TextView.
The AutofitTextView library from MavenCentral handles this nicely. The source hosted on Github(1k+ stars) at https://github.com/grantland/android-autofittextview
Add the following to your app/build.gradle
repositories {
mavenCentral()
}
dependencies {
implementation 'me.grantland:autofittextview:0.2.+'
}
Enable any View extending TextView in code:
AutofitHelper.create(textView);
Enable any View extending TextView in XML:
<me.grantland.widget.AutofitLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
/>
</me.grantland.widget.AutofitLayout>
Use the built in Widget in code or XML:
<me.grantland.widget.AutofitTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
/>
New since Android O:
https://developer.android.com/preview/features/autosizing-textview.html
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoSizeTextType="uniform"
android:autoSizeMinTextSize="12sp"
android:autoSizeMaxTextSize="100sp"
android:autoSizeStepGranularity="2sp"
/>
I have played with this for quite some time, trying to get my font sizes correct on a wide variety of 7" tablets (kindle fire, Nexus7, and some inexpensive ones in China with low-res screens) and devices.
The approach that finally worked for me is as follows. The "32" is an arbitrary factor that basically gives about 70+ characters across a 7" tablet horizontal line, which is a font size I was looking for. Adjust accordingly.
textView.setTextSize(getFontSize(activity));
public static int getFontSize (Activity activity) {
DisplayMetrics dMetrics = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(dMetrics);
// lets try to get them back a font size realtive to the pixel width of the screen
final float WIDE = activity.getResources().getDisplayMetrics().widthPixels;
int valueWide = (int)(WIDE / 32.0f / (dMetrics.scaledDensity));
return valueWide;
}
I was able to answer my own question using the following code (see below), but my solution was very specific to the application. For instance, this will probably only look good and/or work for a TextView sized to approx. 1/2 the screen (with also a 40px top margin and 20px side margins... no bottom margin).
The using this approach though, you can create your own similar implementation. The static method basically just looks at the number of characters and determines a scaling factor to apply to the TextView's text size, and then incrementally increases the text size until the overall height (an estimated height -- using the width of the text, the text height, and the width of the TextView) is just below that of the TextView. The parameters necessary to determine the scaling factor (i.e. the if/else if statements) were set by guess-and-check. You'll likely have to play around with the numbers to make it work for your particular application.
This isn't the most elegant solution, though it was easy to code and it works for me. Does anyone have a better approach?
public static void autoScaleTextViewTextToHeight(final TextView tv, String s)
{
float currentWidth=tv.getPaint().measureText(s);
int scalingFactor = 0;
final int characters = s.length();
//scale based on # of characters in the string
if(characters<5)
{
scalingFactor = 1;
}
else if(characters>=5 && characters<10)
{
scalingFactor = 2;
}
else if(characters>=10 && characters<15)
{
scalingFactor = 3;
}
else if(characters>=15 && characters<20)
{
scalingFactor = 3;
}
else if(characters>=20 && characters<25)
{
scalingFactor = 3;
}
else if(characters>=25 && characters<30)
{
scalingFactor = 3;
}
else if(characters>=30 && characters<35)
{
scalingFactor = 3;
}
else if(characters>=35 && characters<40)
{
scalingFactor = 3;
}
else if(characters>=40 && characters<45)
{
scalingFactor = 3;
}
else if(characters>=45 && characters<50)
{
scalingFactor = 3;
}
else if(characters>=50 && characters<55)
{
scalingFactor = 3;
}
else if(characters>=55 && characters<60)
{
scalingFactor = 3;
}
else if(characters>=60 && characters<65)
{
scalingFactor = 3;
}
else if(characters>=65 && characters<70)
{
scalingFactor = 3;
}
else if(characters>=70 && characters<75)
{
scalingFactor = 3;
}
else if(characters>=75)
{
scalingFactor = 5;
}
//System.out.println(((int)Math.ceil(currentWidth)/tv.getWidth()+scalingFactor));
//the +scalingFactor is important... increase this if nec. later
while((((int)Math.ceil(currentWidth)/tv.getWidth()+scalingFactor)*tv.getTextSize())<tv.getHeight())
{
tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, tv.getTextSize()+0.25f);
currentWidth=tv.getPaint().measureText(s);
//System.out.println(((int)Math.ceil(currentWidth)/tv.getWidth()+scalingFactor));
}
tv.setText(s);
}
Thanks.
I had the same problem and wrote a class that seems to work for me. Basically, I used a static layout to draw the text in a separate canvas and remeasure until I find a font size that fits. You can see the class posted in the topic below. I hope it helps.
Auto Scale TextView Text to Fit within Bounds
Stumbled upon this whilst looking for a solution myself... I'd tried all the other solutions out there that I could see on stack overflow etc but none really worked so I wrote my own.
Basically by wrapping the text view in a custom linear layout I've been able to successfully measure the text properly by ensuring it is measured with a fixed width.
<!-- TextView wrapped in the custom LinearLayout that expects one child TextView -->
<!-- This view should specify the size you would want the text view to be displayed at -->
<com.custom.ResizeView
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_margin="10dp"
android:layout_weight="1"
android:orientation="horizontal" >
<TextView
android:id="#+id/CustomTextView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
</com.custom.ResizeView>
Then the linear layout code
public class ResizeView extends LinearLayout {
public ResizeView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ResizeView(Context context) {
super(context);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// oldWidth used as a fixed width when measuring the size of the text
// view at different font sizes
final int oldWidth = getMeasuredWidth() - getPaddingBottom() - getPaddingTop();
final int oldHeight = getMeasuredHeight() - getPaddingLeft() - getPaddingRight();
// Assume we only have one child and it is the text view to scale
TextView textView = (TextView) getChildAt(0);
// This is the maximum font size... we iterate down from this
// I've specified the sizes in pixels, but sp can be used, just modify
// the call to setTextSize
float size = getResources().getDimensionPixelSize(R.dimen.solutions_view_max_font_size);
for (int textViewHeight = Integer.MAX_VALUE; textViewHeight > oldHeight; size -= 0.1f) {
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
// measure the text views size using a fixed width and an
// unspecified height - the unspecified height means measure
// returns the textviews ideal height
textView.measure(MeasureSpec.makeMeasureSpec(oldWidth, MeasureSpec.EXACTLY), MeasureSpec.UNSPECIFIED);
textViewHeight = textView.getMeasuredHeight();
}
}
}
Hope this helps someone.
maybe try setting setHoriztonallyScrolling() to true before taking text measurements so that the textView doesn't try to layout your text on multiple lines
One way would be to specify different sp dimensions for each of the generalized screen sizes. For instance, provide 8sp for small screens, 12sp for normal screens, 16 sp for large and 20 sp for xlarge. Then just have your layouts refer to #dimen text_size or whatever and you can rest assured, as density is taken care of via the sp unit. See the following link for more info on this approach.
http://www.developer.android.com/guide/topics/resources/more-resources.html#Dimension
I must note, however, that supporting more languages means more work during the testing phase, especially if you're interested in keeping text on one line, as some languages have much longer words. In that case, make a dimens.xml file in the values-de-large folder, for example, and tweak the value manually. Hope this helps.
Here is a solution that I created based on some other feedback. This solution allows you to set the size of the text in XML which will be the max size and it will adjust itself to fit the view height.
Size Adjusting TextView
private float findNewTextSize(int width, int height, CharSequence text) {
TextPaint textPaint = new TextPaint(getPaint());
float targetTextSize = textPaint.getTextSize();
int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
while(textHeight > height && targetTextSize > mMinTextSize) {
targetTextSize = Math.max(targetTextSize - 1, mMinTextSize);
textHeight = getTextHeight(text, textPaint, width, targetTextSize);
}
return targetTextSize;
}
private int getTextHeight(CharSequence source, TextPaint paint, int width, float textSize) {
paint.setTextSize(textSize);
StaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
return layout.getHeight();
}
If your only requirement is to have the text automatically split and continue in the next line and the height is not important then just have it like this.
<TextView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:maxEms="integer"
android:width="integer"/>
This will have your TextView wrap to it's content vertically depending on your maxEms value.
Check if my solution helps you:
Auto Scale TextView Text to Fit within Bounds
I found that this worked well for me. see: https://play.google.com/store/apps/details?id=au.id.rupert.chauffeurs_name_board&hl=en
Source Code at http://www.rupert.id.au/chauffeurs_name_board/verson2.php
http://catchthecows.com/?p=72 and https://github.com/catchthecows/BigTextButton
This is based on mattmook's answer. It worked well on some devices, but not on all. I moved the resizing to the measuring step, made the maximum font size a custom attribute, took margins into account, and extended FrameLayout instead of LineairLayout.
public class ResizeView extends FrameLayout {
protected float max_font_size;
public ResizeView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.ResizeView,
0, 0);
max_font_size = a.getDimension(R.styleable.ResizeView_maxFontSize, 30.0f);
}
public ResizeView(Context context) {
super(context);
}
#Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
// Use the parent's code for the first measure
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Assume we only have one child and it is the text view to scale
final TextView textView = (TextView) getChildAt(0);
// Check if the default measure resulted in a fitting textView
LayoutParams childLayout = (LayoutParams) textView.getLayoutParams();
final int textHeightAvailable = getMeasuredHeight() - getPaddingTop() - getPaddingBottom() - childLayout.topMargin - childLayout.bottomMargin;
int textViewHeight = textView.getMeasuredHeight();
if (textViewHeight < textHeightAvailable) {
return;
}
final int textWidthSpec = MeasureSpec.makeMeasureSpec(
MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight() - childLayout.leftMargin - childLayout.rightMargin,
MeasureSpec.EXACTLY);
final int textHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
for (float size = max_font_size; size >= 1.05f; size-=0.1f) {
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
textView.measure(textWidthSpec, textHeightSpec);
textViewHeight = textView.getMeasuredHeight();
if (textViewHeight <= textHeightAvailable) {
break;
}
}
}
}
And this in attrs.xml:
<declare-styleable name="ResizeView">
<attr name="maxFontSize" format="reference|dimension"/>
</declare-styleable>
And finally used like this:
<PACKAGE_NAME.ui.ResizeView xmlns:custom="http://schemas.android.com/apk/res/PACKAGE_NAME"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="start|center_vertical"
android:padding="5dp"
custom:maxFontSize="#dimen/normal_text">
<TextView android:id="#+id/tabTitle2"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</PACKAGE_NAME.ui.ResizeView>
Try this...
tv.setText("Give a very large text anc check , this xample is very usefull");
countLine=tv.getLineHeight();
System.out.println("LineCount " + countLine);
if (countLine>=40){
tv.setTextSize(15);
}
On a layout I want to scale the background image (keeping its aspect ratio) to the space allocated when the page gets created. Does anyone have an idea how to do this?
I am using layout.setBackgroundDrawable() and a BitmapDrawable() to set gravity for clipping and filling, but don't see any option for scaling.
To customize background image scaling create a resource like this:
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:gravity="center"
android:src="#drawable/list_bkgnd" />
Then it will be centered in the view if used as background. There are also other flags: http://developer.android.com/guide/topics/resources/drawable-resource.html
Haven't tried to do exactly what you want, but you can scale an ImageView using android:scaleType="fitXY"
and it will be sized to fit into whatever size you give the ImageView.
So you could create a FrameLayout for your layout, put the ImageView inside it, and then whatever other content you need in the FrameLayout as well w/ a transparent background.
<FrameLayout
android:layout_width="fill_parent" android:layout_height="fill_parent">
<ImageView
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:src="#drawable/back" android:scaleType="fitXY" />
<LinearLayout>your views</LinearLayout>
</FrameLayout>
There is an easy way to do this from the drawable:
your_drawable.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:drawable="#color/bg_color"/>
<item>
<bitmap
android:gravity="center|bottom|clip_vertical"
android:src="#drawable/your_image" />
</item>
</layer-list>
The only downside is that if there is not enough space, your image won't be fully shown, but it will be clipped, I couldn't find an way to do this directly from a drawable. But from the tests I did it works pretty well, and it doesn't clip that much of the image. You could play more with the gravity options.
Another way will be to just create an layout, where you will use an ImageView and set the scaleType to fitCenter.
Use image as background sized to layout:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ImageView
android:id="#+id/imgPlaylistItemBg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:maxHeight="0dp"
android:scaleType="fitXY"
android:src="#drawable/img_dsh" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
</LinearLayout>
</FrameLayout>
To keep the aspect ratio you have to use android:scaleType=fitCenter or fitStart etc. Using fitXY will not keep the original aspect ratio of the image!
Note this works only for images with a src attribute, not for the background image.
When you set the Drawable of an ImageView by using the setBackgroundDrawable method, the image will always be scaled. Parameters as adjustViewBounds or different ScaleTypes will just be ignored. The only solution to keep the aspect ratio I found, is to resize the ImageView after loading your drawable. Here is the code snippet I used:
// bmp is your Bitmap object
int imgHeight = bmp.getHeight();
int imgWidth = bmp.getWidth();
int containerHeight = imageView.getHeight();
int containerWidth = imageView.getWidth();
boolean ch2cw = containerHeight > containerWidth;
float h2w = (float) imgHeight / (float) imgWidth;
float newContainerHeight, newContainerWidth;
if (h2w > 1) {
// height is greater than width
if (ch2cw) {
newContainerWidth = (float) containerWidth;
newContainerHeight = newContainerWidth * h2w;
} else {
newContainerHeight = (float) containerHeight;
newContainerWidth = newContainerHeight / h2w;
}
} else {
// width is greater than height
if (ch2cw) {
newContainerWidth = (float) containerWidth;
newContainerHeight = newContainerWidth / h2w;
} else {
newContainerWidth = (float) containerHeight;
newContainerHeight = newContainerWidth * h2w;
}
}
Bitmap copy = Bitmap.createScaledBitmap(bmp, (int) newContainerWidth, (int) newContainerHeight, false);
imageView.setBackgroundDrawable(new BitmapDrawable(copy));
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
imageView.setLayoutParams(params);
imageView.setMaxHeight((int) newContainerHeight);
imageView.setMaxWidth((int) newContainerWidth);
In the code snippet above is bmp the Bitmap object that is to be shown and imageView is the ImageView object
An important thing to note is the change of the layout parameters. This is necessary because setMaxHeight and setMaxWidth will only make a difference if the width and height are defined to wrap the content, not to fill the parent. Fill parent on the other hand is the desired setting at the beginning, because otherwise containerWidth and containerHeight will both have values equal to 0.
So, in your layout file you will have something like this for your ImageView:
...
<ImageView android:id="#+id/my_image_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
...
This is not the most performant solution, but as somebody suggested instead of background you can create FrameLayout or RelativeLayout and use ImageView as pseudo background - other elements will be position simply above it:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
<ImageView
android:id="#+id/ivBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:scaleType="fitStart"
android:src="#drawable/menu_icon_exit" />
<Button
android:id="#+id/bSomeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="61dp"
android:layout_marginTop="122dp"
android:text="Button" />
</RelativeLayout>
The problem with ImageView is that only scaleTypes available are:
CENTER, CENTER_CROP, CENTER_INSIDE, FIT_CENTER,FIT_END, FIT_START, FIT_XY, MATRIX
(http://etcodehome.blogspot.de/2011/05/android-imageview-scaletype-samples.html)
and to "scale the background image (keeping its aspect ratio)" in some cases, when you want an image to fill the whole screen (for example background image) and aspect ratio of the screen is different than image's, the necessary scaleType is kind of TOP_CROP, because:
CENTER_CROP centers the scaled image instead of aligning the top edge to the top edge of the image view and FIT_START fits the screen height and not fill the width. And as user Anke noticed FIT_XY doesn't keep aspect ratio.
Gladly somebody has extended ImageView to support TOP_CROP
public class ImageViewScaleTypeTopCrop extends ImageView {
public ImageViewScaleTypeTopCrop(Context context) {
super(context);
setup();
}
public ImageViewScaleTypeTopCrop(Context context, AttributeSet attrs) {
super(context, attrs);
setup();
}
public ImageViewScaleTypeTopCrop(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setup();
}
private void setup() {
setScaleType(ScaleType.MATRIX);
}
#Override
protected boolean setFrame(int frameLeft, int frameTop, int frameRight, int frameBottom) {
float frameWidth = frameRight - frameLeft;
float frameHeight = frameBottom - frameTop;
if (getDrawable() != null) {
Matrix matrix = getImageMatrix();
float scaleFactor, scaleFactorWidth, scaleFactorHeight;
scaleFactorWidth = (float) frameWidth / (float) getDrawable().getIntrinsicWidth();
scaleFactorHeight = (float) frameHeight / (float) getDrawable().getIntrinsicHeight();
if (scaleFactorHeight > scaleFactorWidth) {
scaleFactor = scaleFactorHeight;
} else {
scaleFactor = scaleFactorWidth;
}
matrix.setScale(scaleFactor, scaleFactor, 0, 0);
setImageMatrix(matrix);
}
return super.setFrame(frameLeft, frameTop, frameRight, frameBottom);
}
}
https://stackoverflow.com/a/14815588/2075875
Now IMHO would be perfect if somebody wrote custom Drawable which scales image like that. Then it could be used as background parameter.
Reflog suggests to prescale drawable before using it. Here is instruction how to do it:
Java (Android): How to scale a drawable without Bitmap?
Although it has disadvantage, that upscaled drawable/bitmap will use more RAM, while scaling on the fly used by ImageView doesn't require more memory. Advantage could be less processor load.
The Below code make the bitmap perfectly with same size of the imageview. Get the bitmap image height and width and then calculate the new height and width with the help of imageview's parameters. That give you required image with best aspect ratio.
int bwidth=bitMap1.getWidth();
int bheight=bitMap1.getHeight();
int swidth=imageView_location.getWidth();
int sheight=imageView_location.getHeight();
new_width=swidth;
new_height = (int) Math.floor((double) bheight *( (double) new_width / (double) bwidth));
Bitmap newbitMap = Bitmap.createScaledBitmap(bitMap1,new_width,new_height, true);
imageView_location.setImageBitmap(newbitMap)
What Dweebo proposed works. But in my humble opinion it is unnecessary.
A background drawable scales well by itself. The view should have fixed width and height, like in the following example:
< RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#android:color/black">
<LinearLayout
android:layout_width="500dip"
android:layout_height="450dip"
android:layout_centerInParent="true"
android:background="#drawable/my_drawable"
android:orientation="vertical"
android:padding="30dip"
>
...
</LinearLayout>
< / RelativeLayout>
One option to try is to put the image in the drawable-nodpi folder and set background of a layout to the drawable resource id.
This definitely works with scaling down, I haven't tested with scaling up though.
Kotlin:
If you needed to draw a bitmap in a View, scaled to FIT.
You can do the proper calculations to set bm the height equal to the container and adjust width, in the case bm width to height ratio is less than container width to height ratio, or the inverse in the opposite scenario.
Images:
// binding.fragPhotoEditDrawCont is the RelativeLayout where is your view
// bm is the Bitmap
val ch = binding.fragPhotoEditDrawCont.height
val cw = binding.fragPhotoEditDrawCont.width
val bh = bm.height
val bw = bm.width
val rc = cw.toFloat() / ch.toFloat()
val rb = bw.toFloat() / bh.toFloat()
if (rb < rc) {
// Bitmap Width to Height ratio is less than Container ratio
// Means, bitmap should pin top and bottom, and have some space on sides.
// _____ ___
// container = |_____| bm = |___|
val bmHeight = ch - 4 //4 for container border
val bmWidth = rb * bmHeight //new width is bm_ratio * bm_height
binding.fragPhotoEditDraw.layoutParams = RelativeLayout.LayoutParams(bmWidth.toInt(), bmHeight)
}
else {
val bmWidth = cw - 4 //4 for container border
val bmHeight = 1f/rb * cw
binding.fragPhotoEditDraw.layoutParams = RelativeLayout.LayoutParams(bmWidth, bmHeight.toInt())
}
you'll have to pre-scale that drawable before you use it as a background
You can use one of following:
android:gravity="fill_horizontal|clip_vertical"
Or
android:gravity="fill_vertical|clip_horizontal"