How to set the theme for the application, to avoid wrong color transitions?

前端 未结 4 1427
天命终不由人
天命终不由人 2020-12-14 03:21

Background

I\'m developing a themes chooser feature in my \"app manager\" app, and I\'ve succeeded setting the theme dynamically for each of the activities.

<
4条回答
  •  旧时难觅i
    2020-12-14 04:07

    The transition color is retrieved from the activity theme on the manifest (or the application if not set).

    Currently the only way around this limitation is to create a dummy subclass for each real Activity, eg. MyActivityLight, to declare a different theme. Activity alias won't work, the attribute will be ignored.

    For activities with IntentFilter's, you should only maintain one of each "type" enabled, using PackageManager#setComponentEnabledSetting(). Note that the change may take some seconds.

    For activities that are started by class name, you can infer the correct prefix according to the user's theme.


    So lets suppose you have 2 themes: AppTheme.Dark and AppTheme.Light and some activities. The dark theme is the default one.

    Original manifest:

    
        
            
    
            
                
                    
                    
                    
                 
            
        
    
    

    Change all activities above as abstract classes and create dummy subclasses suffixed by Light and Dark.

    Then the manifest should be changed like this:

    
    
        
        
            
            
    
            
                
                    
                    
                    
                 
            
            
                
                    
                    
                    
                 
            
        
    
    

    Then you could have something like this to get the themed Activity class, given an abstract Activity:

    public static ComponentName getThemedActivityName(
            Context ctx, 
            Class clazz) {
    
        // Probably gets some value off SharedPreferences
        boolean darkTheme = isUsingDarkTheme(ctx);
    
        String baseName = clazz.getName();
        String name += (darkTheme) ? "Dark" : "Light";
        return new ComponentName(ctx, name);
    }
    
    public static void startThemedActivity(
            Activity ctx, 
            Class clazz) {
        Intent intent = new Intent();
        intent.setComponent(getThemedActivityName(ctx, clazz));
        ctx.startActivity(intent);
    }
    

    And also change the enabled status where needed, when the theme is changed.

    public void onThemeChanged(Context ctx, boolean dark) {
        // save theme to SharedPreferences or similar and...
    
        final PackageManager pm = ctx.getPackageManager();
        final String pckgName = ctx.getPackageName();
    
        final PackageInfo pckgInfo;
        try {
            final int flags = PackageManager.GET_ACTIVITIES 
                                 | PackageManager.GET_DISABLED_COMPONENTS;
            pckgInfo = pm.getPackageInfo(pckgName, flags);
        } catch (PackageManager.NameNotFoundException e) {
            throw new RuntimeException(e);
        }
    
        final ActivityInfo[] activities = pckgInfo.activities;
    
        for (ActivityInfo info: activities) {
            final boolean enable;
            if (info.theme == R.style.AppTheme_Light) {
                enable = !dark;
            } else if (info.theme == R.style.AppTheme_Dark) {
               enable = dark;
            } else {
               continue;
            }
    
            final int state = (enable) ? 
                    PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
    
            final String name = info.targetActivity;
            final ComponentName cmp = new ComponentName(pckgName, name);
            pm.setComponentEnabledSetting(cmp, state, PackageManager.DONT_KILL_APP);
        }
    }
    

    If doing IPC on a loop scares you, you can do this asynchronously on a helper thread, as long as multiple calls to onThemeChanged() run sequentially.

    Note that in this example I change the enabled status of all activities (that have a known theme) but only had to do that for the ones with intent filters. If the activities aren't hardcoded its easier this way.

    Important Note: As Richard Le Mesurier and other have pointed out, using this technique on Launcher Activities removes or disables the shortcut on the home screen, if it exists. This is just a solution for non launcher Activities.

提交回复
热议问题