Apply tint to PreferenceActivity widgets with AppCompat v21

devrocca picture devrocca · Oct 20, 2014 · Viewed 7.4k times · Source

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?

Answer

Tamás Szincsák picture Tamás Szincsák · Dec 30, 2014

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.