I have a frame layout encompassing an imageview that's supposed to update the image it's showing when the user presses the next button (think of a slideshow). All the images that I want to thusly put in this imageview are of the same dimensions. The initial image that's shown by the imageview at the start of the activity is in the res/drawable-hdpi directory, while the other images live in the assets directory. The problem is that the images that I swap into the imageview appear smaller than the first image although all these images are of the same size.
With regard to layout parameters, my frame layout's layout_width and layout_height are set to wrap_content and so is the imageview, I tried setting the scaleType parameter to every possible value and I still see the same results. So unless the images stored in res/drawable-hdpi and assets are somehow scaled differently this behavior shouldn't happen.
So, what's the best approach to preserve the dimensions of the imageview as the user cycles through these images?
Here is my XML for the frame layout & my imageview
<FrameLayout
android:id="#+id/coverLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" >
<ImageView
android:id="#+id/coverImageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="false"
android:scaleType="fitCenter"
android:src="#drawable/coverImage" />
</FrameLayout>
Here is my code that loads new images into my imageview
_coverImageView = (ImageView) findViewById(R.id.coverImageView);
...
...
if (loadNewImage) {
AssetManager assetMgr = getAssets();
InputStream stream;
try {
stream = assetMgr.open("covers/" + _coverImageName);
Drawable coverDrawable = Drawable.createFromStream(stream, _coverImageName);
_coverImageView.setImageDrawable(coverDrawable);
}
catch (IOException ex) {
System.out.println(ex);
}
}
So unless the images stored in res/drawable-hdpi and assets are somehow scaled differently this behavior shouldn't happen.
This is exactly the issue. Android scales bitmaps based on several rules including the density (class) of the screen and which resource folder the bitmap was from. See this related question/answer. If you want the same behavior for both assets and resources, you can put the initial cover image in drawable-nodpi instead of drawable-hdpi.
Related
In my code I want to set a default image for my imageview. All my images are of 174px X 174px. So, I want the default image to be same size. This is my xml for image view
//....code
<ImageView
android:id="#+id/ivCover"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:contentDescription="Default Image"
android:src="#drawable/default_cover" />
//....some other code
Now, my default_cover.jpg is also 174px X 174px. But it shows a smaller image. I've tried all
android:scaleType="centerInside"
android:scaleType="center"
android:scaleType="matrix"
even when most of them were senseless for me but still I tried all of 'em & none of them works. Then I specified the height & width of image by specifying
android:layout_width="174px"
android:layout_height="174px"
(I know using dp is better than px but because this image size is fixed & I was just testing it). So it shows
[default image]
while when loaded from an external url using
Bitmap bitmap = BitmapFactory.decodeStream((InputStream) new URL(url_source_for_image).getContent());
ivCover.setImageBitmap(bitmap);
desired image
[]
The two pictures may not look very different here but the difference is very clear on my android device. And please don't say that I didn't try other solutions..
Here is a list of links of so itself..
ImageView fit without stretching the image
Fit image into ImageView, keep aspect ratio and then resize ImageView to image dimensions?
ImageView one dimension to fit free space and second evaluate to keep aspect ration
Resizing ImageView to fit to aspect ratio
Maximum width and height for ImageView in Android
Image in ImageView is stretched - Android
Android: How to prevent image from being scaled in ImageView or ImageButton?
Sounds like the answer is to use drawable-nodpi. For future reference, if you have the same named image in both the ldpi/hdpi/etc and in no-dpi, I think the no-dpi one will be ignored.
My app has an Activity that displays a vertically scrolling list of ImageButtons. I want each buttons image to (A) come from the assets folder and (B) retain it's aspect ratio as it scales. Unfortunately, my ImageButton doesn't size correctly when it's image comes from the assets folder.
ImageButton src set from drawable
The first screenshot is my test app where the images all come from my apps drawables. That's the "correct" aspect ratio for that image, which is what I want to keep (all of the image buttons have been given scaleType "fitXY" "centerCrop").
ImageButton src set from assets
The second screenshot is my test app where the images all come from my apps "assets" folder — as you can see, the images are stretched out to the full width of the screen as desired, but the original aspect ratio has been lost:
Activity code (MainActivity.java)
LinearLayout buttons = (LinearLayout) findViewById(R.id.buttons);
View button = layoutInflater.inflate(R.layout.snippet_imagebutton, null);
ImageButton imageButton = (ImageButton) button.findViewById(R.id.imageButton);
if (GET_IMAGE_FROM_ASSETS) {
InputStream s = assetManager.open("image.png");
Bitmap bmp = BitmapFactory.decodeStream(s);
imageButton.setImageBitmap(bmp);
} else {
imageButton.setImageResource(R.drawable.image);
}
TextView buttonText = (TextView) button.findViewById(R.id.textView);
buttonText.setText("Button text!");
buttons.addView(button,
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
Button layout (snippet_imagebutton.xml)
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp">
<ImageButton
android:id="#+id/imageButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" />
<TextView
android:id="#+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:gravity="center"
android:textColor="#FFF"
android:background="#88000000"
android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>
Dodgy hack
I've found a dodgy hack which achieves what I want and illustrates the problem — the ImageButton will size correctly to match a scaled up image from the resources, but it won't size correctly to match a scaled up image from the assets. I'd prefer a "real" solution over the dodgy hack if there is one. :)
// to start with, scale the ImageButton based on an image in our
// resources that has the same dimensions as the image in our assets
imageButton.setImageResource(R.drawable.image);
// when we eventually render (we don't have width / height yet)...
imageButton.post(new Runnable() {
public void run() {
int height = imageButton.getHeight();
int width = imageButton.getWidth();
// replace our "resource" image with our "asset" image
imageButton.setImageBitmap(bmp);
// and force the ImageButton to keep the same scale as previously
imageButton.setLayoutParams(
new RelativeLayout.LayoutParams(width, height));
}
});
Summary
I want to get the images for these buttons from my apps "assets" folder. How do I fix my app so that the button images are all scaled properly (i.e. retain their original aspect ratio)?
I'm assuming this has something to do with the framework not actually knowing the width / height of the image before it renders the ImageButton to screen — so how do I fix that? I tried setting adjustViewBounds to "true" on both the RelativeLayout and the ImageButton itself, but that didn't seem to have any effect.
The aspect ratio is not kept when you give the scale type attribute as "fitXY". Try "center", "center_crop" or "center_inside" according to your needs. Check ImageViewScaleType as well.
From what I gather, AdjustViewBounds is actually the method that provides the exact functionality you need. However, as Roman Nurik explains here, AdjustViewBounds does NOT increase the ViewBounds beyond the natural dimensions of the drawable.
Without seeing how you allocated resources/assets, my suspicion would be that this is why you see a difference when loading from Resources and when loading from Assets. Your resource file is - presumably - being scaled up when you load it (since that is the default behavior for resources), so that the actual size of the drawable returned from /res is significantly larger than the bitmap you decode from /assets.
Overriding ImageButton (as Nurik suggests), is of course one option, but probably overkill.
Assuming I am correct about the problem, you should be able to fix it simply by setting your BitmapFactory.options correctly when you load the Assets file. You need to set inDensity and inTargetDensity correctly according to your device (get DisplayMetrics), and set inScaled to true.
Alternatively, you can always rescale the bitmap manually before loading it on the ImageButton. Just grab the screen width to determine how large you need to make it.
P.S.
Auto-scaling is obviously the elegant solution, but it does have a weakness (which applies equally to both resources and assets) - if the width of your display > bitmap witdh when scaled up, you'll likely still have the aspect ratio messed up. You can check this pretty easily by going to landscape mode and seeing whether the resource file still result in the right aspect ratio. If you have a large enough base image (or use different layouts according to the screen size/orientation), then this should not be a problem though. Just something to keep in mind.
I have developed this custom ImageView class to override some of the default behavior to fit my needs. Let me describe what this custom ImageView does...
Let's say you have a bunch of icons to display in GridView both in the drawable-mdpi and drawable-hdpi folder, they are 48x48px and 72x72px in size, respectively. There are no icons available in the drawable-xhdpi folder. The GridView attributes are so that all the icons size will be in 48x48dp (this will translate to 48px, 72px and 96px for mpdi, hdpi and xhdpi densities, respectively).
Since there are no icons in the drawable-xhdpi folder, when this app is ran on a device with such density, the icons will be pulled from the drawable-hdpi folder. And since they are only 72px and the xhdpi devices are expecting 96px images, the icons will be stretched to fill the remaining pixels.
This is the behavior my custom ImageView attempts to override. With my custom component, what will happen is that the images will simply not get stretched. For instance, in the example above using my class, each ImageView inside the GridView will still be 96x96px (because of the 48x48dp size defined) but the images used are from the drawable-hdpi folder which are 72x72px. What will happen is that these images from the drawable-hdpi folder will be placed in the center of the ImageView which is 96x96px in size without stretching the image to fit the whole view size.
If the above is confusing, let's try with a few pictures. The example below does not use GridView, I'm trying to simplify the idea behind my custom class. These are the source pictures I'm using for this example:
This is the result on HDPI device:
And this is the result on XHDPI device:
The code for the layout on the screenshots above is this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Standard ImageView:"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_margin="10dp"
android:scaleType="center"
android:background="#FFEEEE"
android:src="#drawable/ic_female"/>
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_margin="10dp"
android:scaleType="center"
android:background="#FFEEEE"
android:src="#drawable/ic_male"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Custom UnscaledImageView:"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<com.sampleapp.widget.UnscaledImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_margin="10dp"
android:scaleType="center"
android:background="#FFEEEE"
android:src="#drawable/ic_female"/>
<com.sampleapp.widget.UnscaledImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_margin="10dp"
android:scaleType="center"
android:background="#FFEEEE"
android:src="#drawable/ic_male"/>
</LinearLayout>
Is it more clear now? This is what I want to do and this is working nicely, besides a small performance issue... Now let me post the code I'm using for such custom component:
attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="UnscaledImageView">
<attr name="android:src" />
</declare-styleable>
</resources>
UnscaledImageView.java:
public class UnscaledImageView extends ImageView {
private int mDeviceDensityDpi;
public UnscaledImageView(Context context) {
super(context);
mDeviceDensityDpi = getResources().getDisplayMetrics().densityDpi;
}
public UnscaledImageView(Context context, AttributeSet attrs) {
super(context, attrs);
mDeviceDensityDpi = getResources().getDisplayMetrics().densityDpi;
TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.UnscaledImageView);
int resourceId = styledAttrs.getResourceId(R.styleable.UnscaledImageView_android_src, 0);
if(resourceId != 0) {
setUnscaledImageResource(resourceId);
}
styledAttrs.recycle();
}
public void setUnscaledImageResource(int resId) {
setImageBitmap(decodeBitmapResource(resId));
}
#SuppressWarnings("deprecation")
public void setUnscaledBackgroundResource(int resId) {
BitmapDrawable drawable = new BitmapDrawable(null, decodeBitmapResource(resId));
drawable.setTargetDensity(mDeviceDensityDpi);
drawable.setGravity(Gravity.CENTER);
setBackgroundDrawable(drawable);
}
private Bitmap decodeBitmapResource(int resId) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inDensity = mDeviceDensityDpi;
return BitmapFactory.decodeResource(getResources(), resId, options);
}
}
So, this class will do it's thing if the UnscaledImageView view is used in XML layouts or directly initialized in code. I've also provided 2 methods so the image can be changed in code while keeping it from being stretched. As you can see, these methods only take resource ids, so far I haven't felt the need to use drawables or bitmaps directly.
Now the real issue I'm having with this...
If this class is used as single image view in some layout, no problem, it's only decoding one image. But if it's used in a GridView where there can be like 40 icons (I'm taking this value from what really happens on my app running on my xhdpi device) visible at the same time, scrolling the GridView will be very slow because the decodeBitmapResource() is calling BitmapFactory.decodeResource() for each and every image.
This is my problem and that is my question. How can I optimize this? If possible, at all...
Putting those images into the res/drawable-nodpi/ could do what you want (I'm saying to put different resolution images side by side).
It would be a bit tricky because probably you'd have to follow a naming convention to be able to find the best resource for a given image that you are trying to draw. Probably this will require you to try finding images by their name and that's not a very efficient way to retrieve resources.
The way I imagin this is: on the layout (or anywhere else), you specify the name (string, not id!) of the image resource you want to use.
In that nodpi folder, you'd have the images with a suffix for the intended screen density.
Then, in the setter method you have to try different combinations in order to find the best available resource.
Problem for you to think: what if you're scaling down an image? The resource would be bigger than the view where you'd draw it!
Although the answer by Pedro Loureiro could be a possible solution, I decided to take a different approach after realizing something...
I've timed both the native ImageView loading and my UnscaledImageView loading in a GridView, non-scientifically of course and I've realized that my class loads all the images a little bit faster than the native one. Maybe the native methods have something else going on, besides decoding the resource (they still have to do it, right?) while my class simply decodes a resource (using BitmapFactory) and that's basically it.
I thought that it was my class that was making the GridView kinda slow while scrolling but after a few more tests, using the original ImageView without any tweaks, also revealed to be a little choppy while scrolling the GrivView.
The solution I found to solve this issue (either with my class or the native one) was to cache the icons in the GridView and for that I used the LruCache which is the recommended way of caching images in a GridView.
So that's the solution I'll be using to solve my issue. For more details please refer to the official training guide: http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
For reference, I've also found the following tutorial useful: http://andrewbrobinson.com/2012/03/05/image-caching-in-android/
I load images from assets folder, but I have one problem.
The same image loaded from assets and resource are not same dimensions - resolution.
For getting image from assets I use this method:
AssetManager am=this.getAssets();
try {
Bitmap bmp = BitmapFactory.decodeStream(am.open("imgs/a.jpg"));
imageViewDraw.setImageBitmap(bmp);
} catch (IOException e) {
e.printStackTrace();
}
Output for the same image loaded from R.drawable and assets are on images
Can some one tell me why and how can I read image from assets, but resolution should be the same as from R.drawable ?
Thanks
If you are loading the bitmap to an Imageview use the scaleType and adjustViewbounds parameters. So for example in XML:
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="centerInside" />
The thing is that when you load images from the assets directory the system doesn't apply any scaling or processiong whatsoever to your bitmaps, instead when using the drawable folder, android tries to scale your image according to your device's resolution.
So the only real way to have the exact same result is to use a fixed size for the imageview, ideally you should specify it in dps and ideally you should put that value on a dimens.xml file so you can change that value for various system configurations
You need to use setDrawable instead with a scaled drawable.
imageView.setImageDrawable(new BitmapDrawable(getContext().getResources(),bitmap));
This is also why the other method that does not get the getResources() parameter is deprecated, it will take care of the scalings in this case.
Situtation:
I am working on a small application which should enable user to trigger something depending on which part of the screen they clicked. Imagine a picture of a teddy bear and if you click a nose it says "nose". What I have done is put in layout xml a LinearLayout. Because I have to cover different screen sizes I didn't set background in a xml layout file.
snippet form xml:
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/viewMain"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:visibility="visible" >
<LinearLayout
android:id="#+id/viewThouShaltRespondToClicks"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="true"
android:orientation="vertical"
android:longClickable="true"
android:visibility="visible" />
<!-- mask -->
<LinearLayout
android:id="#+id/viewInvisibleMask"
android:visibility="invisible"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
</LinearLayout>
</FrameLayout>
And then in onCreate event of activity I check the display resolution and via setBackgroundResource() set the appropriate background. I have prepared background images for each resolution.
final Point QVGA = new Point(240,320); // portrait
// Obtain the screen resolution if the device
Display defaultDisplay = ((WindowManager)this.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
Point displayResolution = new Point(defaultDisplay.getWidth(), defaultDisplay.getHeight());
if (displayResolution.equals(QVGA))
{
viewThouShaltRespondToClicks.setBackgroundResource(R.drawable.image_to_be_clicked_upon_240x320);
viewInvisibleMask.setBackgroundResource(R.drawable.mask_240x320);
}
else if ... // check for another resolution
Even if the image in the resources is not in the correct dimension it is stretched/shrinked to the screen size. This would be OK as long as the width-height ratio of background image is similar to ratio of display and the resulting image is not kewed to much. But there is a problem, explained below.
To detect which region was clicked, the region itself might be irregular shaped I have taken the following approach. I create an image – mask (bmp) with same dimensions as background image only that it has a white background and the clickable areas are in different colors. The color identifies the area. All I have to is get a coordinate of a click event (no problem here), go to the mask image and read the color of pixel on this coordinates. The problem is that the mask image is not of the correct size. On my device it is set to 1200x700, but I guess it takes on some arbitrary size on other devices.
First question: Is there a way to somehow convince the invisible layout to load background image and then stretch/shrink it to display size as it happens for visible layout by itself?
Another approach would be to load mask image (bmp, png) into some memory structure and resize it to display size.
I have tried with something like:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false; // do not scale
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
bitmapMask = BitmapFactory.decodeResource(this.getResources(), R.drawable.mask, options);
// on this place stretch shrink should follow but I have no idea how
But I don’t know how to scale Bitmap to proper size.
Any suggestions?
First question: yes, you can have a separate View in your layout containing the mask image, and set it to be invisible with android:visibility=invisible.
Second question: you can read a pixel value from the bitmap with Bitmap.getPixel(). Docs are here.
Just a simple suggestion. You could have the reference image in an imageview behind the real image. So both images are loaded in the exact same way, placed in ImageViews, and then inserted into a relativelayout. That way they should be equal size, and only one of them is visible.
This might not be a very pretty way of doing it, and i'm not sure if it will work, but you can try it out.