Apply tint to PreferenceActivity widgets with AppCompat v21
Asked Answered
K

5

16

I'm using CheckboxPreference in a PreferenceActivity and an AppCompat theme from the v21 support library. As you already know, with this latest library widgets like checkboxes, editTexts, radio buttons etc are tinted with the secondary color defined in the theme. In the preference screen, text is in the right color as specifified by my theme, but checkboxes and edittext are not. It seems that when the CheckboxPreference instance creates the widget, it doesn't apply my theme to it.

Radio buttons in a normal layout, tinted:

screenshot 1

Checkbox from the CheckboxPreference, not tinted:

screenshot 2

I'm using as the parent theme Theme.AppCompat.Light.NoActionBar. This happens to every subclass of Preference with a widget, like EditTextPreference to say one, where the EditText has a black bottom line, instead of a tinted line. How can I apply the tint to the widgets shown by the Preference subclasses?

UPDATE: tinting is not applied because PreferenceActivity extends the framework Activity. In the working case, I'm using an ActionBarActivity from the support library. Now the question is: how come?

Keller answered 20/10, 2014 at 13:13 Comment(3)
I have also hit the wall with widget tinting and CheckBox widget. I am using CheckBox control in ActionBarActivity layout and it doesn't get tinted. All I get is default black rect. Bummer.Comus
Moreover, it has been officially stated that this is working as intended (see the FAQ at the end) android-developers.blogspot.it/2014/10/…Keller
I'm using ActionBarActivity in my Preferences Activity and the tint work's on Checkbox. But inside an ListPreference the Checkbox is not tinted, only after clicking...Hiramhirasuna
K
2

So far, my own (sad) workaround was to create from scratch my own checkbox drawables, using the colors which the checkbox should have been tinted with in the first place.

In styles.xml:

<?xml version="1.0" encoding="utf-8"?>
<style name="AppThemeBase" parent="Theme.AppCompat.Light.NoActionBar">
...
<!-- edit the checkbox appearance -->
<item name="android:listChoiceIndicatorMultiple">@drawable/my_checkbox</item>
...
</style>

drawable/my_checkbox.xml:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true" android:drawable="@drawable/checkbox_on" />
    <item android:drawable="@drawable/checkbox_off" />
</selector>

checkbox_on" andcheckbox_off` are the PNG drawables for the selected and unselected states, obviously one for each screen density. If you mind dimension consistency, the baseline (MDPI) dimension of the drawables should be 32px full asset and 18px optical square.

Keller answered 20/10, 2014 at 18:53 Comment(2)
I accepted this because it pertains to the question and PreferenceActivity usage. However, since that Activity is deprecated, the best approach would be using PreferenceFragment as in reVerse's answer.Keller
I ended up with your solution, moving to PreferenceFragment just too much in my case.Haruspicy
P
10

Edit: As of AppCompat 22.1, any activity can be themed using AppCompatDelegate. The name of the tinted view classes also changed from v7.internal.widget.TintXYZ to v7.widget.AppCompatXYZ. The answer below is for AppCompat 22.0 and older.


I've also came across this problem and solved it by simply copying the code related to widget tinting from ActionBarActivity. One downside of this solution is that it relies on internal classes that might change or become unavailable in the future.

import android.support.v7.internal.widget.TintCheckBox;
import android.support.v7.internal.widget.TintCheckedTextView;
import android.support.v7.internal.widget.TintEditText;
import android.support.v7.internal.widget.TintRadioButton;
import android.support.v7.internal.widget.TintSpinner;

public class MyActivity extends PreferenceActivity {
    @Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        // Allow super to try and create a view first
        final View result = super.onCreateView(name, context, attrs);
        if (result != null) {
            return result;
        }

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            // If we're running pre-L, we need to 'inject' our tint aware Views in place of the
            // standard framework versions
            switch (name) {
                case "EditText":
                    return new TintEditText(this, attrs);
                case "Spinner":
                    return new TintSpinner(this, attrs);
                case "CheckBox":
                    return new TintCheckBox(this, attrs);
                case "RadioButton":
                    return new TintRadioButton(this, attrs);
                case "CheckedTextView":
                    return new TintCheckedTextView(this, attrs);
            }
        }

        return null;
    }
}

This works because onCreateView gets called by the LayoutInflater service for every view that is being inflated from a layout resource, which allows the activity to override which classes get instantiated. Make sure that the activity theme is set to Theme.AppCompat. (or descendants) in the manifest.

See ActionBarActivity.java and ActionBarActivityDelegateBase.java for the original code.

Photofluorography answered 30/12, 2014 at 21:51 Comment(0)
C
6

I know this questions is kinda old but I wanted to leave a solution to overcome this issue you've experienced. First of all I'd like to say that the PreferenceActivity is a relic of pre-honeycomb times so don't expect Google to tint your Widgets in this really really old Activity subset.
Instead of the PreferenceActivity you should use PreferenceFragments which will be wrapped in an Activity (preferably an ActionbarActivity if you want your Widgets to be tinted).

Following a pretty basic code example how your Settings Activity should look like.

Example

public class MySettings extends ActionBarActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.settings_activity);

        getFragmentManager().beginTransaction()
                .replace(R.id.container, new MyPreferenceFragment()).commit();
    }

    public static class MyPreferenceFragment extends PreferenceFragment {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            addPreferencesFromResource(R.xml.preferences_file);
        }
    }
}

Note: In this example I've used a FrameLayout as my container for the PreferenceFragments

Result:

example showing tinted widgets in preferences

So as you can see your Widgets will be tinted properly according to the colorAccent you've set.
More about PreferenceFragments on developer.android.com (click).

Comanche answered 13/12, 2014 at 9:28 Comment(6)
Thank you for your answer, but the app had a minSDK of 10, so even if I did know that PreferenceActivity is deprecated, I was bound to use it.Keller
I am using the combination of ActionBarActivity and PreferenceFragment as well and it works when added via resource. However, any CheckBoxPreference that is dynamically instantiated via code, still yields a black checkbox.Bear
@Q'' This is normal. The supported Widgets will be intercepted during inflation and tinted properly. If you add something dynamically this won't happen. As a reference you can listen to this podcast (click) especially the part starting at minute 7:25Comanche
@Keller In this case you should use two different activities. ;) No need to support this ultra old versions properly.Comanche
Checkboxes are working for me but switches and list preference aren't working.Ozoniferous
Switches will only be tinted if you're using android.support.v7.widget.SwitchCompat afaik the SwitchPreference doesn't use it. So the only option would be to implement a SwitchPreference on your own. In terms of the list preference it's possibly due the fact that it's shown in a Dialog.Comanche
K
2

So far, my own (sad) workaround was to create from scratch my own checkbox drawables, using the colors which the checkbox should have been tinted with in the first place.

In styles.xml:

<?xml version="1.0" encoding="utf-8"?>
<style name="AppThemeBase" parent="Theme.AppCompat.Light.NoActionBar">
...
<!-- edit the checkbox appearance -->
<item name="android:listChoiceIndicatorMultiple">@drawable/my_checkbox</item>
...
</style>

drawable/my_checkbox.xml:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true" android:drawable="@drawable/checkbox_on" />
    <item android:drawable="@drawable/checkbox_off" />
</selector>

checkbox_on" andcheckbox_off` are the PNG drawables for the selected and unselected states, obviously one for each screen density. If you mind dimension consistency, the baseline (MDPI) dimension of the drawables should be 32px full asset and 18px optical square.

Keller answered 20/10, 2014 at 18:53 Comment(2)
I accepted this because it pertains to the question and PreferenceActivity usage. However, since that Activity is deprecated, the best approach would be using PreferenceFragment as in reVerse's answer.Keller
I ended up with your solution, moving to PreferenceFragment just too much in my case.Haruspicy
C
2

The solution given by Tamás Szincsák works great but it should be updated, if using appcompat-v7:22.1.1, as follows:

imports should be changed to

import android.support.v7.widget.AppCompatCheckBox;
import android.support.v7.widget.AppCompatCheckedTextView;
import android.support.v7.widget.AppCompatEditText;
import android.support.v7.widget.AppCompatRadioButton;
import android.support.v7.widget.AppCompatSpinner;

And respectively the switch should be

switch (name) {
    case "EditText":
        return new AppCompatEditText(this, attrs);
    case "Spinner":
        return new AppCompatSpinner(this, attrs);
    case "CheckBox":
        return new AppCompatCheckBox(this, attrs);
    case "RadioButton":
        return new AppCompatRadioButton(this, attrs);
    case "CheckedTextView":
        return new AppCompatCheckedTextView(this, attrs);
}
Cycloparaffin answered 28/4, 2015 at 12:24 Comment(0)
V
1

If you are using AppCompat, then use the app compat items in your style.xml file.

For example, use:

<style name="AppTheme" parent="Theme.AppCompat.Light">
    <item name="colorPrimary">@color/primary</item>
    <item name="colorPrimaryDark">@color/primary_dark</item>
    <item name="colorAccent">@color/accent</item>
</style>

NOT (notice "android:"):

<style name="AppTheme" parent="Theme.AppCompat.Light">
    <item name="android:colorPrimary">@color/primary</item>
    <item name="android:colorPrimaryDark">@color/primary_dark</item>
    <item name="android:colorAccent">@color/accent</item>
</style>

This fixed the issue for me.

Volcanic answered 6/3, 2015 at 22:15 Comment(2)
This is right, nevertheless it won't work with PreferenceActivity because as stated by devs, that deprecated Activity doesn't use the AppCompat widgets that are able to apply tints on themselves. It does work with PreferenceFragment instead, which is probably what you are using.Keller
Yes. I was using PreferenceFragment. Thanks for the clarification devrocca.Volcanic

© 2022 - 2024 — McMap. All rights reserved.