What units to pass UI methods? - android

Reading the Supporting Multiple Screens of the Android developer guide it says it is always best to use density independent pixels (dp) instead of pixels and it also says Android handles most of the application rendering on different density devices.
Now my question is, if we look at the padding function of the View class
setPadding (int left, int top, int
right, int bottom)
left the left padding in pixels
top the top padding in pixels
right the right padding in pixels
bottom the bottom padding in pixels
When I use this function is it alright to pass direct values or do I have to pass converted values, from dp to px, to best show the UI in all devices?

Check out this padding reference and search for android:padding
It looks like you will need to convert from dip to pixels to set the padding. You should convert the values from dip to pixels so that it works correctly on different devices.

Convert your dip value to pixels and pad with that.
final float scale = getContext().getResources().getDisplayMetrics().density;
int valuePixels = (int)(valueDip * scale);

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())

Why dpToPixel should not be used to set layout dimensions?

Why in javadocs for dpToPixel (declared here) is stated that it shouldn't be used to set layout dimensions?
We don't use pixels to define layout dimensions. Using density independent pixels ensures proper layout sizes across a variety of devices. Meaning said layout will have roughly the same visual size on a 4" phone and a 7" tablet. (Which is completely unrelated to the problem at hand :) )
That being said the actual layout sizes (e.g. LayoutParams class) are in fact using whole pixels to define the resulting size. It is viable to use the dpToPixels method in this way:
float px = dpToPixels(16, getResources()); // 16 dp to pixels precise
int pxOffset = (int) px; // 16dp rounded down to whole pixels
int pxSize = (int) (0.5f + px); // 16dp rounded up to whole pixels
Now you can use these values in layouts, pxOffset for padding, margin etc. (can be zero) and pxSize for width and height (ensures at least 1px size unless zero). The same calculation is done when using methods int Resources.getDimensionPixelOffset(int) and int Resources.getDimensionPixelSize(int) which are suitable for use with layouts and float Resources.getDimension(int) which is suitable for drawing precisely.
EDIT
Elevation uses float values so using the precise dimension is completely fine.
TranslationX, translationY or translationZ are defined using float values. These and many more view properties are used for animation so it makes sense to use smooth values. If set by hand use whole integers for predictable behavior.

calculate how much space will text need

I need to make "pages" (most likely for use ViewPager) which will contain images and text.
For example first will have image, at image download will get image dimensions. After image on layout will be X space left where i can display Y lenght text. Then for next page i will split rest of text into new string to be displayed. TextSize is in dp units.
I had idea to get how much pixels avarage letter takes and then calculate approx how many lines i can show in one page.
What would be a best way to make these calculations ?
And for starter i did letter calculation
final float densityMult = ctx.getResources().getDisplayMetrics().density;
final float scaledPx = 20 * densityMult; //i guess its same as 20dp
paint.setTextSize(scaledPx);
final float size = paint.measureText("a");
Where on 480x800 3.7" screen it returns value 16.0 and on 540x960 4.0" 17.0
Is these values pixels ?
I didn't really understand why you want to measure your text, but just like you have:
paint.measureText("a");
you can measure any string, not just characters. If you want to split your text manually (I shouldn't recommend that), you can check whenever the measure of your text is higher than the available width.
And yes, measureText returns the measure in px

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)

Relative translation in property animation in xml

Currently I have an ImageView that extends the length of the device and is scaled by 3. Obviously, the sides are cropped outside the screen. I want the animation to start with the left-side of the image on the left-side of the device, then shift it over until the right-side of the image is on the right side of the device.
I can achieve them by setting up the image to it's initial base then basically doing this:
<objectAnimator
android:propertyName="translationX"
android:duration="6000"
android:valueTo="-1280"
android:valueType="floatType"
/>
However, this only works because I know the exact size of the image and the exact size of my Motorola Xoom. Naturally, I want this to work on any device, so I need something less hard-coded. With Tween animations, it works alright since you can translate something based off a percentage of it's size. It wasn't perfect, but it worked well enough for this effect. Property aniimations don't seem to have this. The translationX and the X properties must have units.
Is there a simple way to translate a view with property animations based on the relative location? Am I going to have to make a separate animation file for each dimension? Is there any other way I can achieve this effect? I'd prefer not to make my own animation.
Could you create code for separate device size 'buckets' that you want to support (e.g. tablet, phone landscape etc.) which has known dp widths. Then you could use this could below to scale the image (where 384 is 384dp for a certain device bucket width) and do something similar for the the valueTo int you need.
//Get the device screen pixel density so that you can scale
//the image in density independent pixels rather than pixels
final float scale = getActivity().getResources().
getDisplayMetrics().density;
//set the number of dp for the height and width by changing
//the number you multiply the (scale + 0.5f) by e.g. 384
int pixelsh = (int) (384 * scale + 0.5f);
int pixelsw = (int) (384 * scale + 0.5f);
This is the way to convert absolute pixels to dp pixels. I do not know how to use these for what you are doing but at least it is a start.
You should create the animation at run time by passing the screen size of the current device screen. First get the screen size (NOTE: the metrics (width, height) change depending on the rotation of the device):
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
int screenSize = metrics.widthPixels;
and then create your ObjectAnimator:
ObjectAnimator oa = ObjectAnimator.ofFloat(*yourImageView*, "translationX", 0, (Float) screenSize);
oa.setDuration(6000);
oa.start();

Categories

Resources