Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a general way to find the styles that apply to a View?

How, in general, do you find what theme attribute to override in order to alter the look of any UI element?

Currently I rely on trawling through framework source files: the theme definitions in values.xml (usually the support library variant), attribute definitions in attrs.xml, and the R.styleable class docs.

But this is totally hit-and-miss. It's not only unduly time-consuming, but sometimes I miss entirely, for instance I've been trying without success to find out how to change the text styles in a DatePickerDialog's OK and Cancel buttons. Feel free to use that as an example, but if you do, please outline your discovery process. The answer I'm looking for is how to discover the applied styles for any UI element,

Or is there just no deterministic way to find out? Do you just have to know?

like image 612
Cris Avatar asked Sep 03 '15 10:09

Cris


1 Answers

Finding how to change styles for widgets on Android has always been troublesome. For example, DatePickerDialog has different styles for Holo and Material design. So, the style of the dialog may depend on the SDK value or if you are using the AppCompat library. There is also little documentation.

It would be nice if tools like Hierarchy Viewer showed attributes and the current theme of a widget. However, I haven't come across such a tool.

We can get the current theme and attributes after the view is created. Here are a couple methods I wrote and tested on a DatePickerDialog to find the style being used:


Get the current theme from a context:

static String getThemeNameFromContext(Context context) {
    Resources.Theme theme = context.getTheme();
    String themeName;
    try {
        Field field = theme.getClass().getDeclaredField("mThemeResId");
        if (!field.isAccessible()) {
            field.setAccessible(true);
        }
        int themeResId = field.getInt(theme);
        themeName = context.getResources().getResourceEntryName(themeResId);
    } catch (Exception e) {
        // If we are here then the context is most likely the application context.
        // The theme for an application context is always "Theme.DeviceDefault"
        themeName = "Theme.DeviceDefault";
    }
    return themeName;
}

Get the name/value of an attribute:

static String getResourceName(Context context, int attribute) {
    TypedArray typedArray = context.obtainStyledAttributes(new int[]{attribute});
    try {
        int resourceId = typedArray.getResourceId(0, 0);
        return context.getResources().getResourceEntryName(resourceId);
    } finally {
        typedArray.recycle();
    }
}

In the example below I created a DatePickerDialog and got the theme and attribute values being used by the dialog and the dialog's positive button using the above methods:

// Create the DatePickerDialog
DatePickerDialog datePickerDialog = new DatePickerDialog(getActivity(),
        new DatePickerDialog.OnDateSetListener() {

            @Override
            public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {

            }
        }, 2015, Calendar.SEPTEMBER, 9);
// Show the dialog
datePickerDialog.show();

// Get the positive button from the dialog:
Button positiveButton = datePickerDialog.getButton(DatePickerDialog.BUTTON_POSITIVE);
// Get the theme used by this dialog
String theme = getThemeNameFromContext(datePickerDialog.getContext());
// Get the date picker style used by the dialog
String datePickerStyle = getResourceName(datePickerDialog.getContext(), android.R.attr.datePickerStyle);
// Get the style of the positive button:
String buttonStyle = getResourceName(positiveButton.getContext(), android.R.attr.buttonStyle);

Log.i("LOGTAG", "Theme: " + theme);
Log.i("LOGTAG", "datePickerStyle: " + positiveButton);
Log.i("LOGTAG", "buttonStyle: " + buttonStyle);

In my test project I got these values for the theme, datePickerStyle, and buttonStyle:

Theme: ThemeOverlay.Material.Dialog

datePickerStyle: Widget.Material.Light.DatePicker

buttonStyle: Widget.Material.Light.Button


This is somewhat helpful, but we still haven't changed the positive and negative button style. If we view the source for DatePickerDialog we can see that it extends AlertDialog. This makes things harder as you will need to set a custom style in your theme which would affect all AlertDialog buttons. If you need an example on how to change the buttonStyle please leave a comment.

A better approach would be to style the positive and negative buttons after the dialog is visible. For example, in your DialogFragment you can place the following code in onStart() to style the buttons:

@Override
public void onStart() {
    super.onStart();
    DatePickerDialog dialog = (DatePickerDialog) getDialog();
    Button btnPos = dialog.getButton(DatePickerDialog.BUTTON_POSITIVE);
    Button btnNeg = dialog.getButton(DatePickerDialog.BUTTON_NEGATIVE);

    /* customize the buttons here */
    btnPos.setText("CUSTOM");
    btnPos.setTextAppearance(android.R.style.TextAppearance_Large);
    btnNeg.setTextColor(Color.RED);
}

enter image description here


Conclusion:

You can use the above methods on other views to find the style that applies for that view. It is still a pain in the a** to style widgets on Android. You may need to dig through source code and XML from time to time.

like image 62
Jared Rummler Avatar answered Nov 15 '22 16:11

Jared Rummler