Android - Styling Action Bar at run time

    Android's style framework helps change the look and feel of UI elements without having to change the source code and invoking View specific APIs at run time. One would assume that this is true for all UI elements. Unfortunately, the Action Bar's Theme can't be changed at run time via applying a different theme. Here is a test code to demonstrate this,

  Application has two themes,

    <style name="AppTheme2" parent="android:Theme.Holo.Light.DarkActionBar">
        <item name="@android:attr/actionBarStyle">@style/MyActionBarStyle2</item>
    </style>

    <style name="AppTheme1" parent="@android:style/Theme.Holo.Light">
        <item name="@android:attr/actionBarStyle">@style/MyActionBarStyle1</item>
    </style>

Activity's is started with AppTheme1 and changes the theme at runtime based on certain events,

        <activity
            android:name=".SampleActivity"
            android:theme="@style/AppTheme1"
            android:label="@string/app_name" >
        </activity>

The following code resolves the reference attribute actionBarStyle in theme obtained from a view's and action bar's context.

            TypedValue resolvedValue;
            setContentView( R.layout.activity_sample );
            View view = findViewById( R.id.sample_text );
            Resources.Theme theme = view.getContext().getTheme();

            resolvedValue = new TypedValue();
            theme.resolveAttribute( android.R.attr.actionBarStyle, resolvedValue, true );
            int actualResourceId = resolvedValue.resourceId;

            Log.v( TAG, " Resource via View Context [" + actualResourceId + "]");

            theme = getActionBar().getThemedContext().getTheme();
            resolvedValue = new TypedValue();
            theme.resolveAttribute( android.R.attr.actionBarStyle, resolvedValue, true );
            actualResourceId = resolvedValue.resourceId;

            Log.v( TAG, " Resource via Action Bar Context [" + actualResourceId + "]");

Now, the activity changes the theme at run time and sets the content view in order to ensure that the views are inflated again using the new theme. The question is as to what happens to the action bar.

            setTheme( R.style.AppTheme2 );
            setContentView(R.layout.activity_sample);
            view = findViewById( R.id.sample_text );

            resolvedValue = new TypedValue();
            theme.resolveAttribute( android.R.attr.actionBarStyle, resolvedValue, true );
            actualResourceId = resolvedValue.resourceId;

            Log.v( TAG, " Resource via View Context for new Theme [" + actualResourceId + "]");

            theme = getActionBar().getThemedContext().getTheme();
            resolvedValue = new TypedValue();
            theme.resolveAttribute( android.R.attr.actionBarStyle, resolvedValue, true );
            actualResourceId = resolvedValue.resourceId;

            Log.v( TAG, " Resource via Action Bar Context for new Theme[" + actualResourceId + "]");

  Upon execution, the resourceId obtained from the view's context before and after changing the theme is different. However, this is not the case of the Action Bar's context. This is why Action Bar's look and feel doesn't change with run time theme change.

    The reasoning is due to the internal implementation of Action Bar initialization. Lets start from setContentView().

    public void setContentView(int layoutResID) {
        getWindow().setContentView(layoutResID);
        initActionBar();
    }

   The implementation seems to suggest that entire view hierarchy is initialize again. However, this is not the case. initActionBar() works only for the first initialization of the Action Bar. It works for cases like screen orientation where in the activity is destroyed and recreated again.

   Let's say, we bypass this validation via platform change and force initialization of ActionBar every time setContentView() is invoked. Would it then work? Well, this too wouldn't solve the problem. Its because the ActionBar is initialized using a specific context whose theme doesn't change even if setTheme() is invoked.

    public ActionBarImpl(Activity activity) {
        mActivity = activity;

        Window window = activity.getWindow();
        View decor = window.getDecorView();
        init(decor);

        if (!overlayMode) {
            mContentView = decor.findViewById(android.R.id.content);
        }
    }

   The context used is that of the decor view which is initialized only once during the activity creation and its theme doesn't change at all.

    So the only way to change the style of the ActionBar is to invoke APIs at run time like setBackgroundDrawable() etc. If not, consider using the new ToolBar as that claims support of moving the control to application's layout.

No comments: