492

I need to implement my own attributes like in com.android.R.attr

Found nothing in official documentation so I need information about how to define these attrs and how to use them from my code.

Suragch
  • 364,799
  • 232
  • 1,155
  • 1,198
Alexander Oleynikov
  • 17,956
  • 11
  • 34
  • 50
  • 21
    These docs may be newer that your post, but in order to keep this current, you can find good, official documentation for attributes here : http://developer.android.com/training/custom-views/create-view.html#customattr – OYRM Apr 01 '13 at 22:26
  • I recommend nice article with an example about the custom attributes: https://amcmobileware.org/android/blog/2016/09/11/custom-attributes/ – Arkadiusz Cieśliński Oct 23 '16 at 18:05
  • a small working example may be helpful: https://github.com/yujiaao/MergeLayout1 – Yu Jiaao Nov 02 '16 at 01:55

5 Answers5

1014

Currently the best documentation is the source. You can take a look at it here (attrs.xml).

You can define attributes in the top <resources> element or inside of a <declare-styleable> element. If I'm going to use an attr in more than one place I put it in the root element. Note, all attributes share the same global namespace. That means that even if you create a new attribute inside of a <declare-styleable> element it can be used outside of it and you cannot create another attribute with the same name of a different type.

An <attr> element has two xml attributes name and format. name lets you call it something and this is how you end up referring to it in code, e.g., R.attr.my_attribute. The format attribute can have different values depending on the 'type' of attribute you want.

  • reference - if it references another resource id (e.g, "@color/my_color", "@layout/my_layout")
  • color
  • boolean
  • dimension
  • float
  • integer
  • string
  • fraction
  • enum - normally implicitly defined
  • flag - normally implicitly defined

You can set the format to multiple types by using |, e.g., format="reference|color".

enum attributes can be defined as follows:

<attr name="my_enum_attr">
  <enum name="value1" value="1" />
  <enum name="value2" value="2" />
</attr>

flag attributes are similar except the values need to be defined so they can be bit ored together:

<attr name="my_flag_attr">
  <flag name="fuzzy" value="0x01" />
  <flag name="cold" value="0x02" />
</attr>

In addition to attributes there is the <declare-styleable> element. This allows you to define attributes a custom view can use. You do this by specifying an <attr> element, if it was previously defined you do not specify the format. If you wish to reuse an android attr, for example, android:gravity, then you can do that in the name, as follows.

An example of a custom view <declare-styleable>:

<declare-styleable name="MyCustomView">
  <attr name="my_custom_attribute" />
  <attr name="android:gravity" />
</declare-styleable>

When defining your custom attributes in XML on your custom view you need to do a few things. First you must declare a namespace to find your attributes. You do this on the root layout element. Normally there is only xmlns:android="http://schemas.android.com/apk/res/android". You must now also add xmlns:whatever="http://schemas.android.com/apk/res-auto".

Example:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:whatever="http://schemas.android.com/apk/res-auto"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">

    <org.example.mypackage.MyCustomView
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:gravity="center"
      whatever:my_custom_attribute="Hello, world!" />
</LinearLayout>

Finally, to access that custom attribute you normally do so in the constructor of your custom view as follows.

public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);

  TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView, defStyle, 0);

  String str = a.getString(R.styleable.MyCustomView_my_custom_attribute);

  //do something with str

  a.recycle();
}

The end. :)

Austyn Mahoney
  • 11,078
  • 7
  • 59
  • 85
Rich Schuler
  • 40,748
  • 6
  • 68
  • 58
  • 15
    Here is a sample project demonstrating custom attributes for use with a custom `View`: http://github.com/commonsguy/cw-advandroid/tree/master/Views/ColorMixer/ – CommonsWare Aug 09 '10 at 16:13
  • At this point, one cannot define a layout in a library package and use it in different main projects if it uses custom attributes. This is because of how the XML name space must be declared when using custom attributes. See [this discussion](http://code.google.com/p/android/issues/detail?id=9656&q=declare-styleable&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars) on the Android issues list. – Ted Hopp May 16 '11 at 18:32
  • 7
    If you are using custom attrs from a library project: see this question: http://stackoverflow.com/questions/5819369/error-no-resource-identifier-found-for-attribute-adsize-in-package-com-googl - It seems to work if you use `xmlns:my="http://schemas.android.com/apk/lib/my.namespace"` - no copying attrs.xml. Note the namespace URI path must be /apk/*lib* not /apk/res. – thom_nic Mar 01 '12 at 15:17
  • How may I resolve the issue if there exist the same attribute in two packages? Assume an attribute of `` exists in package `com.pack1` (in `module1`) and also that same attribute exists in `com.pack2` (in `module2`) and both modules are referenced in `module3` (executable). I received error `Attribute "title" has already been defined`. – anonim Aug 28 '12 at 09:37
  • Sorry for the necro but if you put the directly in the root , how do you then reference the attributes in the view class? – obie Sep 07 '12 at 08:52
  • Is it really true that valid values for `format` and their meanings are not described anywhere in the official docs? I've had a good look and can't find them. Seems odd for such an important feature of the architecture. – JulianSymes Dec 01 '12 at 12:12
  • 2
    @ThomNichols the `apk/lib` trick didn't work for me on custom attributes with reference format from a library project. What _did_ work was to use `apk/res-auto`, as suggested in http://stackoverflow.com/a/13420366/22904 just below and also in http://stackoverflow.com/a/10217752 – Giulio Piancastelli Jan 14 '13 at 11:28
  • I've updated the answer to use apk/res-auto since this is now the best way to do it and much less confusing. – Intrications Feb 07 '13 at 17:07
  • This way of defining custom attributes will work in any resource type? Say, for example, in `drawable` or `values` xml files? – taylor Apr 23 '13 at 21:08
  • 1
    Quoting @Qberticus: "flag attributes are similar except the values need to be defined so they can be bit ored together". In my opinion this is kind of understating the main difference between `enum` and `flag`: the former lets us pick one and only one value, the latter lets us combine several. I wrote up a longer answer in a similar question [here](http://stackoverflow.com/questions/6020379/declaring-styleable-attributes-in-android), and having now found this question I figured I'd link to that. – Rad Haring Mar 13 '14 at 18:36
  • I use `xmlns:xxxx="http://schemas.android.com/apk/res/my_package_name"` and it works – suitianshi Jul 22 '14 at 03:18
  • Is it possible to make an attribute mandatory? Something like this does not work unfortunately: – Couitchy Sep 23 '14 at 15:54
  • 5
    `a.recycle()` is very important here to free up memory – Tash Pemhiwa Jan 21 '16 at 08:34
  • Hello, What about decimal values? I need to take in value like 20.5 or 0.51 etc. – Vincent_Paing Sep 13 '16 at 07:46
  • for me `MyCustomView(Context context, AttributeSet attrs, int defStyle)` was not getting called so changing to `MyCustomView(Context context, AttributeSet attrs)` worked and used `context.obtainStyledAttributes(attrs,R.styleable.MyCustomView)` to get `TypedArray`. – Zeeshan Ghazanfar Sep 27 '16 at 16:30
  • This should really go in bold: **"Note, all attributes share the same global namespace. That means that even if you create a new attribute inside of a element it can be used outside of it and you cannot create another attribute with the same name of a different type."** It's so counter-intuitive and can easily become a reason why you can't build Resources and have no idea why. – Varvara Kalinina May 28 '17 at 11:01
  • How does then `width` and `height` work with values like `16dp` and `match_parent`? Are they `dimension|enum`? – Michel Feinstein Sep 02 '18 at 22:41
88

Qberticus's answer is good, but one useful detail is missing. If you are implementing these in a library replace:

xmlns:whatever="http://schemas.android.com/apk/res/org.example.mypackage"

with:

xmlns:whatever="http://schemas.android.com/apk/res-auto"

Otherwise the application that uses the library will have runtime errors.

Neil Miller
  • 1,034
  • 8
  • 10
  • 3
    This was only recently added... I think within a few weeks ago. Certainly it was added long after Qberticus wrote his answer. – ArtOfWarfare Nov 19 '12 at 20:38
  • 12
    I think it's older than that, but it certainly was added long after Qberticus wrote his answer. Not faulting him at all, just adding a useful detail. – Neil Miller Nov 26 '12 at 21:20
  • 11
    I've updated Qbericus's answer to use apk/res-auto to save confusion. – Intrications Feb 07 '13 at 17:08
15

The answer above covers everything in great detail, apart from a couple of things.

First, if there are no styles, then the (Context context, AttributeSet attrs) method signature will be used to instantiate the preference. In this case just use context.obtainStyledAttributes(attrs, R.styleable.MyCustomView) to get the TypedArray.

Secondly it does not cover how to deal with plaurals resources (quantity strings). These cannot be dealt with using TypedArray. Here is a code snippet from my SeekBarPreference that sets the summary of the preference formatting its value according to the value of the preference. If the xml for the preference sets android:summary to a text string or a string resouce the value of the preference is formatted into the string (it should have %d in it, to pick up the value). If android:summary is set to a plaurals resource, then that is used to format the result.

// Use your own name space if not using an android resource.
final static private String ANDROID_NS = 
    "http://schemas.android.com/apk/res/android";
private int pluralResource;
private Resources resources;
private String summary;

public SeekBarPreference(Context context, AttributeSet attrs) {
    // ...
    TypedArray attributes = context.obtainStyledAttributes(
        attrs, R.styleable.SeekBarPreference);
    pluralResource =  attrs.getAttributeResourceValue(ANDROID_NS, "summary", 0);
    if (pluralResource !=  0) {
        if (! resources.getResourceTypeName(pluralResource).equals("plurals")) {
            pluralResource = 0;
        }
    }
    if (pluralResource ==  0) {
        summary = attributes.getString(
            R.styleable.SeekBarPreference_android_summary);
    }
    attributes.recycle();
}

@Override
public CharSequence getSummary() {
    int value = getPersistedInt(defaultValue);
    if (pluralResource != 0) {
        return resources.getQuantityString(pluralResource, value, value);
    }
    return (summary == null) ? null : String.format(summary, value);
}

  • This is just given as an example, however, if you want are tempted to set the summary on the preference screen, then you need to call notifyChanged() in the preference's onDialogClosed method.
JJD
  • 44,755
  • 49
  • 183
  • 309
Steve Waring
  • 2,576
  • 2
  • 26
  • 35
5

The traditional approach is full of boilerplate code and clumsy resource handling. That's why I made the Spyglass framework. To demonstrate how it works, here's an example showing how to make a custom view that displays a String title.

Step 1: Create a custom view class.

public class CustomView extends FrameLayout {
    private TextView titleView;

    public CustomView(Context context) {
        super(context);
        init(null, 0, 0);
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0, 0);
    }

    public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs, defStyleAttr, 0);
    }

    @RequiresApi(21)
    public CustomView(
            Context context, 
            AttributeSet attrs,
            int defStyleAttr,
            int defStyleRes) {

        super(context, attrs, defStyleAttr, defStyleRes);
        init(attrs, defStyleAttr, defStyleRes);
    }

    public void setTitle(String title) {
        titleView.setText(title);
    }

    private void init(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        inflate(getContext(), R.layout.custom_view, this);

        titleView = findViewById(R.id.title_view);
    }
}

Step 2: Define a string attribute in the values/attrs.xml resource file:

<resources>
    <declare-styleable name="CustomView">
        <attr name="title" format="string"/>
    </declare-styleable>
</resources>

Step 3: Apply the @StringHandler annotation to the setTitle method to tell the Spyglass framework to route the attribute value to this method when the view is inflated.

@HandlesString(attributeId = R.styleable.CustomView_title)
public void setTitle(String title) {
    titleView.setText(title);
}

Now that your class has a Spyglass annotation, the Spyglass framework will detect it at compile-time and automatically generate the CustomView_SpyglassCompanion class.

Step 4: Use the generated class in the custom view's init method:

private void init(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    inflate(getContext(), R.layout.custom_view, this);

    titleView = findViewById(R.id.title_view);

    CustomView_SpyglassCompanion
            .builder()
            .withTarget(this)
            .withContext(getContext())
            .withAttributeSet(attrs)
            .withDefaultStyleAttribute(defStyleAttr)
            .withDefaultStyleResource(defStyleRes)
            .build()
            .callTargetMethodsNow();
}

That's it. Now when you instantiate the class from XML, the Spyglass companion interprets the attributes and makes the required method call. For example, if we inflate the following layout then setTitle will be called with "Hello, World!" as the argument.

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:width="match_parent"
    android:height="match_parent">

    <com.example.CustomView
        android:width="match_parent"
        android:height="match_parent"
        app:title="Hello, World!"/>
</FrameLayout>

The framework isn't limited to string resources has lots of different annotations for handling other resource types. It also has annotations for defining default values and for passing in placeholder values if your methods have multiple parameters.

Have a look at the Github repo for more information and examples.

Helios
  • 839
  • 9
  • 14
  • You can achieve the same with Google Data Binding - if there is no attribute binding for specific attribute, GDB tries to find set* method and uses it instead. In this case you'd have to wrote, say `android:title="@{"Hello, world!"}"`. – Spook Feb 04 '19 at 06:06
1

if you omit the format attribute from the attr element, you can use it to reference a class from XML layouts.

  • example from attrs.xml.
  • Android Studio understands that the class is being referenced from XML
    • i.e.
      • Refactor > Rename works
      • Find Usages works
      • and so on...

don't specify a format attribute in .../src/main/res/values/attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="MyCustomView">
        ....
        <attr name="give_me_a_class"/>
        ....
    </declare-styleable>

</resources>

use it in some layout file .../src/main/res/layout/activity__main_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<SomeLayout
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- make sure to use $ dollar signs for nested classes -->
    <MyCustomView
        app:give_me_a_class="class.type.name.Outer$Nested/>

    <MyCustomView
        app:give_me_a_class="class.type.name.AnotherClass/>

</SomeLayout>

parse the class in your view initialization code .../src/main/java/.../MyCustomView.kt

class MyCustomView(
        context:Context,
        attrs:AttributeSet)
    :View(context,attrs)
{
    // parse XML attributes
    ....
    private val giveMeAClass:SomeCustomInterface
    init
    {
        context.theme.obtainStyledAttributes(attrs,R.styleable.ColorPreference,0,0).apply()
        {
            try
            {
                // very important to use the class loader from the passed-in context
                giveMeAClass = context::class.java.classLoader!!
                        .loadClass(getString(R.styleable.MyCustomView_give_me_a_class))
                        .newInstance() // instantiate using 0-args constructor
                        .let {it as SomeCustomInterface}
            }
            finally
            {
                recycle()
            }
        }
    }
Eric
  • 12,320
  • 4
  • 53
  • 63