Why dpToPixel should not be used to set layout dimensions? - android

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.

Related

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)

Spinning wheel half way off screen android

I have a requirement for a spinning wheel graphic that is half-off the bottom of the screen and then animates (spins) when users take action.
I can place the wheel graphic off the screen by calling:
Animation animation = new TranslateAnimation(Animation.ABSOLUTE,0,Animation.ABSOLUTE, 0,Animation.ABSOLUTE,wheelPos, Animation.ABSOLUTE, wheelPos);
animation.setDuration(1);
animation.setFillAfter(true);
wheel.startAnimation(animation);
That is fine, I can set the width to whatever I want via LayoutParams.
But how can I make the wheel the same size for different screen sizes? For example, I need to place the wheel Y pixels off the screen (wheelPos variable) above, but on a Nexus S versus Nexus 7 how can I calculate an appropriate Y value?
The same goes for width, how can I calculate a width so that it appears exactly the same size?
I note the Catch android app https://catch.com/ - if I expand their wheel menu, it's size is identical on the Nexus S versus Nexus 7 - how might they have achieved that?
Developer of Catch here. Thanks for the email pointing us to your question here.
For our wheel menu, its width and positioning are specified within XML layout files, using dp/dip (density-independent pixels). For a given dimension, say, 300dp, it will appear the same size on any Android device; the system will scale that value depending on whether the device's screen is low, medium, high, xhigh, etc. density.
So, set your width and Y offset using dp and you should be good to go. You can do it completely programmatically if you wish:
final int wheelWidth = 300;
final int wheelYoffset = 64;
DisplayMetrics dm = getResources().getDisplayMetrics();
final float scaledWheelWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, wheelWidth, dm);
final float scaledWheelYoffset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, wheelYoffset, dm);
I personally find it much easier to maintain all of my UI dimensions as resources. In res/values/dimens.xml I'd add:
<dimen name="wheel_width">300dp</dimen>
<dimen name="wheel_y_offset">64dp</dimen>
and then if I needed to use those dimensions in code, I can grab them with a one-liner:
final float scaledWheelWidth = getResources().getDimensionPixelSize(R.dimen.wheel_width);
final float scaledWheelYoffset = getResources().getDimensionPixelOffset(R.dimen.wheel_y_offset);
Using dp for layout is one of the keystone principles of making an Android UI that scales nicely to different devices, and is especially critical for anything you expect your user to touch/interact with, since you can size your hit targets during development and rest assured that they will be physically similarly-sized no matter what device your app is running on.

Shape Drawable gradientRadius only works in pixels. Isn't this useless? % values don't work

I want to use shape drawable with a radial gradient as a background for a View. According to the javadoc the radius of the gradient can be set as a specific value (presumably pixels) or as a percent:
android:gradientRadius
Radius of the gradient, used only with radial gradient.
May be a floating point value, such as "1.2".
May be a fractional value, which is a floating point number appended
with either % or %p, such as "14.5%". The % suffix always means a
percentage of the base size; the optional %p suffix provides a size
relative to some parent container.
Correct me if I'm wrong, but using a pixel value here is completely useless since this gradient will look totally different from one screen density to another (tested, and yes this is true). I tried to go with the % and %p values, but they didn't work at all.
I dove into the Android source code to see how gradientRadius is being processed and found this in the GradientDrawable Class:
TypedValue tv = a.peekValue(
com.android.internal.R.styleable.GradientDrawableGradient_gradientRadius);
if (tv != null) {
boolean radiusRel = tv.type == TypedValue.TYPE_FRACTION;
st.mGradientRadius = radiusRel ?
tv.getFraction(1.0f, 1.0f) : tv.getFloat();
} else ...
}
Ah HA! So all adding % or %p does is divide my value by 100. (I tested this because the documentation for TypeValue.getFraction() is even more unclear). 65% became 0.65. Makes sense, but serves no useful purpose.
So what is the best way to use gradientRadius?
PS.
I have added my background programmatically using a GradientDrawable and I am getting the desired results. I used GradientDrawable.setGradientRadius() with a value relative to the view width and get a consistent Gradient across devices.
To work with all the screen size you have to set radius in dp. You can calculate px from dp by this method
public float pxfromDp(float dp) {
return dp * getResources().getDisplayMetrics().density;
}
So for example you want to set GradientDrawable.setGradientRadius(pxfromDp(7));

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

What units to pass UI methods?

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

Categories

Resources