Android: Is it possible to use string/enum in drawable selector?

微笑、不失礼 提交于 2020-07-05 05:26:11

问题


Questions

Q1: Has anyone managed to get custom string/enum attribute working in xml selectors? I got a boolean attribute working by following [1], but not a string attribute.

EDIT: Thanks for answers. Currently android supports only boolean selectors. See accepted answer for the reason.

I'm planning to implement a little complex custom button, whose appearance depends on two variables. Other will be a boolean attribute (true or false) and another category-like attribute (has many different possible values). My plan is to use boolean and string (or maybe enum?) attributes. I was hoping I could define the UI in xml selector using boolean and string attribute.

Q2: Why in [1] the onCreateDrawableState(), boolean attributes are merged only if they are true?

This is what I tested, boolean attribute works, string doesn't

NOTE: This is just a test app to figure out if string/enum attribute is possible in xml selector. I know that I could set button's textcolor without a custom attribute.

In my demo application, I use a boolean attribute to set button background to dark/bright and string attribute to set text color, one of {"red", "green", "blue"}. Attributes are defined in /res/values/attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyCustomButton">
        <attr name="make_dark_background" format="boolean" />
        <attr name="str_attr" format="string" />
    </declare-styleable>
</resources>

Here are the selectors I want to achieve:

@drawable/custom_button_background (which works)

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

    <item app:make_dark_background="true" android:drawable="@color/dark" />
    <item android:drawable="@color/bright" />

</selector>

@color/custom_button_text_color (which does not work)

<selector 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res/com.example.customstringattribute">

    <item app:str_attr="red" android:color="@color/red" />
    <item app:str_attr="green" android:color="@color/green" />
    <item app:str_attr="blue" android:color="@color/blue" />

    <item android:color="@color/grey" />

</selector>

Here is how custom button background is connected to boolean selector, and text color is connected to string selector.

<com.example.customstringattribute.MyCustomButton
    ...
    android:background="@drawable/custom_button_background"
    android:textColor="@color/custom_button_text_color"
    ...
/>

Here is how attributes are loaded in the init() method:

private void init(AttributeSet attrs) {

    TypedArray a = getContext().obtainStyledAttributes(attrs,
            R.styleable.MyCustomButton);

        final int N = a.getIndexCount();
        for (int i = 0; i < N; ++i)
        {
            int attr = a.getIndex(i);
            switch (attr)
            {
                case R.styleable.MyCustomButton_str_attr:
                    mStrAttr = a.getString(attr);
                    break;
                case R.styleable.MyCustomButton_make_dark_background:
                    mMakeDarkBg  = a.getBoolean(attr, false);
                    break;
            }
        }
        a.recycle();
}

I have the int[] arrays for the attributes

private static final int[] MAKE_DARK_BG_SET = { R.attr.make_dark_background };
private static final int[] STR_ATTR_ID = { R.attr.str_attr };

And those int[] arrays are merged to drawable state

@Override
protected int[] onCreateDrawableState(int extraSpace) {
    Log.i(TAG, "onCreateDrawableState()");
    final int[] drawableState = super.onCreateDrawableState(extraSpace + 2);
    if(mMakeDarkBg){
        mergeDrawableStates(drawableState, MAKE_DARK_BG_SET);
    }
    mergeDrawableStates(drawableState, STR_ATTR_ID);
    return drawableState;
}

I also have refreshDrawableState() in my attribute setter methods:

public void setMakeDarkBg(boolean makeDarkBg) {
    if(mMakeDarkBg != makeDarkBg){
        mMakeDarkBg = makeDarkBg;
        refreshDrawableState();
    }
}

public void setStrAttr(String str) {
    if(mStrAttr != str){
        mStrAttr = str;
        refreshDrawableState();
    }
}

[1] : How to add a custom button state


回答1:


Q1:

When you open the source-code of StateListDrawable.java, you can see this piece of code in the inflate method that reads the drawable xml selector: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/graphics/java/android/graphics/drawable/StateListDrawable.java

        ...

        for (i = 0; i < numAttrs; i++) {
            final int stateResId = attrs.getAttributeNameResource(i);
            if (stateResId == 0) break;
            if (stateResId == com.android.internal.R.attr.drawable) {
                drawableRes = attrs.getAttributeResourceValue(i, 0);
            } else {
                states[j++] = attrs.getAttributeBooleanValue(i, false)
                        ? stateResId
                        : -stateResId;
            }
        }
        ...

attrs are the attributes of each <item> element in the <selector>.

In this for-loop it gets the android:drawable, the various android:state_xxxx and custom app:xxxx attributes. All but the android:drawable attributes seem to be interpreted as booleans only: attrs.getAttributeBooleanValue(....) is called.

I think this is the answer, based on the source code:

You can only add custom boolean attributes to your xml, not any other type (including enums).

Q2:

I'm not sure why the state is merged only if it is specifically set to true. I would suspect the code should have looked like this instead:

private static final int[] MAKE_DARK_BG_SET     = {  R.attr.make_dark_background };
private static final int[] NOT_MAKE_DARK_BG_SET = { -R.attr.make_dark_background };
....
....
@Override
protected int[] onCreateDrawableState(int extraSpace) {
    Log.i(TAG, "onCreateDrawableState()");
    final int[] drawableState = super.onCreateDrawableState(extraSpace + 2);
    mergeDrawableStates(drawableState, mMakeDarkBg? MAKE_DARK_BG_SET : NOT_MAKE_DARK_BG_SET);
    //mergeDrawableStates(drawableState, STR_ATTR_ID);
    return drawableState;
}



回答2:


Q1:

I haven't tried this myself, but:

Have you tried placing your @color/custom_button_text_color.xml in the drawable folder? (Just to be sure, there's a bit of folder magic here and there in Android and I'm not sure about this one.)

Q2:

There are two use cases for state sets. One is to explicitly declare selectors for stateful drawables programmatically. In this case, for selectors, you need to be able to tell Android to use this drawable if an attribute is not set. To express this, you can include the negated criteria (preceded by a minus sign) in the int[].

While this is barely mentioned anywhere in the context of selector criteria, it is never mentioned for drawable states themselves (aka the representation of the drawable's state). So one is definitely on the safe side if one does not include negated state IDs in the set; the provided Android implementations also do not includde them.




回答3:


Sorry, you cannot create custom drawables in xml : https://groups.google.com/d/msg/android-developers/glpdi0AdMzI/LpW4HGMB3VIJ



来源:https://stackoverflow.com/questions/13147360/android-is-it-possible-to-use-string-enum-in-drawable-selector

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!