androidandroid-layoutdeclare-styleablestyleable

Working with styleable


I wanted to make a view containing a progress view and a button. Through the view's xml I wanted to add fields that define button and porgress bar style.

What I've done so far but it does not work:

   <io.**.**.view.ButtonLoading android:id="@+id/b_recover_password"
                                       android:layout_width="wrap_content"
                                       android:gravity="center"
                                       app:styleButton="@style/ButtonGrey"
                                       app:styleProgress="@style/ProgressBar"
                                       app:textButton="@string/recover_password"
                                       android:layout_height="wrap_content"/>

Code:

   a = context.getTheme().obtainStyledAttributes(
            attrs,
            R.styleable.ButtonLoading,
            0, 0);

    int buttonId = 0;// R.style.Widget_AppCompat_Button;
    try {
        buttonId = a.getResourceId(R.styleable.ButtonLoading_styleButton, 0);
    } catch (Exception e) {
    }

    button = new Button(getContext(), attrs, buttonId);

    LayoutParams lpButton = createLayoutParams();
    button.setLayoutParams(lpButton);
    color = button.getCurrentTextColor();

    int progressId = 0;// R.style.Widget_AppCompat_ProgressBar;
    try {
        progressId = a.getResourceId(R.styleable.ButtonLoading_styleProgress, 0);
    } catch (Exception e) {
    }

    progressBar = new ProgressBar(getContext(), attrs, progressId);
    LayoutParams lpProgressBar = createLayoutParams();
    lpProgressBar.addRule(RelativeLayout.CENTER_IN_PARENT);
    progressBar.setLayoutParams(lpProgressBar);

    LayoutParams lpRelativeLayout = createLayoutParams();
    setLayoutParams(lpRelativeLayout);

    addView(button);
    addView(progressBar);

    try {
        String value = a.getString(R.styleable.ButtonLoading_textButton);
        button.setText(value);
    } catch (Exception e) {
    }
    a.recycle();

Styleable:

   <declare-styleable name="ButtonLoading">
    <attr name="styleButton" format="reference" />
    <attr name="styleProgress" format="reference" />
    <attr name="textButton" format="string" />
</declare-styleable>

Someone help me? thanks


Solution

  • The problem is in your constructors for Button and ProgressBar. Let's consider two of the constructors. (Emphasis is mine.) (Documentation)

    View(Context context, AttributeSet attrs, int defStyleAttr) [Appropriate for below API 21]

    Perform inflation from XML and apply a class-specific base style from a theme attribute. This constructor of View allows subclasses to use their own base style when they are inflating. For example, a Button class's constructor would call this version of the super class constructor and supply R.attr.buttonStyle for defStyleAttr; this allows the theme's button style to modify all of the base view attributes (in particular its background) as well as the Button class's attributes.

    View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) [Appropriate for API 21+]

    Perform inflation from XML and apply a class-specific base style from a theme attribute or style resource. This constructor of View allows subclasses to use their own base style when they are inflating.

    Focusing on the last two arguments.

    defStyleAttr int: An attribute in the current theme that contains a reference to a style resource that supplies default values for the view. Can be 0 to not look for defaults.

    defStyleRes int: A resource identifier of a style resource that supplies default values for the view, used only if defStyleAttr is 0 or can not be found in the theme. Can be 0 to not look for defaults.

    It is confusing because there are resource ids pointing to resource ids, but bear with me.

    What you have defined for buttonId and progressId is a resource id that points to a style (<style...). This style resource id is appropriate to use for the defStyleRes attribute of the Button and ProgressBar constructors as noted .above. You are trying to use each of these resource values as an id that points to an attribute in the current theme. In other words, you are saying that R.attr.styleButton (styleButton) is an attribute in the current theme which it is not as currently defined. To fix this, change the theme style to the following:

    styles.xml

    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="styleButton">@style/ButtonGrey</item>
        ...
    </style>        
    

    To make what you have work with API 21+ you can leave the leave the style and layout files as they are and change the custom view code to something like the following. (I am only showing code for the button, but the progress bar would be similar.) You may want to create a separate style file for API 21+ (styles-v21.xml).

    public CustomViewGroup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.getTheme().obtainStyledAttributes(
                attrs,
                R.styleable.ButtonLoading,
                0, 0);
        int defStyleRes = 0;
        try {
            defStyleRes = a.getResourceId(R.styleable.ButtonLoading_styleButton, 0);
        } catch (Exception e) {
          // do something
        }
        Button button;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            // defStyleRes is only used if defStyleAttr == 0 
            // or can't be found in the current theme.
            button = new Button(getContext(), attrs, defStyleAttr, defStyleRes);
        } else {
            button = new Button(getContext(), attrs, 
                         (defStyleAttr != 0) ? defStyleAttr : R.attr.styleButton);
        }
        try {
            String value = a.getString(R.styleable.ButtonLoading_textButton);
            button.setText(value);
        } catch (Exception e) {
          // do something
        }
        addView(button);
        a.recycle();
    }
    

    Setting the text of the button proceeds as you have coded. Styles are trickier. To makeapp:styleButton="@style/ButtonGrey" work as you intended for all APIs, I think that you would have to do something like this.

    I hope this helps.