Search code examples
javaandroidandroid-layoutandroid-resourcesandroid-theme

How to get a standard color of the neutral button


I am using button with style="?android:attr/buttonBarNeutralButtonStyle"

<Button
    style="?android:attr/buttonBarNeutralButtonStyle"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:text="Sign in" />

I would like to get its color, so I can use it with other view elements as well.

Currently I am reading its color value like this:

int color;

View button = findViewById(R.id.passwordSigninButton);
if ((button != null) && (button instanceof Button))
  color = ((Button) button).getCurrentTextColor();
// -16738680

...and it works fine. But I would prefer to get the color associated with the applicable style directly, without need to use of actual button, in case I want to use it without button being in my layout.

So I tried this approach:

TypedValue typedValue = new TypedValue();

getApplicationContext().getTheme().resolveAttribute(
    android.R.attr.buttonBarNeutralButtonStyle, typedValue, true);

TypedArray typedArray = context.obtainStyledAttributes(
    typedValue.data, new int[]{android.R.attr.textColor});

int color = typedArray.getColor(0, -1);
// -1

typedArray.recycle();

But I am getting -1, which means I am not getting the color I expected.

How can I get the color from the android.R.attr.buttonBarNeutralButtonStyle style?


Solution

  • See update for direct derivation below.

    Your approach to determining the color of neutral button text is trying to duplicate the steps that Android takes in determining the color. This processing is under the hood and can change from release to release as I think we are seeing from the responses to this question. I am sure that the Android developers feel free to change the underlying implementation as long as the result is the same, so, even if you can get a solution that works today, it will be fragile and may break tomorrow.

    You do have a solution, but it involves creating a layout that you want to avoid. I suggest the following approach which, I believe, will serve your purpose, will be a little more robust and will avoid the need for a layout file. You indicate that you are using android.support.v7.app.AppCompatDialog which creates AppCompatButton buttons.

    The following two lines will give you the color of the neutral button text without a layout or the complication of extracting attributes. I have tested this on an emulator running API 22 and a Samsung S7 running API 24 and both results are consistent with what is seen with an explicit layout.

    android.support.v7.widget.AppCompatButton button =
        new android.support.v7.widget.AppCompatButton(this, null,
        android.R.attr.buttonBarNeutralButtonStyle);
    int color = button.getCurrentTextColor();
    

    Direct Derivation

    Although I believe that the above solution is the best, the following code will derive the default color of the neutral button for all APIs. This approach simply looks for the textColor attribute and, if that is not defined, looks for textAppearance and looks at the textColor defined there. It looks like the difference between the different APIs is how the color is specified. This solution may be robust enough to withstand subsequent updates but caveat emptor.

    MainActivity.java

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            android.support.v7.widget.AppCompatButton button =
                new android.support.v7.widget.AppCompatButton(this, null, android.R.attr.buttonBarNeutralButtonStyle);
            Log.d("MainActivity", String.format("<<<< Button color = 0x%08X", button.getCurrentTextColor()));
            Log.d("MainActivity", String.format("<<<< Derived color = 0x%08X", getNeutralButtonColor(Color.WHITE)));
        }
    
        private int getNeutralButtonColor(int defaultColor) {
            TypedArray ta;
            int color = defaultColor;
            boolean colorFound = false;
            int textColorResId;
            final int baseAttr = R.attr.buttonBarNeutralButtonStyle;
    
            // Look for an explicit textColor attribute and use it if it is defined.
            ta = getTheme().obtainStyledAttributes(null, new int[]{android.R.attr.textColor}, baseAttr, 0);
            textColorResId = ta.getResourceId(0, -1);
            if (textColorResId == -1) { // try to get color if not resource id
                int type = ta.getType(0);
                if (type >= TypedValue.TYPE_FIRST_COLOR_INT && type <= TypedValue.TYPE_LAST_COLOR_INT) {
                    color = ta.getColor(0, defaultColor);
                    colorFound = true;
                }
            }
            ta.recycle();
    
            if (textColorResId == -1 && !colorFound) {
                // No color, yet. See if textAppearance is defined.
                ta = obtainStyledAttributes(null, new int[]{android.R.attr.textAppearance},
                                            baseAttr, 0);
                int textAppearanceId = ta.getResourceId(0, -1);
                if (textAppearanceId != -1) {
                    // OK, textAppearance is defined. Get the embedded textColor.
                    ta.recycle();
                    ta = obtainStyledAttributes(textAppearanceId, new int[]{android.R.attr.textColor});
                    textColorResId = ta.getResourceId(0, -1);
                    if (textColorResId == -1) { // try to get color if not resource id
                        int type = ta.getType(0);
                        if (type >= TypedValue.TYPE_FIRST_COLOR_INT && type <= TypedValue.TYPE_LAST_COLOR_INT) {
                            color = ta.getColor(0, defaultColor);
                            colorFound = true;
                        }
                    }
                }
                ta.recycle();
            }
    
            if (textColorResId != -1 && !colorFound) {
                ColorStateList colorStateList = AppCompatResources.getColorStateList(this, textColorResId);
                if (colorStateList != null) {
                    color = colorStateList.getDefaultColor();
                    colorFound = true; // in case needed later
                }
            }
    
            return color;
        }
    
        @SuppressWarnings("unused")
        private static final String TAG = "MainActivity";
    }