3

I am trying to find a way to override a non-public attribute of an android style, more specifically an atttribute named itemColor of the Widget.FragmentBreadCrumbs style. This style affects the text color of the breadcumb in a PreferenceActivity when a preference fragment is being displayed on the right pane for large screens. It is used by the class FragmentBreadCrumbs.

My application uses a custom theme that extends Theme.Holo.Light and the theme breaks on API 23 so I am trying to find a workaround.

The aforementioned style sets a default value to itemColor of @null which is not overridden in the Holo theme while for example it is set to a valid value for the Material theme. As a result the title of the breadcrumb is not visible (see screenshot for API 19 and screenshot for API 23)

I guess what I am trying to do is to find a way that could change a private value of a theme similar to the way reflection can be used to modify the private field's value of a class. Alternatively the ContextThemeWrapper seems to be promising but I simple don't get how can I use it or even if it is applicable in my situtation.

What I need is that after FragmentBreadCrumbs class executes its constructor below the mTextColor attribute to not be @null (which I am guessing is 0) as is set by the Android theme configuration but to have a valid color value.

Do you think this is possible?

public FragmentBreadCrumbs(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);

    final TypedArray a = context.obtainStyledAttributes(attrs,
            com.android.internal.R.styleable.FragmentBreadCrumbs, defStyleAttr, defStyleRes);

    mGravity = a.getInt(com.android.internal.R.styleable.FragmentBreadCrumbs_gravity,
            DEFAULT_GRAVITY);

    mLayoutResId = a.getResourceId(
            com.android.internal.R.styleable.FragmentBreadCrumbs_itemLayout,
            com.android.internal.R.layout.fragment_bread_crumb_item);

    /* This is the value needed to be overridden */
    mTextColor = a.getColor(
            com.android.internal.R.styleable.FragmentBreadCrumbs_itemColor,
            0);

    a.recycle();
}
c.s.
  • 4,786
  • 18
  • 32
  • 1
    Do you have the FragmentBreadCrumbs object in your code? (Can you get an instance of it?) @c.s. – Nir Duan Aug 08 '16 at 17:13
  • @NirDuan No. I don't know how to get one. This class is instantiated by an `` statement inside the xml layout file used by the `PreferenceActivity` – c.s. Aug 08 '16 at 17:23

1 Answers1

2

Unfortunately the toolchain will report an error if you try to use android:itemColor because this does not correspond to a public attribute name, so you cannot even make a style with this attribute.

The only thing I can think of is to change the text color via reflection just after the views have been constructed(/inflated). You would want to do this as early as possible, before the first time updateCrumbs() is run inside of FragmentBreadCrumbs. Perhaps you can override onCreate() of PreferenceActivity or onCreateView() of PreferenceFragment (whichever is applicable here) and do something like this:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    FragmentBreadCrumbs fbc = (FragmentBreadCrumbs) findViewById(...);
    int color = ...;
    FragmentBreadCrumbsUtils.setTextColor(fbc, color);
}


public class FragmentBreadCrumbsUtils {

    private static final Field FRAGMENT_BREAD_CRUMBS_TEXT_COLOR = findField();
    private static Field findField() {
        try {
            Field f = FragmentBreadCrumbs.class.getDeclaredField("mTextColor");
            f.setAccessible(true);
            return f;
        } catch (Throwable t) {
            // don't fail for any reason, just log it
            Log.e("FragmentBreadCrumbsUtils",
                "Couldn't find mTextColor field in FragmentBreadCrumbs",
                t);
        }
        return null;
    }

    public static void setTextColor(FragmentBreadCrumbs fbc, int color) {
        if (FRAGMENT_BREAD_CRUMBS_TEXT_COLOR == null) {
            return; // can't do anything, we don't have the field
        }

        try {
            FRAGMENT_BREAD_CRUMBS_TEXT_COLOR.set(fbc, color);
        } catch (Throwable t) {
            // don't fail for any reason, just log it
            Log.e("FragmentBreadCrumbsUtils",
                "Couldn't set mTextColor field in FragmentBreadCrumbs",
                t);
        }
    }
}
Karakuri
  • 38,365
  • 12
  • 84
  • 104
  • Unfortunately `onCreate()` is already too late. However your post and and the initial comment helped me to find the answer. I can override the `findViewById()` to find the instance before it is used by `PreferenceActivity`. If you don't mind adding this to your answer I will accept it otherwise I will answer my own question. Thanks for the help – c.s. Aug 09 '16 at 09:33
  • 1
    @c.s. Overriding `findViewbyId()` feels uncomfortable to my taste (but then again, so does reflection trickery). Whatever works I guess, that's just how Android is sometimes. – Karakuri Aug 09 '16 at 14:54
  • I completely agree. Reflection was not even in my list until after I have made the question. I have mentioned it in the text only as an example of what I am trying to do which was to override the style's private attribute. However I have pretty much exhausted any other options before making the question, (to be honest I did not expect any answer that would work). But it is as you said, this is how Android is sometimes – c.s. Aug 09 '16 at 15:29