Background
When creating custom view, you most likely want to use custom attributes for easy customization. To do that, you’ll typically declare your custom attributes with <declare-styleable />
in an attrs.xml
file under the project’s res/values/
folder. It will then be parsed at compilation by aapt
into R.styleable.MyCustomView
and the corresponding R.styleable.MyCustomView_attr1
, R.styleable.MyCustomView_attr2
and so forth for each of the attributes in the styleable.
There are cases, however, in particular when you’re only using predefined attributes such as those defined in android.R.attr.*
or appcompat’s R.attr.*
, you may want or need to declare styleable programmatically instead of depending on xml resources. One reason can be if you want or need to distribute your custom view as a JAR
only distribution. Another reason is when your custom view extends other view, and you need to get the values of attributes defined by the base class but don’t have accessor method for the attribute, e.g. the android:src
of ImageView
, android:textAppearance
of the TextView
, and many others. Or you might want to do it purely just for convenience.
How To Do It
Here’s how you can programmatically declare styleable and use it to customize your component:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initAttributes(context, attrs, defStyleAttr); } private void initAttributes(Context context, AttributeSet attrs, int defStyleAttr) { // declare styleable here final int[] styleable = new int[] { android.R.attr.src, android.R.attr.textAppearance, android.R.attr.text, android.R.attr.textSize }; // This is important. The styleable must be sorted. Otherwise you'll get unexpected result. Arrays.sort(styleable); TypedArray a = context.obtainStyledAttributes(attrs, styleable, defStyleAttr,0); for (int attrIndex = 0; attrIndex < styleable.length; attrIndex++) { int attribute = styleable[attrIndex]; switch (attribute) { case android.R.attr.src: mAvatar.setImageDrawable(a.getDrawable(attrIndex)); break; case android.R.attr.textAppearance: mName.setTextAppearance(getContext(), a.getResourceId(attrIndex, 0)); break; case android.R.attr.text: mName.setText(a.getText(attrIndex)); break; case android.R.attr.textSize: mName.setTextSize(a.getDimensionPixelSize(attrIndex, 10)); break; } } a.recycle(); } |
What Is Styleable
A styleable is basically an int[]
containing a set of attributes. You use this attributes to obtain values you need to customize the behavior or appearance of your component. When you call the Context#obtainStyledAttributes()
method, you’ll get back a TypedArray
object containing the values referenced by the attributes, taking into consideration the default values from the style and theme set in your app. In order to get the value referenced by particular attribute, you call an appropriate method of the TypedArray
with the attribute’s index (not value!) as parameter.
How This Works
For optimization reasons, the context.obtainStyledAttributes()
method, which internally calls Resources#obtainStyledAttributes()
, and which in turn calls a native method to fill the requested values, assumes that the styleable is sorted in ascending order. So for this to work, it is important to sort your styleable before calling the context.obtainStyledAttributes()
method. It can be done simply by calling Arrays.sort(styleable)
on your styleable. If the styleable is not correctly sorted, you’ll get unexpected result. If you declare your styleable using xml <declare-styleable />
, the aapt
will have it sorted correctly when it compiles the resources.
Conclusion
We’ve been using this simple tips for years, but have never come across any codes that use this techniques (we’ve seen some codes with a styleable of a single attribute, though, but that doesn’t require sorting). So we thought we’d share it. Hope it helps some developers out there.
We’ve put a sample project at Github to demonstrate this tips.
Happy coding 🙂