I have in my project a list of PNG icons in many scales (mdpi at 32px, hdpi at 48px, xhdpi at 64px and xxhdpi at 96px, automatically created by Android Studio), and I get one Activity where I want to display this icons in two distinct sizes, some bigger than the others.
Problem is that the bigger ones (the only ones where I really define layout widths and heights) use low res versions of the drawable instead of nicer ones.
Here is my XML code:
<!-- small icon -->
<ImageView
android:layout_weight="1"
android:layout_width="1dp"
android:layout_height="wrap_content"
android:src="#drawable/ic_icon_like" />
<!-- ... -->
<!-- big icon -->
<ImageView
android:layout_weight="1"
android:src="#drawable/ic_icon_like"
android:layout_width="90dp"
android:layout_height="90dp" />
And here is the result:
As for a comparison, here's the xxhdpi version of PNG of the Drawable, clearly better than the big one I got.
I'm new to Android, so I suppose I'm doing something wrong, but I cannot figure what. Should I define higher res pictures per resolution? Should I provide an ic_icon_like and an ic_icon_like_big for the two displayed versions?
Well, I finally moved on and used a webfont instead, as I get all my icons in SVG formats.
Sizing and coloring are way simpler like this.
So I added an /app/assets/fonts/webfont.ttf file in my project, created an IconView heriting from TextView like this:
public class IconView extends TextView {
public IconView(Context context, AttributeSet attrs) {
super(context, attrs);
// here I trully use a cache, but remove it for a full example
this.setTypeface(Typeface.createFromAsset(context.getAssets(), "fonts/webfont.ttf"));
// the following allows me to use a default color if no 'textColor' attribute is provided
int[] set = {android.R.attr.textColor};
TypedArray attributes = context.obtainStyledAttributes(attrs, set);
ColorDrawable color = (ColorDrawable) attributes.getDrawable(0);
attributes.recycle();
if (color == null) {
this.setTextColor(MY_DEFAULT_COLOR);
}
}
}
Also I added constants in strings.xml for each icon in my webfont, as:
<string name="icon_like"></string>
And used the whole in my XML activities and fragments like this:
<com.mySociety.myProject.IconView android:text="#string/icon_like" />
Related
When I design a layout, I centralize all dimensions in dimens.xml because of topics of maintainability. My question is if this is correct or not. What would it be the best good practice? There is very little information about this, nothing. I know it's good idea to centralize all strings of a layout on strings.xml, colors on colors.xml. But about dimensions?
For example:
<TableLayout
android:id="#+id/history_detail_rows_submitted"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/cebroker_history_detail_rows_border"
android:collapseColumns="*">
<TableRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/history_detail_rows_margin_vertical"
android:background="#color/cebroker_history_detail_rows_background"
android:gravity="center"
android:paddingBottom="#dimen/history_detail_rows_padding_vertical"
android:paddingLeft="#dimen/history_detail_rows_padding_horizontal"
android:paddingRight="#dimen/history_detail_rows_padding_horizontal"
android:paddingTop="#dimen/history_detail_rows_padding_vertical">
<TextView
android:layout_width="match_parent"
android:drawableLeft="#mipmap/ic_history_detail_submitted_by"
android:drawablePadding="#dimen/history_detail_rows_textviews_padding_drawable"
android:gravity="left|center"
android:paddingRight="#dimen/history_detail_rows_textviews_padding"
android:text="#string/history_detail_textview_submitted_by"
android:textColor="#color/cebroker_history_detail_rows_textviews"
android:textSize="#dimen/history_detail_rows_textviews_text_size" />
How to use dimens.xml
Create a new dimens.xml file by right clicking the values folder and choosing New > Values resource file. Write dimens for the name. (You could also call it dimen or dimensions. The name doesn't really matter, only the dimen resource type that it will include.)
Add a dimen name and value.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="my_value">16dp</dimen>
</resources>
Values can be in dp, px, or sp.
Use the value in xml
<TextView
android:padding="#dimen/my_value"
... />
or in code
float sizeInPixels = getResources().getDimension(R.dimen.my_value);
When to use dimens.xml
Thanks to this answer for more ideas.
Reusing values - If you need to use the same dimension multiple places throughout your app (for example, Activity layout padding or a TextView textSize), then using a single dimen value will make it much easier to adjust later. This is the same idea as using styles and themes.
Supporting Multiple Screens - A padding of 8dp might look fine on a phone but terrible on a 10" tablet. You can create multiple dimens.xml to be used with different screens. That way you could do something like set 8dp for the phone and 64dp for the tablet. To create another dimens.xml file, right click your res folder and choose New > Value resource file. (see this answer for details)
Convenient dp to px code conversion - In code you usually need to work with pixel values. However you still have to think about the device density and the conversion is annoying to do programmatically. If you have a constant dp value, you can get it in pixels easy like this for float:
float sizeInPixels = getResources().getDimension(R.dimen.my_value);
or this for int :
int sizeInPixels = getResources().getDimensionPixelSize(R.dimen.my_value);
I give many more details of how to do these things in my fuller answer.
When not to use dimens.xml
Don't put your values in dimens.xml if it is going to make them more difficult to maintain. Generally that will be whenever it doesn't fall into the categories I listed above. Using dimens.xml makes the code harder to read because you have to flip back and forth between two files to see what the actual values are. It's not worth it (in my opinion) for individual Views.
Strings are different. All strings should go in a resource file like strings.xml because almost all strings need to be translated when internationalizing your app. Most dimension values, on the other hand, do not need to change for a different locality. Android Studio seems to support this reasoning. Defining a string directly in the layout xml will give a warning but defining a dp value won't.
add an xml file dimens.xml this is use for support multiple devices.
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="iconarrow">1dp</dimen>
<item name="text_view_padding" type="integer">100</item>
</resources>
then you can use it in your code like this in java code
textview.setPadding(0, 0, 0, getResources().getInteger(R.integer.text_view_padding));
You can also use in other layout(xml file).
android:padding="#dimen/text_view_padding"
you don't need to mention dimen value in value folder file. this library auto manage all the things you just call like that
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/_20sdp"
android:paddingLeft="#dimen/_20sdp"
android:paddingRight="#dimen/_20sdp"
android:paddingTop="#dimen/_20sdp"
android:background="#color/colorPrimary"
android:orientation="vertical"
>
whole code click here for that
But about dimensions?
According to the official Android docs "A dimension is a simple resource that is referenced using the value provided in the name attribute (not the name of the XML file). As such, you can combine dimension resources with other simple resources in the one XML file, under one <resources> element"
For more details refer to http://developer.android.com/guide/topics/resources/more-resources.html
In this post, Devunwired gives three great reasons as to why use dimens.xml When should the dimens.xml file be used in Android?
#Jesús Castro You are doing it right. Maintaining values in the dimens.xml file is better than littering hardcoded values around in all your layout files.
For example, imagine the case where you to increase the left and right margins in all your view. If you used a single value maintained in dimens.xml, this would be a quick change - a single value in a single file.
However, if you had put the margin values as a literal values such as "16dp" in your layout files (instead of using a dimens value like "#dimen/leftright_margin"), you have to go edit each layout file which is error prone and just plain time consuming.
I have a novel method I use which I thought is in keeping with the question. I have been avoiding Xml alot to avoid the cost of parsing xml code.
Rather than using xml dimens ,I use java constants.
either...
public interface DimenConstants { ... }
or...
public class DimenConstants
{
public static void init(Activity activity){...}
}
Then in the case of supporting different screen, you can actually do this yourself in Java at runtime. One way is:
public class TestScreenSizes extends Activity
{
public static final ViewGroup.LayoutParams MAIN_VIEW_SPEC = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);
#Override protected void onCreate(Bundle savedState)
{
super.onCreate(savedState);
setContentView(newTextView(),MAIN_VIEW_SPEC);
}
protected TextView newTextView()
{
TextView tv = new TextView(this);
DisplayMetrics display = getResources().getDisplayMetrics();
int resolution = display.widthPixels * display.heightPixels;
if(resolution == 1024) tv.setText("You are using an iphone");
else if(resolution == 4096) tv.setText("You are using a Samsung Galexy");
return rv;
}
}
yes absolutely It is best to keep the values in the dimens.xml file
I don’t know if it can help you but I wrote a little java programe that allows you to duplicate
a dimension xml file with a new desired value so that you no longer have to do it by hand line by line.
https://github.com/Drex-xdev/Dimensions-Scalable-Android
Say I have following TextView in layout:
<TextView
android:id="#+id/txtLoginError"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/sz_12dp"
android:gravity="center"
android:text="#string/foo" />
Is it possible that I change #dimen/sz_12dp to point to static field in my custom class, for example:
public static class MyDimensions {
public static int topMarginInPixels = 99; // which would be referenced in some fashion like android:layout_marginTop="#class/MyDimensions.topMarginInPixels"
}
I am basically looking for a way to "databind" layout element for some experimentation; instead of loading view and then modifying it in code (findview, setWeight, etc) I would like for view during creation to fetch sizes from my custom class like it fetches it from R.java.
NOTE: I know best practices for supporting multiple screens, differences between px & dp, how to provide different res/values directories for different screen sizes, so please response only if you know answer to this questions rather than repeat what can be read on this link.
It's not possible to achieve what you described, but if you really want to have pixels and only pixels you can very well put px size instead of dp, both in XML or in dimens file - but I guess you knew that already.
EDIT a dimension cannot be changed indeed at runtime, but it is not even supposed to; if you want to change a dimension at runtime for a view, get its LayoutParams object and set its width, height, margin, padding or whatever you want to change.
I have developed this custom ImageView class to override some of the default behavior to fit my needs. Let me describe what this custom ImageView does...
Let's say you have a bunch of icons to display in GridView both in the drawable-mdpi and drawable-hdpi folder, they are 48x48px and 72x72px in size, respectively. There are no icons available in the drawable-xhdpi folder. The GridView attributes are so that all the icons size will be in 48x48dp (this will translate to 48px, 72px and 96px for mpdi, hdpi and xhdpi densities, respectively).
Since there are no icons in the drawable-xhdpi folder, when this app is ran on a device with such density, the icons will be pulled from the drawable-hdpi folder. And since they are only 72px and the xhdpi devices are expecting 96px images, the icons will be stretched to fill the remaining pixels.
This is the behavior my custom ImageView attempts to override. With my custom component, what will happen is that the images will simply not get stretched. For instance, in the example above using my class, each ImageView inside the GridView will still be 96x96px (because of the 48x48dp size defined) but the images used are from the drawable-hdpi folder which are 72x72px. What will happen is that these images from the drawable-hdpi folder will be placed in the center of the ImageView which is 96x96px in size without stretching the image to fit the whole view size.
If the above is confusing, let's try with a few pictures. The example below does not use GridView, I'm trying to simplify the idea behind my custom class. These are the source pictures I'm using for this example:
This is the result on HDPI device:
And this is the result on XHDPI device:
The code for the layout on the screenshots above is this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Standard ImageView:"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_margin="10dp"
android:scaleType="center"
android:background="#FFEEEE"
android:src="#drawable/ic_female"/>
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_margin="10dp"
android:scaleType="center"
android:background="#FFEEEE"
android:src="#drawable/ic_male"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Custom UnscaledImageView:"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<com.sampleapp.widget.UnscaledImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_margin="10dp"
android:scaleType="center"
android:background="#FFEEEE"
android:src="#drawable/ic_female"/>
<com.sampleapp.widget.UnscaledImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_margin="10dp"
android:scaleType="center"
android:background="#FFEEEE"
android:src="#drawable/ic_male"/>
</LinearLayout>
Is it more clear now? This is what I want to do and this is working nicely, besides a small performance issue... Now let me post the code I'm using for such custom component:
attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="UnscaledImageView">
<attr name="android:src" />
</declare-styleable>
</resources>
UnscaledImageView.java:
public class UnscaledImageView extends ImageView {
private int mDeviceDensityDpi;
public UnscaledImageView(Context context) {
super(context);
mDeviceDensityDpi = getResources().getDisplayMetrics().densityDpi;
}
public UnscaledImageView(Context context, AttributeSet attrs) {
super(context, attrs);
mDeviceDensityDpi = getResources().getDisplayMetrics().densityDpi;
TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.UnscaledImageView);
int resourceId = styledAttrs.getResourceId(R.styleable.UnscaledImageView_android_src, 0);
if(resourceId != 0) {
setUnscaledImageResource(resourceId);
}
styledAttrs.recycle();
}
public void setUnscaledImageResource(int resId) {
setImageBitmap(decodeBitmapResource(resId));
}
#SuppressWarnings("deprecation")
public void setUnscaledBackgroundResource(int resId) {
BitmapDrawable drawable = new BitmapDrawable(null, decodeBitmapResource(resId));
drawable.setTargetDensity(mDeviceDensityDpi);
drawable.setGravity(Gravity.CENTER);
setBackgroundDrawable(drawable);
}
private Bitmap decodeBitmapResource(int resId) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inDensity = mDeviceDensityDpi;
return BitmapFactory.decodeResource(getResources(), resId, options);
}
}
So, this class will do it's thing if the UnscaledImageView view is used in XML layouts or directly initialized in code. I've also provided 2 methods so the image can be changed in code while keeping it from being stretched. As you can see, these methods only take resource ids, so far I haven't felt the need to use drawables or bitmaps directly.
Now the real issue I'm having with this...
If this class is used as single image view in some layout, no problem, it's only decoding one image. But if it's used in a GridView where there can be like 40 icons (I'm taking this value from what really happens on my app running on my xhdpi device) visible at the same time, scrolling the GridView will be very slow because the decodeBitmapResource() is calling BitmapFactory.decodeResource() for each and every image.
This is my problem and that is my question. How can I optimize this? If possible, at all...
Putting those images into the res/drawable-nodpi/ could do what you want (I'm saying to put different resolution images side by side).
It would be a bit tricky because probably you'd have to follow a naming convention to be able to find the best resource for a given image that you are trying to draw. Probably this will require you to try finding images by their name and that's not a very efficient way to retrieve resources.
The way I imagin this is: on the layout (or anywhere else), you specify the name (string, not id!) of the image resource you want to use.
In that nodpi folder, you'd have the images with a suffix for the intended screen density.
Then, in the setter method you have to try different combinations in order to find the best available resource.
Problem for you to think: what if you're scaling down an image? The resource would be bigger than the view where you'd draw it!
Although the answer by Pedro Loureiro could be a possible solution, I decided to take a different approach after realizing something...
I've timed both the native ImageView loading and my UnscaledImageView loading in a GridView, non-scientifically of course and I've realized that my class loads all the images a little bit faster than the native one. Maybe the native methods have something else going on, besides decoding the resource (they still have to do it, right?) while my class simply decodes a resource (using BitmapFactory) and that's basically it.
I thought that it was my class that was making the GridView kinda slow while scrolling but after a few more tests, using the original ImageView without any tweaks, also revealed to be a little choppy while scrolling the GrivView.
The solution I found to solve this issue (either with my class or the native one) was to cache the icons in the GridView and for that I used the LruCache which is the recommended way of caching images in a GridView.
So that's the solution I'll be using to solve my issue. For more details please refer to the official training guide: http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
For reference, I've also found the following tutorial useful: http://andrewbrobinson.com/2012/03/05/image-caching-in-android/
The Situation:
I have a imageview with a layer list of two drwables.
The frame drwable is 800x400px and the cover drawable is 800x380px.
Both images reside in the folder drawable
<ImageView
android:id="#+id/preview_img_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="0.9"
android:adjustViewBounds="true"
android:src="#drawable/result_preview"/>
The Drwable layer list in drawable/result_preview
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/cover">
<bitmap
android:gravity="left|top"
android:src="#drawable/cover" />
</item>
<item
android:id="#+id/frame">
<bitmap
android:gravity="left|top"
android:src="#drawable/frame" />
</item>
</layer-list>
This setup works as expected the cover is displayed framed on all devices. Now the user can replace the sample cover with another cover of the same size.
Replacing the cover in the layer list and leave the frame as is.
LayerDrawable layer = (LayerDrawable) getResources().getDrawable(R.drawable.result_preview);
InputStream coverIs = getContentResolver().openInputStream(Uri.parse(coverUri));
this.drwCover = (BitmapDrawable) BitmapDrawable.createFromStream(coverIs, coverUri);
drwCover.setGravity(Gravity.LEFT | Gravity.TOP);
//drwCover.getBitmap().setDensity(160);
layer.setDrawableByLayerId(R.id.cover, drwCover);
imageView.setImageDrawable(layer);
The Problem:
Replacing the current cover with a new one (same dimensions as old) produces different results depending on the Device used.
On a device with a 3.2 screen and a 480x320 resolution the cover replaced fits in the frame. On a device with a 3.7 and a 800x480 resolution the replaced cover is displayed smaller then the old one.
What I found out is that the drwable in the imageview on the small device has a intrinsic height of 800x400 same as the dimensions of the drawable.
On the bigger screen the intrinsic height of the drawable is 30% bigger.
I know the the intrinsic values may differ from screen to screen.
What I was expecting is that the drawable that replaces the old one should will be scaled up the same way the old one was to +30%. But this did not happen.
Question:
Is there a option to tell the imageview or the layer list to Adpt itself? I think there should be a way to do so because the system did it already at the beginning.
First, I'd suggest doing the cover/frame differently:
make your frame a nine-patch drawable, so you can define in the nine-patch the padding that will remain visible when the cover is drawn on top of it.
put the frame drawable as a background
set the cover as the src of the image, and not the layer list
don't forget to set a scaleType for your ImageView, play with the different options, so suit your needs.
<ImageView
android:id="#+id/preview_img_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="0.9"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:background="#drawable/frame"
android:src="#drawable/cover"/>
For other having the same problem. 9patch png is a solution to a problem I don't have.
So far the only thing that came close to solve the problem was to put my resources into the folder drawable-nodpi. Here is the reference to the docs
I am expecting my app to run in fullscreen on all screen sizes using the android stretching capabilities
Am using only one XML layout.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<com.nwm.CD.CCanvas_480x320
android:layout_weight ="1"
android:fillViewport="true"
android:id="#+id/cc_320x480"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
But when I run the app on a WVGA00(480x800) a black bar shows up on the far right and has a width of about 80pixels and the app doesn't fill the entire screen.
The class extending view
public class CC_480x320 extends View implements Runnable{
public CricCanvas_480x320(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = (CricDhamaka)context;
}
Any ideas on how to make it run in fullscreen??
Display displayDevice=getWindowManager().getDefaultDisplay();
int wid=displayDevice.getWidth();
int hit=displayDevice.getHeight();
LayoutParams lp=new LayoutParams(wid, hit);
yourview.setlayoutParams(lp);
have you tried this ???
math_parent should be used in root layout parameters and not in child ones. Prefer to use fill_parent in your child views.
I haven't worked with canvas before. But I guess it works like an ImageView. ImageViews don't actually render to full screen if the actual pixels of the image is smaller than the pixels of the screen. If that is the case then you have to use scaling, or the appropriate thing for canvas.
Also if you are trying to view full screen not the canvas but the whole application try to update your theme in your manifest. In the application section:
<application android:theme="#android:style/Theme.NoTitleBar.Fullscreen" >
... // removes the title bar of every activity in your application and makes it full screen no battery no signal etc
</application>
UPDATE:
According to your screen shot is the scaling effect. Change the scaling effect of your canvas. Try searching on how to make the scale of your canvas to the layout parameters of you view. Delete your weight attribute and test first though
Question: have you added anything else on your layout.xml? Like a view or something?