getDimension()/getDimensionPixelSize() - mutliplier issue - android

So I have android 2.3.5 device which is NORMAL/HDPI. I have a dimens.xml in my project:
...
<dimen name="gameresult_congrats_label_msg_textSize">20sp</dimen>
...
this file is absolutely identical in values-normal/values-hdpi and so on folders.
In my first activity app shows me that value using:
Toast.makeText(this, "textSize is "+getResources().getDimensionPixelSize(R.dimen.gameresult_congrats_label_msg_textSize), Toast.LENGTH_SHORT).show();
and it displays 30. I Tried also:
Toast.makeText(this, "textSize is "+getResources().getDimension(R.dimen.gameresult_congrats_label_msg_textSize), Toast.LENGTH_SHORT).show();
but result is the same. But only when I tried this:
Toast.makeText(this, "textSize is "+getResources().getString(R.dimen.gameresult_congrats_label_msg_textSize), Toast.LENGTH_SHORT).show();
I got my "20sp" finally! But why is that? Official docs says that those methods returns
Resource dimension value multiplied by the appropriate metric.
I checked this by changing my value to 25 and I got 38 which means aos uses 1.5 multiplier. But why? It already gets value from appropriate folder which means it gets a ready to use value! From where aos gets that 1.5x multiplier? I know it depends on DisplayMetrics. But how it calculates 1.5x?
UPDATE
I understand about multiplier but, you see, the real problem here is about double scaling. And thats why I did asked this question.
So if I have some layout.xml (in res\layout folder) with TexView defined like:
<TextView
android:id="#+id/congratsLabel"
...
android:textSize="#dimen/gameresult_congrats_label_msg_textSize" />
Everything looks ok. I mean textview is like Im expecting.
Now lets do the same in code:
TextView congratsLabel = fineViewById(R.id.congratsLabel);
textSize = getResources().getDimension(R.dimen.gameresult_congrats_label_msg_textSize)
congratsLabel.setTextSize(textSize)
and here is the issue!!! getResources().getDimension() returns a SCALED value and thats ok. But the resulting size of my textView will be 1.5 greater than I expecting cuz setTextSize works with SP and here comes the second scale! And thats why AOS makes resulting text size scaled to 45 (originally defined as 20sp).

Just to clarify (information obtained by inspecting Android source code):
Resources.getDimension() and getDimensionPixelOffset()/getDimensionPixelSize() differ only in that the former returns float while the latter two return the same value rounded to int appropriately. For all of them, the return value is in raw pixels.
All three functions are implemented by calling Resources.getValue() and converting the thus obtained TypedValue by calling TypedValue.complexToDimension(), TypedValue.complexToDimensionPixelOffset() and TypedValue.complexToDimensionPixelSize(), respectively.
Therefore, if you want to obtain the "raw" value together with the unit specified in the XML source, call Resources.getValue() and use the methods of the TypedValue class.

Method getDimension() converts dp or sp values into pixels based on current screen density. This is very useful as you don't have to do it on your own, when you want to set in Java width or text size (they accepts only pixels).
But if you need original sp or dp you could do "reverse engineering".
Step 1. Check current scale ratio (based on screen density):
float scaleRatio = getResources().getDisplayMetrics().density;
Step 2. Get dimension from dimens.xml
float dimenPix = getResources().getDimension(R.dimen.your_dimen_name);
Step 3. Do some math
float dimenOrginal = dimenPix/scaleRatio;
Remarks:
usually you need int for dimension methods (like setWidth()), so you have to convert float result to int for instance using Math.round()
more accurate result when rounding to int you could get using such formula (dimenPix-0.5f)/scaleRatio
in case of sp you could take also into account user preferences about text scale
Read more about dimensions in Android: http://android4beginners.com/2013/07/appendix-c-everything-about-sizes-and-dimensions-in-android/

Per the Supporting Different Screen Densities training, hdpi is 1.5x normal (mdpi) sizes. As getDimensionPixelSize takes this difference into account when converting into pixels, the returned value will be 1.5x your value in sp.
Note that sp is also dependent on the user's preferred text size and can therefore change to be even larger than 1.5x your expected value.

If someone else needs this :
To address the double scaling problem Stan show when using getDimensionPixelSize with TextView.setTextSize :
You can use the alternate version of setTextSize where you can specify the unit like this :
title.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimensionPixelSize(R.dimen.title));

Because... NORMAL is NOT hdpi...Normal is mdpi (160dpi) = 1.0x.hdpi (240dpi) is 1.5x.xhdpi (320dpi) is 2.0x.xxdpi (480dpi) is 3.0x.xxxhdpi (640dpi) is 4.0x.And (last, but not least) ldpi (120dpi) is 0.75x.

Related

Jetpack Compose: Why is LocalConfiguration.current.screenWidthDp an Int and not a Float?

It seems having LocalConfiguration.current.screenWidthDp be an Int makes conversions from dp to pixels less accurate.
For example, for a device with density 420 and width in pixels of 1080, the width in dp = 1080 * 160/420 = 411.4285714.
However, if you calculate from dp to pixels using an Int of 411 or 412, then the pixels are 1078.875 or 1081.5. You can't do a simple round to get 1080 from these numbers.
Do most use cases prefer that LocalConfiguration.current.screenWidthDp be an Int?
LocalConfiguration.current is a CompositionLocal that exposes the platform's Configuration. In simple terms, you can think of it as an parameter that's automatically passed to each of your composables, which the runtime knows about in order to recompose when it changes. In that sense, this value is an int because it represents the Configuration's screenWidthDp int value.
The reason it's represented as an int at the platform level is because it will always be a positive integer value since that's what's used for determining the right resources to use in a given configuration. In other words, resource qualifiers deal with integer values, so the configuration exposes integer values.
If you're looking to do something like drawing a line the full width of a component, you'd work in pixels the whole time. Density independent pixels don't really make sense for that case because you don't care about the physical size of the line. Compare that to the case of something like a button where the physical size does matter since it needs to be roughly finger size or larger (~48dp+).
In cases where you do care about the physical size, you'd start with dp and convert to pixels when you need to do the actual drawing (typically just using something like 16.dp.toPx())

Set text size in .xml or programmatically

I have variable at dimens.xml
<resources>
<dimen name="btn_text_size">12sp</dimen>
</resources>
And i can use it in layout file:
<TextView
android:textSize="#dimen/btn_text_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/dialog_tags_complete"
/>
or programmatically
tagButton.setTextSize(c.getResources().getDimension(R.dimen.tag_text_size));
But this 2 methods give different results. I know that getDimension are based on the current DisplayMetrics associated with the resources.
But what should i do to make this 2 ways looks the same?
setTextSize( float ) expects a scaled pixel value. So, setTextSize( 12 ) would give you the desired result. However, getDimension() and getDimensionPixelSize() return the size in units of pixels, so you need to use the unit-typed variant of setTextSize() as follows:
setTextSize( TypedValue.COMPLEX_UNIT_PX, getDimensionPixelSize( R.dimen.tag_text_size ) );
tagButton.setTextSize(c.getResources().getDimensionPixelSize(R.dimen.tag_text_size));
this will work just fine :)
You should also remember that textView has a setTextSize(int unit,float size), which should be used while setting size from code but not from xml dimen.
I have currently the same thing. Did set a dimension in dimens.xml and applied it programmatically, which is 3 times that big, than when settings via xml.
I checked also:
TextView.getTextSize() = 92f
getResources().getDimension(R.dimen ...) = 92f
TextView.setTextSize(92) != TextView with size from XML, other flags like TypedValue.COMPLEX_UNIT_PX make it even bigger.
The default setTextSize does apply COMPLEX_UNIT_SP by default btw. So once again, the Android API is inconsistent, so setting programmatically only and adapt sizes, so they fit, will be my solution.
Edit: Setting text size programmatically under Galaxy Note 2 (4.4.2) vs Note 4 (5.0.1) leads to a totally different result -.-

Why is my TextView padding unpredictable in pixels

I have extended the TextView and added support for borders, the thing is when I am drawing a border I need to put padding on the bordered side, so that the text would move.
I set my widths of borders in pixels, and it draws them accordingly, but on my TF201 tablet when I setPadding on the TextView, out of some reason it multiplies the padding width by 3x in pixels even though the setpadding documentation says it is defined explicitly in pixels.
EDIT:
Even though the answer I have selected is not what was causing my issue, it is a valid answer. The real answer to my question is actually a duplicate from this. Problem was that I have added a value to my padding each time setPadding was called. And it does get called three times on a page that has scrolling to it.
It might be a issue of pixel density. Its true that setpadding docs asks to set the padding in pixels but are you setting it in px, sp or dp ? If you read Supporting Different Densities document it says and I quote:
Different screens have different pixel densities,so the same number of pixels may correspond to different physical sizes on different devices.
So, when you specify spacing between two views, use dp rather than px:
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/clickme"
android:layout_marginTop="20dp" />
When specifying text size, always use sp:
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp" />
Also, based on your comments:
drawRect unit issues android andDraw Rectangle which change size w.r.t different android screen size question might help.
While the method may only accept pixel values, that sadly doesn't save you from needing to take screen densities into account. Instead, you need to determine your values in terms of DP and then programmatically calculate the pixel equivalents at runtime. Fortunately, there are some built-in methods to help you out. This can be done with the following code:
/// Converts 14 dip into its equivalent px
int dimensionInDp = 14;
Resources r = getResources();
float dimensionInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dimensionInDp, r.getDisplayMetrics());
Although the result is a float, you can easily cast it to an int for use in your setPadding(...) method.
(Referencing: Converting pixels to dp)

what does android getIntrinsicHeight and getIntrinsicWidth mean?

Hi I am confused by the two methods from Android Drawable class
getIntrinsicHeight()
getIntrinsicWidth()
api definition says
http://developer.android.com/reference/android/graphics/drawable/Drawable.html#getIntrinsicHeight()
what does the word intrinsic height/width mean?
i mean is it a width of the actual image?
If you want to know the meaning of intrinsic, it is nothing but the actual property possessed by an object. In our case getIntrinsicWidth/Height simply means to provide you with the default width/height of that drawable.
This returns the exact size of the drawable which you have put in the resource folder without any modification.
Now you have to know that getWidth or getHeight will return a value which might vary according to the width and height you specify for your ImageView in your XML layout.
Let's say that you have provided the width and height of your ImageView as 100*100 in the XML layout and the drawable you used as the background is of size 200*200.
Now getIntrinsicWidth must return 200 whereas getWidth must return 100.
related question here on stackoverflow.
IF your image is downloaded from the internet, .getIntrinsicWidth() and .getIntrinsicHeight() indeed give you the "real" width and height, respectively of the image.
It's called intrinsic, because it depends ONLY on the image and on nothing else (such as your phone).
Alas, what you get is NOT intrinsic in all circumstances - it DOES depend things other than the image, unfortunately.
Here is where you get a wrong (namely, non-intrinsic) result. Let's say you are using the default launcher icon, then
Log.i("", "ic_launcher intrinsic width " + getResources().getDrawable(R.drawable.ic_launcher).getIntrinsicWidth());
will tell you the width (in pixels) of the launcher icon. But of which one? - you have several of them, one in drawable-xhdpi folder, one in drawable-hdpi folder, etc. Well, if your device is, say, xhdpi, it gives you 96, which is indeed the pixel-width of the version of the launcher icon residing in the drawable-xhdpi folder. Now, delete the icon in the drawable-xhdpi folder, and run again (still using an xhdpi device (real or emulated)). The image that will be used will be from drawable-hdpi folder, because that's "closest" to the xhdpi version. That icon has a pixel width of 72. But above code WILL STILL GIVE YOU 96!!!
That is clearly NOT "intrinsic" (in the proper sense of the word), as it does not depend only on the image used.
So if you are as lazy as I am, and are therefore not generating 4 versions of each resource icon/image (but instead using only 1 or 2, and scaling them by hand), you have to beware the mentioned androidal misnomer.
In android a drawable can be of many types such as color, bitmap, shape etc.
Some of these drawables have an intrinsic height such as a BitmapDrawable which is the dimension of the image.
Drawables such as ColorDrawable (used to draw just solid colors) don't have an intrinsic height. In this case the value of getIntrinsicHeight/Width returns -1.
Even if a drawable doesn't have intrinsic height/width, every drawable needs to have their bounds set before they can render itself (i.e before you call mydrawable.draw(canvas))
If you using a drawable as a background for a view, the view internally sets the bounds for you. But if you are using drawables in your own onDraw, then you need to explicitly set the bounds via setBounds.

In Android, how may I convert dimension value to exact floating point value?

I have a dimension resource value as
<dimen name="arrowButton_textValue_textSize">20sp</dimen>
I use that value in two following ways
<TextView
android:id="#+id/arrowButton_textValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="#dimen/arrowButton_textValue_textSize"/>
and
float _textSize = getResources().getDimension(R.dimen.arrowButton_textValue_textSize);
TextView _textView = (TextView) findViewById(R.id.arrowButton_textValue);
_textView.setTextSize(_textSize);
I thought those two methods are identical though they cause different text size on real devices. For instance on some devices (HTC Wildfire S) they look the same but on HTC Desire they aren't and the second method generates bigger text size
How may I convert a dimension to exact floating point on real device based on device actual display metrics?

Categories

Resources