Positioning Bitmaps on different screens - android

I know there are numerous other threads about this. I've been checking through them with no avail for the past 2 days and right now it's driving me nuts.
So basically I have a spritesheet, which I load into a Bitmap, cut the frames apart, perform scaling on them and load them into a list for later animation. Scaling looks good ranging from ldpi to xhdpi, so does animation.
But there's another thing that bugs me, and that's the positioning on different devices. I draw them with canvas.drawBitmap(bitmap, srcRect, destRect, paint). Well, I guess code tells more than thousand words.
//converting dps to pixels
int xCoord = convertToPixels(100);
int yCoord = convertToPixels(150);
//defining destination rectangle; sprite is a Bitmap object
Rect destRect = new Rect(xCoord, yCoord, xCoord+sprite.getWidth(), yCoord+sprite.getHeight());
//drawing
canvas.drawBitmap(sprite, null, destRect, null);
convertToPixels(float dp) just returns a number of pixels with formula Math.round(dp*DENSITY).
I believe that if I specify dps, the aspect ratio on all devices should be the same, e.g. if I put something on 10% of the screen size of one device, it should maintain those 10% on another smaller/bigger/less dense/more dense screen. But I guess my logic is flawed, because it doesn't work. It draws sprites on different devices on different places.
To summarize: drawing a Bitmap object with destRect with x and y coords 100 dps should maintain ratio on all devices with respect to canvas size or I ought to think this way.
I would kindly ask you to help me with this matter, for I'm lost currently lost, more literally than figuratively.
And please don't just give me a link to "Supporting Multiple Screens" topic on developer site or similar sites, because I've read them many times and yet it seems I don't understand them. Thank you!

Are you sure the dip to px calculation is correct? I wrote the following code to do this:
public class ViewHelper {
public static int getPxFromDip(int dips){
return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dips, Application.getContext().getResources().getDisplayMetrics());
}
}
Where Application.getContext() is the Application wide context, or provide the local Activity context.

Related

Drawing circles on canvas so they are equal or at least comparable sizes on different devices

I have an app that currently needs to draw some circles on a surfaceview's canvas.
Everything works fine, however I'm no understanding how to scale the circles correctly on different devices.
What I mean by this is that on my Galaxy S4, the circles are an appropriate size.
When I load the same app on my wife's Galaxy S3, the circles are huge in comparison.
I'm sure this is due to screen resolution and density, but I'm struggling with how to take the screen resolution/density into account to scale the circles correctly depending on the phone size.
Example of how a circle is drawn in my app. crcl.radius is currently a fixed size of 100. I know this 100 needs to be scaled based on the device but I can't wrap my head around the formula to do so. I'm assuming the conversion is relatively simple.
Disclaimer: somewhat new to Android (about 6 months using it).
myCanvas.drawCircle(crcl.x, crcl.y, crcl.radius, selected_paint);
Thank you in advance for any help you can give.
You can use a dp value instead of pixel. I think this should do the trick.
myCanvas.drawCircle(crcl.x, crcl.y, dipToPixels(getApplicationContext(),crcl.radius), selected_paint);
This function converts dp to pixel.
public static float dipToPixels(Context context, float dipValue) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dipValue, metrics);
}

strange behaviour of live wallpaper in preview [duplicate]

I am learning how to make live wallpapers, but I have a dilemma I'm sure all who start off have as well.
There is so many resolution screen sizes, how can I just make one set of artwork to be rescaled in code for all versions? I know it's been done as I seen the images in the apk's on a lot of them and they get rescaled.
If it was just one image that did not need any positioning that would be easy, but my problem is I have to get the background image rescaled to fit all devices, I also have animations that fit in a certain x and y position on that background image to fit in place so it looks like the whole background is being animated but only parts of it is (my way of staying away from 300 images of frame by frame live wallpapers).
So the background image needs to be rescaled and the animations need to be rescaled as well to the exact percentage as the background image and they need to sit in a specific x and y position.
Any help would be appreciated so I can get this going.
I tired a few things, figured I would make a scaler for everything example: int scaler; then in onSurfaceChanged scaler = width /1024; //if the bigger image is 1024. that will give me a ratio to work with everywhere. then scale accordingly using scaleBitmap by multiplying the scaler by the image height and width, and also use the same scaler for positioning example image x lets say is at 50, scale it using the same thing x = scaler * 50; that should take care of scaling and positioning, just how to translate all this into java is the next lesson, since I'm new to java, I used to program for flash and php but this is a lot different, take some getting used to. Next thing is how to pan the width, when you move your screen from side to side how to make the image show is the next puzzle I have figure out. Right now it just shows the same width no matter what even though the width is double what the surface shows. If you got an answer or somewhere I can find out the info on this one that would be greatly appreciated.
Well, um, all I can say is "Welcome to the real world." You get your screen dimensions passed to you via onSurfaceChanged, and yes, it is your job to figure out how to scale everything based on this data. That's why they pay us the big bucks. :-)
You will want to make sure your resources are large enough to fit the biggest display you intend to support, so you will always be shrinking things (which distorts much less than expanding things).
Suggest starting with "best practices for screen independence" here: http://developer.android.com/guide/practices/screens_support.html
Additional comments in re your request for more help...
You cannot (necessarily) scale your artwork just using the width, because you need to support multiple aspect ratios. If the screen proportions do not match your artwork, you must decide if you want to distort your artwork, leave blank spaces, etc.
I'm not sure how to interpret your trouble passing around the screen dimensions. Most of us put all of our active code within a single engine class, so our methods can share data via private variables. For example, in the Cube wallpaper in the SDK, onSurfaceChanged() sets mCenterX for later use in drawCube(). I suggest beginning with a similar, simple approach.
Handling scrolling takes some "intelligence" and a careful assessment of the data you receive via onOffsetsChanged(). xStep indicates how many screens your launcher supports. Normally xStep will be 0.25, indicating 5 screens (i.e. xOffset = 0, 0.25, 0.5, 0.75, or 1) but it can be any value from 0 to 1; 0.5 would indicate 3 screens. xPixels gives you an indication of how much the launcher "wants" you to shift your imagery based on the screen you're on; normally you should respect this. On my phone, the launcher "desires" a virtual wallpaper with twice the pixels of the physical screen, so each scroll is supposed to shift things only one quarter of one screen's pixels. All this, and more, is documented in http://developer.android.com/reference/android/app/WallpaperManager.html
This is not "easy" coding--apps are easier than wallpaper. :-)
Good luck...George
P.S. I'll throw in one more thing: somewhere along the line you might want to retrieve the "desired minimum width" of the wallpaper desired by the launcher, so you can explicitly understand the virtualization implicit in xPixels. For example, in my engine constructor, I have
mContext = getApplicationContext();
mWM = WallpaperManager.getInstance(mContext);
mDW = mWM.getDesiredMinimumWidth();
My device has 320 pixel width; I get mDW = 640; as I scroll from screen to screen, xPixels changes by 80 each time...because four scrolls (across five screens) is supposed to double the amount of revealed artwork (this effect is called "parallax scrolling"). The rightmost section has xPixels equals 0; the center (of five) sections has xPixels = -160, etc.
I've used this code snippet to scale one image to fit on different screen sizes.
Bitmap image1, pic1;
image1 = BitmapFactory.decodeResource(getResources(), R.drawable.image1);
float xScale = (float) canvas.getWidth() / image1.getWidth();
float yScale = (float) canvas.getHeight() / image1.getHeight();
float scale = Math.max(xScale, yScale); //selects the larger size to grow the images by
//scale = (float) (scale*1.1); //this allows for ensuring the image covers the whole screen.
scaledWidth = scale * image1.getWidth();
scaledHeight = scale * image1.getHeight();
pic1 = Bitmap.createScaledBitmap(image1, (int)scaledWidth, (int)scaledHeight, true);
Make sure that the edges don't contain vital information as it will be scaled out of the picture on some screen ratios.

Best way to resize and position bitmaps in a canvas so they look the same in different devices?

I am developing a game using a surface panel. I've done a lot of research about how to properly scale and position drawables in the canvas for multiple devices and I came up with a solution that is working fine on phones but has some flaws when I try it on tablets. I am aware that I can use different resources for tablets (and i might end up doing that) but let's assume for now that I don't want to do it, I want to use the same resources for every single different phone in the market.
All the resources that I have are located it in the hdpi folder, and they are properly sized for a 480x800 device.
My approach is similar to the one described here, please take a look on the explanation below, and I would like to know if there is a better solution for this problem!
I have a Galaxy S2 for testing my apps. So my first approach was to manually insert position everything directly in the canvas by trying and finding the best position for everything. Taking the first character position as an example:
draw_x = (float) (19);
draw_y = (float) (279);
canvas.drawBitmap(toDrawBitmap, draw_x, draw_y, null);
When I first tested it in different devices, everything as a mess, out of scale. So digging around I thought about using the density for scaling the resources.
// I am dividing by 1.5 because my initial positions are on a high density device
// so when it goes for a medium density it should scale for 0.66 and a small density
// for 0.5 of my positions.
float scale = getResources().getDisplayMetrics().density /1.5;
draw_x = (float) (19) * scale;
draw_y = (float) (279) * scale;
canvas.drawBitmap(toDrawBitmap, draw_x, draw_y, null);
And at first impression this worked like a charm. It all my characters were in the proper positions. But I noticed that if the device has a different scale widht/height compared to the Galaxy S2 that I am using the problems begin. Although everything was properly positioned part of the image was cut out of the screen, the canvas was calculated larger than the phone screen.
Galaxy S2 is 480x800. My background is also 480x800. When I tested it in the emulator on a small screen resolution 320x480 Android didn't scale my background correctly as I expected it to do so. Instead of scaling it for the right resolution it gave me a background larger than my canvas 320x533.
With some simple math we figure that 320x533 / 480x800 = 0.66. So instead of properly scaling the background in the canvas, it just scaled using the density of the devices.
So my workaround for this problem was the simplest I could think of. I know the resolution of my background, I know the resolution of the phone, so I can calculate the proportion I need and force a resize.
//Set the proportions for scaling in multiple devices
public void setProportions(float screenWidth,float ScreenHeight,Bitmap background){
this.heightProportion = ScreenHeight/background.getHeight();
this.widthProportion = screenWidth/background.getWidth();
}
public Bitmap scaleBitmaps(Bitmap bitmap) {
Bitmap out = Bitmap.createScaledBitmap(bitmap, (int) (bitmap.getWidth() * widthProportion),
(int) (bitmap.getHeight()*heightProportion), false);
return out;
}
That worked fine for the sizes of the drawables, so I just needed to do the same for the positions, using the scale and this new Proportion I was able to calculate using a fixed size background image
public float convertX(float x){
return x* scale * widthProportion;
}
public float convertY(float y){
return y* scale * heightProportion;
}
//calculate the positions applying the scale and the proportion
draw_x = convertX((float) (19));
draw_y = convertY((float) (279));
//draw the bitmap with the scaled position
canvas.drawBitmap(toDrawBitmap, draw_x, draw_y, null);
Long story short, to properly position the drawables I manually set the desired position in my device, calculated a scale between the densities and a porportion between the background image size and the screen size.
To re-size the drawables I just used the proportion because android automatically applies the density scale.
I tested in several different phones and tablets and this approach works perfectly for phones. On tablets it gives me some minor mistakes in the re-sizing of the drawables.
Finally after all this, my question is, what is the problem with this solution? Can I make it work on every phone regardless of the size or there is a better solution for this?
Please note that this strict to Canvas. The same background is re-sized correctly for every phone if I use it in the XML layout. If I wasn't clear or I should give more information please let me know!
The first thing you have to know before solve this problem is about device phone running system. Though you suggest the phone will choose either hdpi or other versions, it depends on each phone running system.
A. Size fitting problem
The problem is how do you process the bitmap. Though you re-scale the bitmap with any math formula, the size of original bitmap will have different output for each different phone. To solve this, you have to set inScaled of bitmap to false.
B. Position fitting problem
Thus you have the problem in fitting the size of bitmap, the position of bitmap will synchronize the position depends on your phone screen size. You should define the background object and positioning the object x and y based on the background. For example if you want to put an object in the middle of phone screen no matter what phone we use it, the code must be `
canvas.drawBitmap(toDrawBitmap, background.getwidth()/2, background.getheight()/2, null);
to solve the fitting position problem.
Let me know what happen.

android live wallpaper rescaling

I am learning how to make live wallpapers, but I have a dilemma I'm sure all who start off have as well.
There is so many resolution screen sizes, how can I just make one set of artwork to be rescaled in code for all versions? I know it's been done as I seen the images in the apk's on a lot of them and they get rescaled.
If it was just one image that did not need any positioning that would be easy, but my problem is I have to get the background image rescaled to fit all devices, I also have animations that fit in a certain x and y position on that background image to fit in place so it looks like the whole background is being animated but only parts of it is (my way of staying away from 300 images of frame by frame live wallpapers).
So the background image needs to be rescaled and the animations need to be rescaled as well to the exact percentage as the background image and they need to sit in a specific x and y position.
Any help would be appreciated so I can get this going.
I tired a few things, figured I would make a scaler for everything example: int scaler; then in onSurfaceChanged scaler = width /1024; //if the bigger image is 1024. that will give me a ratio to work with everywhere. then scale accordingly using scaleBitmap by multiplying the scaler by the image height and width, and also use the same scaler for positioning example image x lets say is at 50, scale it using the same thing x = scaler * 50; that should take care of scaling and positioning, just how to translate all this into java is the next lesson, since I'm new to java, I used to program for flash and php but this is a lot different, take some getting used to. Next thing is how to pan the width, when you move your screen from side to side how to make the image show is the next puzzle I have figure out. Right now it just shows the same width no matter what even though the width is double what the surface shows. If you got an answer or somewhere I can find out the info on this one that would be greatly appreciated.
Well, um, all I can say is "Welcome to the real world." You get your screen dimensions passed to you via onSurfaceChanged, and yes, it is your job to figure out how to scale everything based on this data. That's why they pay us the big bucks. :-)
You will want to make sure your resources are large enough to fit the biggest display you intend to support, so you will always be shrinking things (which distorts much less than expanding things).
Suggest starting with "best practices for screen independence" here: http://developer.android.com/guide/practices/screens_support.html
Additional comments in re your request for more help...
You cannot (necessarily) scale your artwork just using the width, because you need to support multiple aspect ratios. If the screen proportions do not match your artwork, you must decide if you want to distort your artwork, leave blank spaces, etc.
I'm not sure how to interpret your trouble passing around the screen dimensions. Most of us put all of our active code within a single engine class, so our methods can share data via private variables. For example, in the Cube wallpaper in the SDK, onSurfaceChanged() sets mCenterX for later use in drawCube(). I suggest beginning with a similar, simple approach.
Handling scrolling takes some "intelligence" and a careful assessment of the data you receive via onOffsetsChanged(). xStep indicates how many screens your launcher supports. Normally xStep will be 0.25, indicating 5 screens (i.e. xOffset = 0, 0.25, 0.5, 0.75, or 1) but it can be any value from 0 to 1; 0.5 would indicate 3 screens. xPixels gives you an indication of how much the launcher "wants" you to shift your imagery based on the screen you're on; normally you should respect this. On my phone, the launcher "desires" a virtual wallpaper with twice the pixels of the physical screen, so each scroll is supposed to shift things only one quarter of one screen's pixels. All this, and more, is documented in http://developer.android.com/reference/android/app/WallpaperManager.html
This is not "easy" coding--apps are easier than wallpaper. :-)
Good luck...George
P.S. I'll throw in one more thing: somewhere along the line you might want to retrieve the "desired minimum width" of the wallpaper desired by the launcher, so you can explicitly understand the virtualization implicit in xPixels. For example, in my engine constructor, I have
mContext = getApplicationContext();
mWM = WallpaperManager.getInstance(mContext);
mDW = mWM.getDesiredMinimumWidth();
My device has 320 pixel width; I get mDW = 640; as I scroll from screen to screen, xPixels changes by 80 each time...because four scrolls (across five screens) is supposed to double the amount of revealed artwork (this effect is called "parallax scrolling"). The rightmost section has xPixels equals 0; the center (of five) sections has xPixels = -160, etc.
I've used this code snippet to scale one image to fit on different screen sizes.
Bitmap image1, pic1;
image1 = BitmapFactory.decodeResource(getResources(), R.drawable.image1);
float xScale = (float) canvas.getWidth() / image1.getWidth();
float yScale = (float) canvas.getHeight() / image1.getHeight();
float scale = Math.max(xScale, yScale); //selects the larger size to grow the images by
//scale = (float) (scale*1.1); //this allows for ensuring the image covers the whole screen.
scaledWidth = scale * image1.getWidth();
scaledHeight = scale * image1.getHeight();
pic1 = Bitmap.createScaledBitmap(image1, (int)scaledWidth, (int)scaledHeight, true);
Make sure that the edges don't contain vital information as it will be scaled out of the picture on some screen ratios.

Android - determine specific locations (X,Y coordinates) on a Bitmap on different resolutions?

My app that I am trying to create is a board game. It will have one bitmap as the board and pieces that will move to different locations on the board. The general design of the board is square, has a certain number of columns and rows and has a border for looks. Think of a chess board or scrabble board.
Before using bitmaps, I first created the board and boarder by manually drawing it - drawLine & drawRect. I decided how many pixels in width the border would be based on the screen width and height passed in on "onSizeChanged". The remaining screen I divided by the number of columns or rows I needed.
For examples sake, let's say the screen dimensions are 102 x 102.
I may have chosen to set the border at 1 and set the number of rows & columns at 10. That would leave 100 x 100 left (reduced by two to account for the top & bottom border, as well as left/right border). Then with columns and rows set to 10, that would leave 10 pixels left for both height and width.
No matter what screen size is passed in, I store exactly how many pixels in width the boarder is and the height & width of each square on the board. I know exactly what location on the screen to move the pieces to based on a simple formula and I know exactly what cell a user touched to make a move.
Now how does that work with bitmaps? Meaning, if I create 3 different background bitmaps, once for each density, won't they still be resized to fit each devices screen resolution, because from what I read there were not just 3 screen resolutions, but 5 and now with tablets - even more. If I or Android scales the bitmaps up or down to fit the current devices screen size, how will I know how wide the border is scaled to and the dimensions of each square in order to figure out where to move a piece or calculate where a player touched. So far the examples I have looked at just show how to scale the overall bitmap and get the overall bitmaps width and height. But, I don't see how to tell how many pixels wide or tall each part of the board would be after it was scaled. When I draw each line and rectangle myself based in the screen dimensions from onSizeChanged, I always know these dimensions.
If anyone has any sample code or a URL to point me to that I can a read about this with bitmaps, I would appreciate it.
BTW, here is some sample code (very simplified) on how I know the dimensions of my game board (border and squares) no matter the screen size. Now I just need to know how to do this with the board as a bitmap that gets scaled to any screen size.
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
intScreenWidth = w;
intScreenHeight = h;
// Set Border width - my real code changes this value based on the dimensions of w
// and h that are passed in. In other words bigger screens get a slightly larger
// border.
intOuterBorder = 1;
/** Reserve part of the board for the boardgame and part for player controls & score
My real code forces this to be square, but this is good enough to get the point
across.
**/
floatBoardHeight = intScreenHeight / 4 * 3;
// My real code actually causes floatCellWidth and floatCellHeight to
// be equal (Square).
floatCellWidth = (intScreenWidth - intOuterBorder * 2 ) / intNumColumns;
floatCellHeight = (floatBoardHeight - intOuterBorder * 2) / intNumRows;
super.onSizeChanged(w, h, oldw, oldh);
}
I think I found the answer. I might not be able to find the exact width/height and location of each playable square within a single scaled bitmap, but by looking at the Snake example in the SDK, I see it doesn't create 1 bitmap for the entire board and scale it based on the screen dimensions - instead it creates a bitmap for each tile and then scales the tile based on the screen resolution and the number of tiles wanted on the screen - just like I do when I draw the board manually. With this method, I should be able find the exact pixel boundaries for all of the playable squares on the board. I just have to break the board into multiple bitmaps for each square. I probably will have to do a similar approach for the borders, so I can detect their width/height as well after scaling.
Now I will test it to verify, but I expect it to work based on what I saw in the Snake SDK example.
--Mike
I tested a way to do what I was asking and it seems to work. Here is what I did:
I created a 320 x 320 bitmap for a board. It was made up of a border and squares (like a chess board). The border was 10 pixels in width all the way around the board. The squares were 20 x 20 pixels.
I detected the width and height of the screen through onSizeChanged. On a 480 x 800 display, I would set the new width for the board to be 480 x 480 and use the following code to scale the whole thing:
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
floatBoardWidth = w;
floatBoardHeight = floatBoardWidth;
bitmapScaledBoard = Bitmap.createScaledBitmap(bitmapBoard, (int)floatBoardWidth, (int)floatBoardHeight, true);
super.onSizeChanged(w, h, oldw, oldh);
}
Now in order to detect how many pixels wide the border was scaled to and how many pixels in height & width the squares were scaled to, I first calculated how much the over all image was scaled. I knew the bitmap was 320 x 320, since I created it. I used the following formula to calculate how much the image was scaled:
floatBoardScale = floatScreenWidth / 320;
In the case of a 480 width screen, floatBoardScale equals: 1.5. Then to calculate what my border within the full bitmap was scaled to, I did:
floatBorderWidth = 10 * floatBoardScale;
10 was the original border width in my 320 x 320 bitmap. In my final code I won't hardcode values, I will use variables. Anyway, in the case of this formula, the new calculated border width should be: 15
When I multiplied the same scale factor to the board squares (that were 20 x 20 in the original bitmap) I got new values of 30 x 30. When I used those values in my formulas to calculate what square a person touched, it worked. I touched every corner of the squares and in the center and it always calculated the right location. Which is important, so no matter what the screen resolution, I know where the user wanted to move a piece and visually it shows up in the right location.
I hope this helps anyone who may have had the same question. Also, if anyone has a better method of accomplishing the same thing, please post it.
A couple things. First, start reading about how to support multiple screens. Pay close attention to learning about dips and how they work.
Next, watch this video (at least the first 15-20 minutes of it).
This subject isn't a cakewalk to grasp. I found it best to start playing around inside my code. I would suggest creating a surfaceview and start messing around with some bitmaps, different emulators (screen sizes and densities), and the different types of drawable folders.
Unfortunately, there is more to this topic than I think Google wants to admit, and while it's definitely do-able is isn't simple to get started on it for some types of applications.
Finally, you should consider boiling down your question to be more straight forward if you aren't looking for an abstract answer (like this one).
Good luck!

Categories

Resources