I'm drawing a bitmap on a SurfaceView using Canvas.drawBitmap(). I have the bitmap in the res/drawable folder and it seems to scale the bitmap correctly on all devices.
Some users have complained that when they change their device's screen density (using a rooted program called LCDDensity Changer) the bitmaps do not scale accordingly. A large bitmap will get squished as if it is still looking for the old density.
Is there a way to handle density changes when users use programs like LCD Density Changer?
Other info:
1. minSDKVersion=3
2. no targetSDKVersion is set
3. I use getMetrics() to determine screen height and width
4. I'm not using the drawable-hdpi, ldpi, etc folders
One approach,I think you need to use hdpi, ldpi etc., Here is an article on how to do that.Android densities
Second approach, you may get screen density prgormatically using DisplayMetrics class and have different images for each density. Here is SO discussion on get screen density
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
I had multiple problems here. I was targeting v1.5 and I've been using hardcoded constants for screen locations without including a bounding rectangle.
I'm going to:
1. Switch to 2.2 framework (which has better compatability with screen resolutions and densities
2. Use "drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)" rather than "drawBitmap(Bitmap bitmap, float left, float top, Paint paint)". This allows me to specify exactly where I want a graphic drawn and it will scale accordingly.
3. Start using the ldpi, hdpi, xhdpi, etc folders. I'm also considering just using the hdpi folder and allowing other devices to scale the images down. (I wouldn't use xhdpi because I read somewhere that it is only supported in gingerbread 2.3).
Related
There are so many material on densities, multiple screen support, so many questions on SO, but I still have not got it.
My goal is simple: display a bitmap as large as available space for ImageView. I need BitMap as I will do some operations on it.
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/figureView" />
I will have some pictures to be used in the bitmap. I will place them in drawables directory. I will reference them with R.drawable.pictureX. I will use Picasso to load them and scale them:
Bitmap bitmap = Picasso.with(getContext()).load(resourceId).resize(w, h).get();
The unclear part for me is all those xxhdpi folders and Android heuristic to select the best.
When a documentation says that Android will automatically scale the image does it apply to my case? I do not want to scale already scaled pictured.
How many dpi variants shall I store in my case and where? Shall I have single file in no-dpi folder or shall I create picture variant for each dpi folder?
How can I determine a dimension for picture resource? It is easy for icons: for example 24x24 dpi and then multiple it with DPI formula. But I want to cover complete screen height. A chapter Configuration examples lists: 240x320 ldpi, 320x480 mdpi, 480x800 hdpi, 720x1280 mdpi, 800x1280 mdpi etc. There are no screen size qualifiers for resources.
Thanks for clarification.
I realized that in fact it is easy to find out Android behavior. I just need each DPI variant different so I can distinguish between them. I put a text with DPI name and pixel resolution inside each picture.
There is a sample GitHub Test DPI project. It has two ImageViews. The first is initialized from XML and Android does scaling. The second is a placeholder that I fill with a BitMap. I entered 200dp as its size and that was correct. Pixel size of MDPI = size in dp.
What I found is logical. Android does not know anything about my intentions. It just selects the best available resource for current DPI.
Nexus 5x uses XXHDPI by default, Samsung SIII Mini uses HDPI. If I delete their default DPI folders, they down-scale higher available variant: XXXHDPI and XHDPI. If I delete all variants except NODPI, this will be used.
And finally if I change the dimensions of ImageView dynamically loaded from source code to 300dp, then it will be scaled and look ugly. Android cannot know at decodeResource execution that I will scale it later and that there is a better resource available.
What remains unknown is how to find out dimensions of picture that fits the screen.
Java:
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inScaled = false;
Bitmap figureBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_test, opt);
ImageView imageView=(ImageView) findViewById(R.id.testView);
imageView.setImageBitmap(figureBitmap);
Activity:
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_test" />
<ImageView
android:id="#+id/testView"
android:layout_width="200dp"
android:layout_height="200dp" />
Update 30th august
Original picture: 230x541 px, loading with
Picasso.with(getContext()).load(R.drawable.amalka).into(target)
stored in nodpi, loaded as 230x541 px Bitmap
stored in xxhdpi, loaded as 230x541 px Bitmap
1.Android will automatically scale image in case when you provided image for one screen dencity and there is no such image for another one. e.g. if you add your image only in drawable-xxxhdpi folder, Android will generate images for xxhdpi, xhdpi screen dencities etc.
2.You can save your image in one folder drawable-xxxhdpi and let Android make the downscale, if you don't like the result you can create images for different dencities on your own.
name dencity scale
mdpi 160dpi x1
hdpi 240dpi x1.5
xhdpi 320dpi x2
xxhdpi 480dpi x3
xxxhdpi 640dpi x4
3.You can define dimensions of your images based on size of you ImageView and dpi scale. For example if your ImageView has width 80dp and height 40dp then your image for drawable-mdpi folder should have size 80x40px because mdpi is a baseline dencity, for drawable-hdpi then you need to have image 120x60px (scale is 1.5) etc. For drawable-xxxhdpi your image will be 320x160px.
If you want your image to fit all the screen on all devices you can use parameter android:scaleType="centerCrop", it will make the image take all the space but little part of image can be hidden depending on screen aspect ratio. You can also try to specify image resorces based on shortest dimension of the available screen area, e.g. drawable-sw720dp. Read more about this here.
Is it possible to use common hdpi folder for all screen densities? I have quite a lot images. If I make specific copies to folders drawable-hdpi, drawable-ldpi, drawable-xhdpi, ... but it takes huge data (backgrounds, bitmaps).
Is it possible to set only one drawable folder for all devices and then rescale according to a specific device programmatically?
I think about this code to get display size of the scree:
Display display = getWindowManager().getDefaultDisplay();
width = display.getWidth();
height = display.getHeight();
Then I will get display density of the device, something like this:
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
density = metrics.density; // 1 - 1,5 - 2 .....
The I will recalculate size of imageview with density:
ImageView logo = (ImageView)findViewById(R.id.logo);
LinearLayout.LayoutParams logo1 = (LinearLayout.LayoutParams) logo.getLayoutParams();
logo1.width = (int)(logo.getWidth()*density);
logo1.height = (int)(logo.getHeight()*density);
logo1.leftMargin=(int)(logo1.leftMargin*density); // for margin
logo1.topMargin=(int)(logo1.topMargin*density); // for margin
logo1.rightMargin=(int)(logo1.rightMargin*density); // for margin
logo1.bottomMargin=(int)(logo1.bottomMargin*density); // for margin
My main problem is I need to have all proportions of graphic same on all devices. It means I must recalculate imageViews accroding to the screen size.
Is this a right way to get density independent screen? How does android work on other devices if only hdpi folder contains files. Does it take files from this folder? Can I set one common drawable folder to all densities?
I would strongly (strongly) advise against doing this. However, if you want the system to rescale your image assets, design them for mdpi (the baseline density) and put them in drawable/.
That said, you need at least mdpi and hdpi to get reasonable scaling (since hdpi is 1.5x mdpi, scaling algorithms produce worse results than for the other conversions from mdpi).
Make sure you've read and understood Providing Resources and Supporting Multiple Screens before you start dealing with resources.
P.S. The layout solution is wrong for a few reasons (e.g., setting margins instead of size) but it's also the completely wrong thing to do. Don't do it!
I need drawable resources for different resolutions, such as 320x480, 800x480, 854x480, 800x600
1024x600, 1024x768, 1024x800. It is a game and the scaling of bitmaps is inacceptable, coz they are very sensitive to it. dpi dependent folders and combination with screen sizes(which are deprecated but no other way to set drawable for large screens with same resolution and differend dpi) are not enaugh. How to distinguish graphics for 1024x600 and 1024x768 for example?
It is so sad to stop use mechanism of auto picking resources and switching to manual loading from assets.
any ideas?
I'm usually using 4 resource folders for drawables:
drawable-mdpi for 320x480
drawable-hdpi for 480x800 and 480x854
drawable-large-hdpi for 600x1024, 768x1024 and so on
drawable-xlarge-mdpi for 800x1280
These are just enough in my mind. Also, you don't need to worry about different drawable resources for, in example, devices with 800x480 and 854x480 screen sizes: you can specify an offset on the edges of your screen equal to 27 pixels and center your game on the screen. Hope this helps.
If you want a pixel perfect fit you could always load the background at runtime via a simple method:
private void setBackgroundImage() {
DisplayMetrics dm = getResources().getDisplayMetrics();
int screenWidth = dm.widthPixels;
int screenHeight = dm.heightPixels;
// TODO: Conditional Structure to apply the right drawable
// ressource to be applied in the background
}
After you've acquired the resolution of the display you can apply the appropriate background via the setBackgroundDrawable method.
I have read all the pages I could find about support multiple screens in android, including
Supporting Multiple Screens
Providing Resources
Screen Sizes and Densities
And many others. But I still don't understand what resources I should provide for it to correctly position drawables(sprites) on a Canvas.
It's my first time making a game and I am currently a game Whack a Mole. I am confused about how to use the ldpi, mdpi, and hdpi, folders and how to properly position sprites to draw over a SurfaceView canvas.
I have a background image, resolution 480x800, so I added it to my hdpi folder. Then I have 120x150 sprites of moles, that I should position correctly on the holes for that background.
Currently I am using the following code to draw it:
canvas.drawBitmap(toDrawBitmap, draw_x, draw_y, null);
draw_x and draw_y are pixels that I found trying to place them correctly:
So far everything is fine, they are correctly placed in my hdpi, 480x800 screen. And android re scales them correctly on the different resolutions.
But when I try to use a different resolution screen, they are all drawn in wrong places, out of the holes, some of them are even out of the screen.
Please correct me if I am wrong but for what I've read these three resolutions are the most common in phones:
240x320 small - ldpi
320x480 normal - mdpi
480x800 normal - hdpi
My goal is to make the game work properly in those three kinds of screen. So my question is:
Can I calculate a draw_x and draw_y value, that will work on all of the devices? If not, how do i solve this problem?
Yes you can calculate it using device width and height.
final Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
final int width = display.getWidth();
final int height = display.getHeight();
Should be a simple one.
When I pull image.getDrawable().getIntrinsicWidth() I get a value larger than the source image width. It's X coordinate is returning 2880 instead of 1920 which is 1.5 times too big?
I wondered wether the ImageView having a scaleType of "center" effected it but, according to the documentation:
"Center the image in the view, but perform no scaling."
Here is the source:
<ImageView
android:id="#+id/backgroundImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/background"
android:scaleType="center"/>
You said the drawable is from your /res folder. Which folder is it in?
/res/drawable
/res/drawable-mdpi
/res/drawable-hdpi
etc..
And what is the density of the device you are testing on? Is it a Nexus S with general density of 240dpi? Because if your source drawable is located in the drawable-mdpi folder and you are testing on a 240dpi device, then the Android system will automatically scale the drawable up by a factor of 1.5 so that the physical size will be consistent with the baseline device density at 160dpi.
When you call getIntrinsicWidth() what is returned is the size the drawable wants to be after Android scales the drawable. You'll notice that 2880 = 1920 * 1.5
If you placed the drawable in /res/drawable the Android system treats those drawables as meant for mdpi devices, thus the upscaling in your Nexus S. If this was meant for hdpi screens and you do not want this to upscale then try placing it in drawable-hdpi
Have you tried to multiply height and width by density:
getResources().getDisplayMetrics().density
Is your drawable in ressources or download from the web? If it is downloaded, you have to give it the density:
DisplayMetrics metrics = new DisplayMetrics();
getContext().getWindowManager().getDefaultDisplay().getMetrics(metrics);
Resources r = new Resources(getContext().getAssets(), metrics, null);
BitmapDrawable bmd = new BitmapDrawable(r, bitmap);
Check if it is returning the value of it's displayed size, which is not it's actual size. For example, a 50x320px banner ad on a traditional 800x480 phone displays as 75x480.
Should be able to compare against density (or your eyes!) to see what it is doing.
This is probably going to be nothing to with the issue you're having, but just for kicks I'll suggest it to be sure anyway: Are you specifying android:minSdkVersion in your manifest?
Only reason I mention this is because for a while I wasn't doing so in a project, and I learned that this screws the screen density up and caused all sorts of strange problems.