Applying AppCompat theme to individual preferences in a PreferenceFragment

混江龙づ霸主 提交于 2019-12-02 20:37:45
Eugen Pechanec

UPDATE: I made a library to battle the issue (uses AppCompat r22): https://github.com/consp1racy/android-support-preference


The ListPreference extends DialogPreference which uses this piece of code to create the dialog:

mBuilder = new AlertDialog.Builder(context)
    .setTitle(mDialogTitle)
    .setIcon(mDialogIcon)
    .setPositiveButton(mPositiveButtonText, this)
    .setNegativeButton(mNegativeButtonText, this);

As you can see the AlertDialog.Builder constructor is not supplied with the second optional int theme parameter. That means the dialog will be themed by whatever your activity's theme has in its android:alertDialogTheme attribute.

Now you have to create a custom theme for your dialog which derives from Theme.AppCompat.Dialog like so:

<style name="Theme.YourApp.Dialog.Alert" parent="Theme.AppCompat.Light.Dialog">
  <item name="android:windowBackground">@android:color/transparent</item>
  <item name="android:windowContentOverlay">@null</item>
  <item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item>
  <item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item>
  <item name="colorAccent">@color/accent_yourapp</item>
  <item name="colorPrimary">@color/primary_yourapp</item>
  <item name="colorPrimaryDark">@color/primary_dark_yourapp</item>
</style>

Problem 1: The above solution will not work for RingtonePreference because it does not extend ListPreference but calls a ringtone chooser intent, so it's always themed according to system. Check out this answer: RingtonePreference Theme So we can mark this as solved.

Problem 2: The AppCompat dialogs lack title. And so far I haven't found a way to fix this. True that I am not looking hard enough. Let's ignore the title absence as a minor issue.

Problem 3: The radio button drawables are not mutated so the graphics are inconsistent between passive and active (colored) state - all are colored (not just the one you!re pressing) or all are grey. Now this is really annoying

Problems 2 & 3 forced me to take another route - my dialog theme looks like this on API 14+

<style name="Theme.MyApp.Dialog.Alert" parent="android:Theme.DeviceDefault.Light.Dialog.MinWidth">
  <item name="android:windowBackground">@android:color/transparent</item>
  <item name="android:windowContentOverlay">@null</item>
</style>

and like this on API 21+

<style name="Theme.YourApp.Dialog.Alert" parent="android:Theme.DeviceDefault.Light.Dialog.MinWidth">
  <item name="android:colorPrimary">@color/primary_yourapp</item>
  <item name="android:colorPrimaryDark">@color/primary_dark_yourapp</item>
  <item name="android:colorAccent">@color/accent_yourapp</item>
</style>

These values have been experimentally gained by crawling through the platform's source files and well tested.

The point is that the only reliable solution seems to be using the device default dialog theme. The only choice before Lollipop is a light or dark variant. On Lollipop this will work as intended and requested.

EDIT: Since appcompat-v7-r21.1.0 you can use AppCompatDialog which is material themed variant of native AlertDialog.

You can use provided AlertDialog.Builder (not to be confused with its native counterpart) for creating its instances.

DialogPreference uses android.app.AlertDialog.Builder directly so it would be impossible to switch the displayed dialog to the material design one for all API levels (which requires using android.support.v7.widget.AlertDialog.Builder).

I believe the only option for now is to extend and override dialog creation logic in DialogPreference or its subclasses (and use reflection to work around private accessors if required). Check out this reddit thread and an example AppCompatListPreference made by the author of that thread.

Taking cues from the answers already given I set in the manifest a custom theme for Settings activity because it is necessary to remove the border background around the Dialog that appear, using a transparent background this only for API <21:

AndroidManifest.xml

<application
    android:name=".MyApplication"
    ...
    android:theme="@style/AppTheme">
    ...
    <activity
        android:name=".ui.SettingsActivity"
        ...
        android:theme="@style/AppTheme.Settings">
    </activity>
</application>

values/style.xml

<!-- Application theme -->
<style name="AppTheme" parent="AppTheme.Base">
    <!-- All customizations that are NOT specific to a particular API-level can go here. -->

</style>

<style name="AppTheme.Base" parent="Theme.AppCompat.Light.NoActionBar">
    ...
    <item name="android:alertDialogTheme">@style/AppTheme.AlertDialog</item>
</style>

<!-- Must be different because of transparent background -->
<style name="AppTheme.Settings" parent="AppTheme.Base">
    <item name="android:alertDialogTheme">@style/AppTheme.SettingsAlertDialog</item>
</style>

<style name="AppTheme.AlertDialog" parent="Theme.AppCompat.Light.Dialog.Alert">
    <item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item>
    <item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item>
    <item name="android:windowContentOverlay">@null</item>
    <item name="colorAccent">@color/accent_light</item>
    <item name="colorControlActivated">@color/accent_light</item>
    <item name="colorControlHighlight">@color/primary_highlight</item>
</style>

<style name="AppTheme.SettingsAlertDialog" parent="Theme.AppCompat.Light.Dialog.Alert">
    <item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item>
    <item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item>
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowContentOverlay">@null</item>
    <item name="colorAccent">@color/accent_light</item>
    <item name="colorControlActivated">@color/accent_light</item>
    <item name="colorControlHighlight">@color/primary_highlight</item>
</style>

finally for values-v21/style.xml the normal dialog, if you don't put this in v21 dialogs will be transparent.

<style name="AppTheme.Settings" parent="AppTheme.Base">
    <item name="android:alertDialogTheme">@style/AppTheme.AlertDialog</item>
</style>

Actually I've the problem to set the colors directly from the Theme attributes: ?android:colorAccent instead of my fixed color @color/accent_light.

You should check out this Gist about how different components use the theme color attributes. You might need to add the following attributes to your theme to get the effect you want:

<item name="colorControlNormal">@color/x_color</item>
<item name="colorControlActivated">@color/y_color</item>
<item name="colorControlHighlight">@color/z_color</item>
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!