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?
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);
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With