I'm trying to dynamically build a layout, so it scales and looks more or less identical on all screen sizes and resolutions. I do this by programmatically placing my views inside a RelativeLayout and scaling everything according to a certain factor.
I noticed something strange when moving an ImageView around though, the bigger the values on margins get, the smaller the ImageView gets. This gets really annoying when trying to get things at right place and size.
private void initializeObjects() {
Display display = getWindowManager().getDefaultDisplay();
Point screenSize = new Point();
display.getSize(screenSize);
float scale = screenSize.x / ( (screenSize.x < screenSize.y) ? 480 : 800 ); // Scale according to Galaxy S II resolution
RelativeLayout screenLayout = (RelativeLayout) findViewById(R.id.main_layout);
mRobotView = new ImageView(getApplicationContext());
mRobotView.setImageResource(R.drawable.soomla_logo_new); // a 743 x 720 png
mRobotView.setScaleX(scale * 100 / 743); // Make sure the image is 100 "units" in size
mRobotView.setScaleY(scale * 100 / 720);
mRobotView.setPivotX(scale * 100 / 743); // reset the image origin according to scale
mRobotView.setPivotY(scale * 100 / 720);
RelativeLayout.LayoutParams robotParams = new RelativeLayout.LayoutParams(743, 720);
robotParams.leftMargin = 0xDEADBEEF; // The bigger these get the smaller the view gets
robotParams.topMargin = 0xDEADBEEF;
screenLayout.addView(mRobotView, robotParams);
}
Any idea what's causing this?
Use
android:layout_height="720px"
android:layout_height="743px"
in the main_layout.xml to have the image not shrinked
Related
Super simple question: what is the relationship of font size to screen resolution on adroid? AVD? E.g. if resolution width is 100px and a TextView has 10 monospaced chars in it, if text size is 10px, can i guarantee that the Text will occupy the full screen width?
My specific problem is that I need to scale 10 characters of text, using textSize, to use the full screen width (using textSize alone). If screen resolution is 100 x 100 and I want 10 characters to fully occupy the screen horizontally, why can't I just use:
DisplayMetrics metrics = getResources().getDisplayMetrics();
float pixelFontSize = metrics.widthPixels / 10f;
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, pixelFontSize);
? In my testing, I get text that is way to small (uses about 1/3 the resolution) on a 280 x 280 wtih 280 dpi AVD, yet on another AVD 360 x 330 hdpi, the code above produce text way too big to fit on one line. ...I mean, sure I could see needing to subtract a small amount from the pixcelFontSize to account for text layout, but my results are all over the place. I must be missing something. First I was messing with density and the scaled measurement dp and sp, but logic would say that given my problem, density has nothing to do with it at all, right?
EDIT, to further show the problem, here is my solution to scale the text to fit the display, using font size, for 7 or 8 AVD's all wear, square round and chin
switch (metrics.widthPixels) {
case 280: //280 dpi
resolutionMultiplier = .4f;
ideoResolutionMultiplier = 0f;
break;
case 360: //hdpi
resolutionMultiplier = .5f;
ideoResolutionMultiplier = .0f;
break;
case 320: //works for round hdpi, chin tvdpi, but the res 320 square 280 dpi too tall....
if(metrics.xdpi == 280) {
resolutionMultiplier = .45f;
ideoResolutionMultiplier = 0f;
}
else {
resolutionMultiplier = .47f;
ideoResolutionMultiplier = 0f;
}
break;
case 400: //280 dpi
resolutionMultiplier = .55f;
ideoResolutionMultiplier = 0f;
break;
case 480: //360 dpi
resolutionMultiplier = .71f;
ideoResolutionMultiplier = 0f;
break;
}
if (mWzTheme.mWordArrays != null
&& mWzTheme.mWordArrays.isIdeographic()) {
pixelFontSize = (metrics.widthPixels / new Integer(Constants.MAX_LINE_LENGTH_CHINESE_WATCH).floatValue())
+ (new Integer(Constants.MAX_LINE_LENGTH_WATCH).floatValue() * ideoResolutionMultiplier);
}
else {
pixelFontSize = (metrics.widthPixels / new Integer(Constants.MAX_LINE_LENGTH_WATCH).floatValue())
+ (new Integer(Constants.MAX_LINE_LENGTH_WATCH).floatValue() * resolutionMultiplier);
}
Try this library:
https://github.com/AndroidDeveloperLB/AutoFitTextView
This will fit the text to the screen exactly. (Not sure if there were any issues with Android Wear target.)
Here is a simple solution:
Rect bounds = new Rect();
Paint textPaint = textView.getPaint();
textPaint.getTextBounds(text,0,text.length(),bounds);
int height = bounds.height();
int width = bounds.width();
...but still need a guessing algorithm to go from screen resolution to the font size required to fill the screen with x characters...
So I have been experimenting today with making an Android Application, but I have tried the LineairLayout to make a welcom screen for my application, but I cannot get it right..
So I tried RelativeLayout and I saw I can move my ImageViews and buttons to everywhere. So my question is if I will move the items to places like center, bottom left and bottom right. Would this be a problem or all phones since not all phones have the same dimensions?
public class WelcomeActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.welcome_screen_relative);
final ImageView logo = (ImageView) findViewById(R.id.myImageView);
DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
int displayHeight = metrics.heightPixels;
int displayWidth = metrics.widthPixels;
float scaledDensity = metrics.scaledDensity;
BitmapFactory.Options dimensions = new BitmapFactory.Options();
dimensions.inJustDecodeBounds = true;
Bitmap mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.log, dimensions);
int imageHeight = dimensions.outHeight;
int imageWidth = dimensions.outWidth;
float percentageToMoveViewDown = (float) 20.0;
float viewY_float = (float) ((displayHeight / 100.0) * percentageToMoveViewDown);
int viewY_int = Math.round(viewY_float);
RelativeLayout.LayoutParams view_Layout_params = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
view_Layout_params.topMargin = viewY_int;
logo.setLayoutParams(view_Layout_params);
logo.getLayoutParams().height = imageHeight;
logo.getLayoutParams().width = imageWidth;
}
Thats depends. If you give objects a fixed size of course it will. for dp/dpi make sure to test it in Emu or real devices. You can also create density and orientation specific layout to support many screens. Consider that there are not only changes in size but also aspect ration and resolution and DPI.
For most apps RelativeLayout is might be the right approach.
You can read an excelent article about it here: http://developer.android.com/guide/practices/screens_support.html
If the items have fixed sizes you always will have trouble with some phones. For the big ones it may be too small, for the small ones too big...
In my experience Androids small/normal/large screens won't help you much for configuring, since the differences are just too big.
If you want to make sure everything sits where it belongs to, you could get the device metrics. That way you don't even need to rely on center, but you can work with percentages to place everything where you want it to be. Plus you can set the sizes in percentage, which is great. Like you could say I want a button thats width is 50% of the screen, no matter how large the screen is. Its more work (maybe even overkill), but I really like that approach. Once you figured it out its basically just a bit copy paste at the start of your classes.
Example:
DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
int displayHeight = metrics.heightPixels;
int displayWidth = metrics.widthPixels;
float scaledDensity = metrics.scaledDensity;
//move some View 20% down:
float percentageToMoveViewDown = (float) 20.0;
float viewY_float = (float) ((displayHeight / 100.0) * percentageToMoveViewDown);
int viewY_int = Math.round(viewY_float);
RelativeLayout.LayoutParams view_Layout_params = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
view_Layout_params.topMargin = viewY_int;
view.setLayoutParams(view_Layout_params);
//even works with text size for a TextView:
float percentageToResizeTextViewTextSize = (float) 3.1;
float textViewTextSize_float = (float) ((displayHeight / 100.0) * percentageToResizeTextViewTextSize);
int textViewTextSize_int = Math.round(textViewTextSize_float / scaledDensity);
textView.setTextSize(textViewTextSize_int);
Just a side note for the overkill thing: This should be only necessary if you want to support small devices (they mostly run something like android 2.3, but still are sold as budget phones) and big devices as well, but the trouble with the big ones is not as big as the trouble with the small ones. I personally rather put more effort in it than less, you never know.
Edit: ImageView by code
The easiest way is to do it hybridly, using xml and code. Note that you will have to change width and height if you set it to 0 in xml like in the following example.
Just place it in the xml where you would anyways, somewhere in your RelativeLayout.
In your xml:
<ImageView
android:id="#+id/myImageView"
android:layout_width="0dp"
android:layout_height="0dp" />
In your Code:
ImageView myImageView = (ImageView)findViewById(R.id.myImageView);
You now can work with that myImageView as I did it with view and textView. You can even set the image right here in code.
This imageView with the size of 0,0 is now placed where it would have been before. Now you could set the width to like 50% of the screenwidth and the height to...lets say 40% of the screen height. Then You would need to place it. If you want to center it you know that there must be 25% of the screen on each side, so you can add 25% as left and right margin.
Edit 2: maintain original imagesize
If you want to keep the original size of a image in your drawables, you can get its width and height like this:
BitmapFactory.Options dimensions = new BitmapFactory.Options();
dimensions.inJustDecodeBounds = true;
Bitmap mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.yourImageName, dimensions);
int imageHeight = dimensions.outHeight;
int imageWidth = dimensions.outWidth;
Now that you have that, you could use it to calculate the aspect ratio to keep it.
Now since you know the devices width and height, you can easily calculate how much of the screen this image will need.(imageheight/screenheight*100 = your percentage of the screen you want the imageviews height to be). So the Height you set to the imageview would be displayHeight / 100 * (imageHeight / displayHeight * 100).
How to place that?
Now if you take a Screenheight of 100 and a imageheight of 80 you get 80%. You would now take this percentage and divide it from 100. Divide that /2 and you know how much space you would have as top and bottom margins if you wanted it to be placed in the middle of the screen (you would have to do the same for width).
Caution: If you don't want it to be relative to the screensize but the original size, that percentage approach is kind of pointless. If you do to your image what I just described, it may still be too big for small devices and too small for big ones. Instead you could think about what percentage would look good in proportion to the rest of the stuff on the screen and resize it to that, since it would have that relative size on all devices.
Edit 3:
Since you load the image in original size, it will be small on big devices if it is a small image.
//first you need to know your aspect ratio.
float ratio = imageWidth / imageHeight;
//lets say you wanted the image to be 50% of the screen:
float percentageToResizeImageTo = (float) 50.0;
float imageX_float = (float) ((displayHeight / 100.0) * percentageToResizeImageTo);
int imageX_int = Math.round(imageX_float);
//now you know how much 50% of the screen is
imageWidth = imageX_int;
imageHeight = imageWidth * ratio;
RelativeLayout.LayoutParams view_Layout_params = new RelativeLayout.LayoutParams(imageHeight, imageWidth);
view_Layout_params.topMargin = viewY_int;
logo.setLayoutParams(view_Layout_params);
logo.setScaleType(ImageView.ScaleType.Fit_XY);
I want my RelativeLayouts be at the same size and position independently of the size of the device screen (relatively). For example:
If device A has a screen width of 480 and height of 800 and device B has a screen width of 720 and height of 1280, my RelativeLayout must have, for example, 160 width and 267 height in device A and 240 width and 427 height in device B. It's simple, just get the screen width and height and calculate the percent you want. This part I did with this code below and it's working:
int width = methodToGetScreenWidth();
relativeOneWidth = ((width / 100) * 90); // 90% of the screen width.
relativeOneHeight = (int) (relativeOneWidth / 1.75f);
relativeTwoWidth = (relativeOneWidth / 2);
relativeTwoHeight = (relativeOneHeight / 4);
relativeThreeWidth = (relativeOneWidth / 4);
relativeThreeHeight = (relativeOneHeight / 4);
It's working fine in all devices I tested. The problem is the position. I tried a lot of things to calculate the margins, but it didn't be at the same position in every devices (most times it's very close to the desired position, but not the exactly). Here's what I tried:
relativeOneLeftMargin = ((width - relativeOneWidth) / 2); // The screen width - the relativeOne width / 2, it should returns the relativeOne left margin, shouldn't it?
relativeOneTopMargin = ((height - relativeOneHeight) / 2); // And this, the relativeOne top margin, right?
relativeTwoLeftMargin = (int) (relativeOneLeftMargin * 1.5f);
relativeTwoTopMargin = (int) (relativeOneTopMargin * 0.78f);
relativeThreeLeftMargin = relativeOneTopMargin + relativeOneHeight;
P.S.: The relativeOne is in the middle of the screen:
relativeOne.addRule(RelativeLayout.CENTER_IN_PARENT);
I also tried to get the margins this way, but it returns 0:
relativeOne.getLeft();
relativeOne.getTop();
I really don't know why it's not in the same position in all devices. Any suggestion?
First, when using positioning in different devices, it is recommended to use DP (device-dependent points). You can see how to use them at http://developer.android.com/guide/practices/screens_support.html
When you use:
relativeOne.getLeft();
relativeOne.getTop();
You will always get 0, as, at the moment you make this call, Android did not finish drawing the parent of your layout, thus it has no information about its relative positioning. If you want to has access to this information, you have to make the call after the drawing. One way to do this is by using method post of class View:
parent.post(new Runnable() {
#Override
public void run() {
relativeOne.getLeft();
relativeOne.getTop();
}
});
In this way, you execute the call after the layout has been drawn.
I changed this code:
relativeTwoLeftMargin = (int) (relativeOneLeftMargin * 1.5f);
relativeTwoTopMargin = (int) (relativeOneTopMargin * 0.78f);
To this one:
relativeTwoLeftMargin = Math.round(relativeOneLeftMargin * 1.5f);
relativeTwoTopMargin = Math.round(relativeOneTopMargin * 0.78f);
Now it's working. I guess the int cast did not return the exactly value.
I have countdown functionality in my app.
Every second a timer sets the current time to a TextView.
So strings are: "56:05","56:04","56:03","56:02"...
I want to make the text size as big as possible
Therefore I've written the following code.
private void measureAndSetText(String text) {
Paint pMeasure = new Paint();
Integer iWidth = _tvContent.getWidth();
Float maxTextSize = 1000f;
pMeasure.setTextSize(maxTextSize);
pMeasure.setFakeBoldText(true);
Float fCurrentWidth = pMeasure.measureText(text);
while (fCurrentWidth > iWidth) {
pMeasure.setTextSize(maxTextSize -= 1);
fCurrentWidth = pMeasure.measureText(text);
}
_tvContent.setText(text);
_tvContent.setTextSize(TypedValue.COMPLEX_UNIT_PX, maxTextSize);
}
This code seems to work on my Note2, Lg Optimus 4x HD, Galay ACE and some others.
But not on my Xperia Z in landscape mode.
I guess the reason is the Full Hd Display but I don't understand why.
On the Xperia Z just the ":" sign is displayed in landscape mode. So I think the text is wrapped but I don't know why.
It would make sense to me if the text size I set to it is higher than the screen height (in landscape mode this is actually screen width -> 1080) but this isn't the case.
When I try to set a longer text -> "asdfasd asdf" it is correctly displayed.
Can someone point me to the problem?
Cheers,
Stefan
UPDATE:
I figured out that my iWidth variable which holds the width of my TextView (_tvContent.getWidth()) has the value 1770.
How can that be since my Xperia Z has just 1920x1080???
So I thought that it may be a failure in TextView.getWidth() and added the following code:
Display display3 = getWindowManager().getDefaultDisplay();
Point size3 = new Point();
display3.getSize(size3);
int width = size3.x;
Display display1 = getWindowManager().getDefaultDisplay();
int width1 = display1.getWidth(); // deprecated
int height1 = display1.getHeight(); // deprecated
Display display = getWindowManager().getDefaultDisplay();
Integer iWidth = display.getWidth(); // deprecated
int height = display.getHeight(); // deprecated
Results:
Point(1080, 1776)
Display id 0: DisplayInfo{"Integrierter Bildschirm", app 1080 x 1776, real 1080 x 1920, largest app 1794 x 1701, smallest app 1080 x 1005, 60.0 fps, rotation 0, density 480, 442.451 x 443.345 dpi, layerStack 0, type BUILT_IN, address null, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS}, DisplayMetrics{density=3.0, width=1080, height=1776, scaledDensity=3.0, xdpi=442.451, ydpi=443.345}, isValid=true
Is this a Bug on site of Sony?
After I implemented my own TextView with its own OnDraw I've seen that there is an error in LogCat: ERROR/OpenGLRenderer(2503): Font size to large to fit in cache.
After a short research I figgured out that this is really a Bug in Android.
Addding this line to my custom TextView did the trick for me:
this.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
(This is somewhat a follow-up on Android: How do you scale multiple views together?)
My task is to port an iPhone / iPad app on Android that consists of a simple image view on which animations are layered on top in absolute positions. While this sounds rather easy on iOS where you only have to target a few possible screen sizes, it gets rather messy with Android.
My current setup is this: A RelativeLayout in which I place my main (background) image on left = 0, top = 0 and multiple ViewFlipper instances used as "animation containers" that are positioned relatively to the upper left corner of the parent layout instance.
This approach has two basic problems:
The positioned "animations" are mis-positioned as soon as the actual size of the layout does not match the size of the main background image.
The positioned "animations" are also mis-sized, because since they usually have "enough space" around themselves, Android doesn't scale them to fit into the RelativeLayout (nor would it scale them relatively to the original background.
Since the animations itself must be interactive, its not a solution to place and position all of the animations on a transparent layer that has the same size as the main (background) image, as they'd overlap each other and only the upper-most would be interactive at all.
I thought of different solutions:
To get the the scale factor of the main image, I could retrieve its measuredWidth and measuredHeight and set this into relation of the original width and height of the view. Then I'd use this scale factor for custom positioning and eventually custom scaling. But, apparently the measuredWidth/-Height properties are only set during the onMeasure() call and this is called after the component tree was built, so I don't know if this solution is feasible at all.
Implement my own layout manager and scale / position the views accordingly. I had a look at the implementation of RelativeLayout, but have to admit that the onMeasure() method scares me a bit.
What would you do in my case? Is there anything I haven't yet taken into account?
Thanks in advance.
Well, answering my own question - here is the way I resolved the issue:
I placed the background image on the top of my ImageView with ImageView.setScaleType(ScaleType.FIT_START)
I calculated the scale factor of my background image like so:
WindowManager mgr = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics = new DisplayMetrics();
mgr.getDefaultDisplay().getMetrics(metrics);
Drawable image = context.getResources().getDrawable(R.drawables.someImage);
float scale = metrics.widthPixels / (float) image.getIntrinsicWidth();
Finally, I used this scale in a custom ImageView class that loads the overlays to position and scale the view properly:
public class OverlayImage extends ImageView
{
private int imgWidth, imgHeight;
private final float scale;
public OverlayImage(Context context, int xPos, int yPos, float scale)
{
super(context);
this.scale = scale;
LayoutParams animParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
animParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
animParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
animParams.leftMargin = (int) (scale * xPos);
animParams.topMargin = (int) (scale * yPos);
setLayoutParams(animParams);
Drawable dr = context.getResources().getDrawable(R.id.someImage);
setBackgroundDrawable(dr);
imgWidth = dr.getIntrinsicWidth();
imgHeight = dr.getIntrinsicHeight();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
setMeasuredDimension((int) (scale * imgWidth),
(int) (scale * imgHeight));
}
}
I lately needed to do something similar, i also had to port a IPad app to android, the screen had many images that had to be in specific locations.
I solved this slightly differently, absolute layout, and run through all the views and set the coordinated and size of each.
//This gets the scale of the screen change:
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
Drawable image = getResources().getDrawable(R.drawable.background_image);
float scaleW = displaymetrics.widthPixels / (float)image.getIntrinsicWidth();
float scaleH = displaymetrics.heightPixels / (float)image.getIntrinsicHeight();
//And this scales each view accordingly:
for(int i = 0; i < mainLayout.getChildCount(); i++)
{
View v = mainLayout.getChildAt(i);
v.setLayoutParams(new AbsoluteLayout.LayoutParams(
Math.round(scaleW * v.getMeasuredWidth()),
Math.round(scaleH * v.getMeasuredHeight()),
Math.round(scaleW * ((AbsoluteLayout.LayoutParams)v.getLayoutParams()).x),
Math.round(scaleH * ((AbsoluteLayout.LayoutParams)v.getLayoutParams()).y)));
}