androidattrandroid-custom-viewandroid-resourcesdeclare-styleable

What's the point of declare-styleable?


Suppose I am making some new views with styleable attributes. I declare them thusly (this is how the documentation says to do it:

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

    <declare-styleable name="TriangleView">
        <attr name="direction">
            <enum name="NE" value="0" />
            <enum name="NW" value="1" />
            <enum name="SW" value="2" />
            <enum name="SE" value="3" />
        </attr>
    </declare-styleable>

    <declare-styleable name="BannerView">
        <attr name="direction">
            <enum name="NE" value="0" />
            <enum name="NW" value="1" />
            <enum name="SW" value="2" />
            <enum name="SE" value="3" />
        </attr>
        <attr name="thickness" format="dimension" />
    </declare-styleable>
</resources>

However, this won't work because all attributes are apparently in the same namespace, and I get the error Error: Attribute "direction" has already been defined.

So apparently I have to move the apparently duplicated attributes outside the <declare-styleable> like this:

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


    <attr name="direction">
        <enum name="NE" value="0" />
        <enum name="NW" value="1" />
        <enum name="SW" value="2" />
        <enum name="SE" value="3" />
    </attr>

    <declare-styleable name="BannerView">
        <attr name="thickness" format="dimension" />
    </declare-styleable>
</resources>

But this poses two questions:

  1. If this works, what exactly is the point of <declare-styleable>?
  2. What if I want the attribute to behave differently in different views? For example if BannerView's direction can only be up or down.

Solution

  • What exactly is the point of <declare-styleable>?

    <declare-stylable> tags let you declare attributes for your custom views that you can then set for those views in xml. There are really 3 parts to using the attribute:

    1. Declare an <attr> inside of a <declare-stylable> tag.
    2. Define a custom namespace in your xml layout pointing to your app package name (ex. app). Use the custom attribute in your layout (ex. app:direction="NW").
    3. In your custom view, override the constructors with an AttributeSet parameter, get a TypedArray and read the custom attributes, if any, from it and then within the constructor tell the view how to use those attributes appropriately.

    What if I want the attribute to behave differently in different views? For example if BannerView's direction can only be up or down.

    Try something like this:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    
        <attr name="direction">
            <enum name="NE" value="0" />
            <enum name="NW" value="1" />
            <enum name="SW" value="2" />
            <enum name="SE" value="3" />
        </attr>
    
        <declare-styleable name="TriangleView">
            <attr name="direction" />
        </declare-styleable>
    
        <declare-styleable name="BannerView">
            <attr name="direction" />
            <attr name="thickness" format="dimension" />
        </declare-styleable>
    </resources>
    

    When you build your xml layout for TriangleView or BannerView, you can use the app:direction="NW" example for both. In the constructors with AttributeSet in TriangleView or BannerView, the attributes will have the same format as the original, but what you do with that value is dependent on your implementation of the constructors in each respective view (can be the same or different for both).

    If you want attributes to be defined differenly (ie. different "format" or "enum") for different views, then you have to create different attributes with different names.