How to explore styling in android

android, styling

Solution

I find that styling is about sherlocking your way through the framework. The what (almost always) comes from the widget's implementation. The where, I find is all over the place. I will try my best to explain the process through your particular use-case - AlertDialog's button(s).

Starting off:

You already have this figured out: we start with the widget's source code. We are specifically trying to find - where AlertDialog buttons get their text-color. So, we start with looking at where these buttons come from. Are they being explicitly created at runtime? Or are they defined in an xml layout, which is being inflated?

In source code, we find that `mAlert` handles the button options among other things:

public void setButton(int whichButton, CharSequence text, Message msg) {
    mAlert.setButton(whichButton, text, null, msg);
}

`mAlert` is an instance of `AlertController`. In its constructor, we find that the attribute `alertDialogStyle` defines the xml layout:

TypedArray a = context.obtainStyledAttributes(null,
            com.android.internal.R.styleable.AlertDialog,
            com.android.internal.R.attr.alertDialogStyle, 0);

    mAlertDialogLayout = 
            a.getResourceId(
            com.android.internal.R.styleable.AlertDialog_layout,
            com.android.internal.R.layout.alert_dialog);

So, the layout we should look at is `alert_dialog.xml` - `[sdk_folder]/platforms/android-21/data/res/layout/alert_dialog.xml`:

The layout xml is quite long. This is the relevant part:

<LinearLayout>

....
....

<LinearLayout android:id="@+id/buttonPanel"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="54dip"
    android:orientation="vertical" >
    <LinearLayout
        style="?android:attr/buttonBarStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:paddingTop="4dip"
        android:paddingStart="2dip"
        android:paddingEnd="2dip"
        android:measureWithLargestChild="true">
        <LinearLayout android:id="@+id/leftSpacer"
            android:layout_weight="0.25"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:visibility="gone" />
        <Button android:id="@+id/button1"
            android:layout_width="0dip"
            android:layout_gravity="start"
            android:layout_weight="1"
            style="?android:attr/buttonBarButtonStyle"
            android:maxLines="2"
            android:layout_height="wrap_content" />
        <Button android:id="@+id/button3"
            android:layout_width="0dip"
            android:layout_gravity="center_horizontal"
            android:layout_weight="1"
            style="?android:attr/buttonBarButtonStyle"
            android:maxLines="2"
            android:layout_height="wrap_content" />
        <Button android:id="@+id/button2"
            android:layout_width="0dip"
            android:layout_gravity="end"
            android:layout_weight="1"
            style="?android:attr/buttonBarButtonStyle"
            android:maxLines="2"
            android:layout_height="wrap_content" />
        <LinearLayout android:id="@+id/rightSpacer"
            android:layout_width="0dip"
            android:layout_weight="0.25"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:visibility="gone" />
    </LinearLayout>

We now know that the buttons get the style held by attribute `buttonBarButtonStyle`.

Head over to `[sdk_folder]/platforms/android-21/data/res/values/themes.material.xml` and search for `buttonBarButtonStyle`:

<!-- Defined under `<style name="Theme.Material">` -->
<item name="buttonBarButtonStyle">@style/Widget.Material.Button.ButtonBar.AlertDialog</item>

<!-- Defined under `<style name="Theme.Material.Light">` -->
<item name="buttonBarButtonStyle">@style/Widget.Material.Light.Button.ButtonBar.AlertDialog</item>

Depending on what your activity's parent theme is, `buttonBarButtonStyle` will refer to one of these two styles. For now, let's assume your activity's theme extends `Theme.Material`. We'll look at `@style/Widget.Material.Button.ButtonBar.AlertDialog`:

Open `[sdk_folder]/platforms/android-21/data/res/values/styles_material.xml` and search for `Widget.Material.Button.ButtonBar.AlertDialog`:

<!-- Alert dialog button bar button -->
<style name="Widget.Material.Button.ButtonBar.AlertDialog" parent="Widget.Material.Button.Borderless.Colored">
    <item name="minWidth">64dp</item>
    <item name="maxLines">2</item>
    <item name="minHeight">@dimen/alert_dialog_button_bar_height</item>
</style>

Okay. But these values don't help us in determining the button's text color. We should look at the parent style next - `Widget.Material.Button.Borderless.Colored`:

<!-- Colored borderless ink button -->
<style name="Widget.Material.Button.Borderless.Colored">
    <item name="textColor">?attr/colorAccent</item>
    <item name="stateListAnimator">@anim/disabled_anim_material</item>
</style>

At last, we find the `textColor` - and its supplied by `attr/colorAccent` initialized in `Theme.Material`:

<item name="colorAccent">@color/accent_material_dark</item>

For `Theme.Material.Light`, `colorAccent` is defined as:

<item name="colorAccent">@color/accent_material_light</item>

Browse to `[sdk_folder]/platforms/android-21/data/res/values/colors_material.xml` and locate these colors:

<color name="accent_material_dark">@color/material_deep_teal_200</color>
<color name="accent_material_light">@color/material_deep_teal_500</color>

<color name="material_deep_teal_200">#ff80cbc4</color>
<color name="material_deep_teal_500">#ff009688</color>

Screenshot of an AlertDialog and the corresponding text-color:

Shortcut:

Sometimes, its easier to read the color value (as in the picture above) and search for it using AndroidXRef. This approach would not have been useful in your case since `#80cbc4` would have only pointed out that its the accent color. You would still have to locate `Widget.Material.Button.Borderless.Colored` and tie it with attribute `buttonBarButtonStyle`.

Changing button's text-color:

Ideally, we should create a style that extends `Widget.Material.Button.ButtonBar.AlertDialog`, override `android:textColor` inside it, and assign it to attribute `buttonBarButtonStyle`. But, this won't work - your project won't compile. This is because `Widget.Material.Button.ButtonBar.AlertDialog` is a non-public style and hence cannot be extended. You can confirm this by checking Link.

We'll do the next best thing - extend the parent style of `Widget.Material.Button.ButtonBar.AlertDialog` - `Widget.Material.Button.Borderless.Colored` which is public.

<style name="CusButtonBarButtonStyle" 
       parent="@android:style/Widget.Material.Button.Borderless.Colored">
    <!-- Yellow -->
    <item name="android:textColor">#ffffff00</item>

    <!-- From Widget.Material.Button.ButtonBar.AlertDialog -->
    <item name="android:minWidth">64dp</item>
    <item name="android:maxLines">2</item>
    <item name="android:minHeight">@dimen/alert_dialog_button_bar_height</item>
</style>

Note that we add 3 more items after overriding `android:textColor`. These are from non-public style `Widget.Material.Button.ButtonBar.AlertDialog`. Since we cannot extend it directly, we must include the items it defines. Note: the dimen value(s) will have to be looked up and transferred to appropriate `res/values(-xxxxx)/dimens.xml` files(s) in your project.

The style `CusButtonBarButtonStyle` will be assigned to attribute `buttonBarButtonStyle`. But the question is, how will an AlertDialog know of this? From the source code:

protected AlertDialog(Context context) {
    this(context, resolveDialogTheme(context, 0), true);
}

Passing `0` as the second argument for `resolveDialogTheme(Context, int)` will end up in the `else` clause:

static int resolveDialogTheme(Context context, int resid) {
    if (resid == THEME_TRADITIONAL) {
        ....
    } else {
        TypedValue outValue = new TypedValue();
        context.getTheme().resolveAttribute(
                com.android.internal.R.attr.alertDialogTheme,
                outValue, true);
        return outValue.resourceId;
    }
}

We now know that the theme is held by `alertDialogTheme` attribute. Next, we look at what `alertDialogTheme` points to. The value of this attribute will depend on your activity's parent theme. Browse to your sdk folder and find the `values/themes_material.xml` inside android-21. Search for `alertDialogTheme`. Results:

<!-- Defined under `<style name="Theme.Material">` -->
<item name="alertDialogTheme">@style/Theme.Material.Dialog.Alert</item>

<!-- Defined under `<style name="Theme.Material.Light">` -->
<item name="alertDialogTheme">@style/Theme.Material.Light.Dialog.Alert</item>

<!-- Defined under `<style name="Theme.Material.Settings">` -->
<item name="alertDialogTheme">@style/Theme.Material.Settings.Dialog.Alert</item>

So, based on what your activity's base theme is, `alertDialogTheme` will hold one of these 3 values. To let AlertDialog know of `CusButtonBarButtonStyle`, we need to override attribute `alertDialogTheme` in our app's theme. Say, we're using `Theme.Material` as the base theme.

<style name="AppTheme" parent="android:Theme.Material">
    <item name="android:alertDialogTheme">@style/CusAlertDialogTheme</item>
</style>

From above, we know that `alertDialogTheme` points to `Theme.Material.Dialog.Alert` when your app's base theme is `Theme.Material`. So, `CusAlertDialogTheme` should have `Theme.Material.Dialog.Alert` as its parent:

<style name="CusAlertDialogTheme" 
       parent="android:Theme.Material.Dialog.Alert">
    <item name="android:buttonBarButtonStyle">@style/CusButtonBarButtonStyle</item>
</style> 

Result:

So, instead of accepting fish to eat, can you teach me to fish instead?

In the very least, I hope to have explained where the fish are.

P.S. I realize I have posted a mammoth.

Problem

I'm trying to theme my app in Android. However, each widget is an excrutiating pain in itself: I have to search for theming that particular widget and then create a style that hopefully derives from same style that widget uses. Of course, answers about theming a particular widget don't always contain info about base style, just the particular colors. So, instead of accepting fish to eat, can you teach me to fish instead? How do I interpret those `ObtainStyledAttributes()` calls in widget constructors and extract styles from that? How do I recurse that? In particular, can you walk me through `AlertDialog` button color? What style defines lollipop flat button + teal text color? How do I get to that style if I start from AlertDialog source and ObtainStyledAttributes call?

Original source