# IMKitThemeManager

public class IMKitThemeManager

融云主题管理器

用于管理融云 IM SDK 的主题切换和资源适配，支持多主题动态切换。

### 核心功能

- 内置主题：提供传统主题和欢快主题，欢快主题自动跟随系统深浅色模式
- 自定义主题：支持开发者扩展和注册自定义主题，支持深浅色模式和主题叠加
- 系统跟随：自动跟随系统深浅色模式切换（欢快主题及自定义主题）
- 动态资源：提供动态资源获取方法，根据当前主题和深浅色模式自动选择合适的资源
- 自动应用：通过 Activity 生命周期回调自动应用主题到所有页面
- 主题监听：支持监听主题切换事件，方便进行自定义处理

### 重要说明

关于系统深浅色模式切换：

在部分机型上，系统设置切换深浅色模式后，返回 APP 可能无法及时切换到对应的模式。 这是由于系统原因导致，当前 APP 通过系统方法获取的深浅色状态仍然是旧值，需要重启 APP 才能生效。

解决方案（推荐）：

如果您的应用使用了 AppCompat 库，建议在 Application 中通过 `AppCompatDelegate.setDefaultNightMode(nightMode)` 来设置应用的深浅色模式， 这样可以保证深浅色模式的及时切换。

    // 在 Application 的 onCreate 中设置
    // 跟随系统
    AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);

    // 或强制使用浅色模式
    AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);

    // 或强制使用深色模式
    AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);

Content copied to clipboard

### 快速开始

    // 1. 切换到欢快主题（自动跟随系统深浅色）
    IMKitThemeManager.changeInnerTheme(context, IMKitThemeManager.LIVELY_THEME);

    // 2. 切换到传统主题
    IMKitThemeManager.changeInnerTheme(context, IMKitThemeManager.TRADITION_THEME);

    // 3. 添加自定义主题（支持深浅色模式）
    IMKitThemeManager.addTheme("CUSTOM_THEME",
        R.style.MyCustomLightTheme,  // 浅色模式样式
        R.style.MyCustomDarkTheme    // 深色模式样式
    );

    // 4. 切换到自定义主题（基于欢快主题）
    IMKitThemeManager.changeCustomTheme(context, "CUSTOM_THEME", IMKitThemeManager.LIVELY_THEME);

    // 5. 在代码中动态获取主题资源
    int bgResId = IMKitThemeManager.dynamicResource(
        context,
        R.attr.rc_conversation_bg,  // 主题属性
        R.drawable.rc_old_bg        // 默认资源
    );
    view.setBackgroundResource(bgResId);

    // 6. 获取当前主题
    String currentTheme = IMKitThemeManager.getCurrentThemeName();

    // 7. 判断当前主题类型
    if (IMKitThemeManager.TRADITION_THEME.equals(currentTheme)) {
        // 传统主题
    }

Content copied to clipboard

## Types

OnThemeListener
Link copied to clipboard
public interface OnThemeListener
主题变化监听器接口 用于监听主题切换事件，当主题发生变化时会收到回调通知。 使用示例：

    // 创建监听器
    OnThemeListener listener = new OnThemeListener() {
        @Override
        public void onThemeChanged(Context context, String oldTheme, String newTheme) {
            Log.d("Theme", "主题从 " + oldTheme + " 切换到 " + newTheme);
            // 可以直接使用传入的 context，无需自己持有引用
            if (context instanceof Activity) {
                ((Activity) context).recreate(); // 重建 Activity 应用新主题
            }
        }
    };

    // 添加监听器
    IMKitThemeManager.addThemeListener(listener);

    // 不需要时移除监听器（避免内存泄漏）
    IMKitThemeManager.removeThemeListener(listener);

Content copied to clipboard

## Properties

LIVELY_THEME
Link copied to clipboard
public final static String LIVELY_THEME
欢快主题 - 现代化界面，自动跟随系统深浅色模式
TRADITION_THEME
Link copied to clipboard
public final static String TRADITION_THEME
传统主题 - 经典的界面设计风格

## Functions

addTheme
Link copied to clipboard
public static void addTheme(String themeType, int lightStyleResId, int darkStyleResId)
添加主题（支持浅色和深色样式） 此方法用于添加自定义主题，支持为主题指定浅色和深色两种样式。系统会根据当前的深浅色模式自动应用对应的样式。 使用场景：

    // 添加自定义主题（支持深浅色模式）
    IMKitThemeManager.addTheme(
        "CUSTOM_THEME",
        R.style.MyCustomLightTheme,  // 浅色模式样式
        R.style.MyCustomDarkTheme    // 深色模式样式
    );

    // 添加单一样式主题（浅色和深色使用相同样式）
    IMKitThemeManager.addTheme(
        "SIMPLE_THEME",
        R.style.MyTheme,  // 浅色模式样式
        R.style.MyTheme   // 深色模式样式（相同）
    );

    // 为已有主题追加额外样式（样式叠加）
    IMKitThemeManager.addTheme(
        IMKitThemeManager.LIVELY_THEME,
        R.style.MyCustomOverrideLight,  // 浅色模式扩展样式
        R.style.MyCustomOverrideDark    // 深色模式扩展样式
    );

    // 切换到自定义主题（基于欢快主题）
    IMKitThemeManager.changeCustomTheme(context, "CUSTOM_THEME", IMKitThemeManager.LIVELY_THEME);

Content copied to clipboard
注意事项：

- 多次调用此方法添加同一主题时，样式会按顺序叠加应用
- 后添加的样式属性会覆盖先前的同名属性
- lightStyleResId 和 darkStyleResId 不能为 0
- 主题会自动跟随系统深浅色模式切换

addThemeListener
Link copied to clipboard
public static void addThemeListener(IMKitThemeManager.OnThemeListener listener)
添加主题变化监听器 注册一个监听器以接收主题切换事件通知。当调用 changeInnerTheme 或 changeCustomTheme 方法且主题实际发生变化时，所有已注册的监听器都会收到 onThemeChanged 回调。 使用场景：

- 需要在主题切换时更新自定义 UI 组件
- 需要在主题变化时重新加载资源或刷新界面
- 需要同步主题状态到其他模块

Context 参数的优势：

- 监听器无需自己持有 Context 引用，避免潜在的内存泄漏
- 可以直接使用传入的 Context 进行 UI 操作（如 Activity.recreate()）
- 通过 Context 获取资源或调用其他需要 Context 的方法

使用示例：

    public class MyActivity extends AppCompatActivity {
        private OnThemeListener themeListener;

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

            // 创建监听器
            themeListener = new OnThemeListener() {
                @Override
                public void onThemeChanged(Context context, String oldTheme, String newTheme) {
                    // 使用传入的 context 重新创建 Activity
                    if (context instanceof Activity) {
                        ((Activity) context).recreate();
                    }
                }
            };

            // 添加监听器
            IMKitThemeManager.addThemeListener(themeListener);
        }

        @Override
        protected void onDestroy() {
            super.onDestroy();
            // 移除监听器，避免内存泄漏
            IMKitThemeManager.removeThemeListener(themeListener);
        }
    }

Content copied to clipboard
注意事项：

- 同一个监听器对象不会被重复添加
- 请在合适的时机（如 Activity/Fragment 的 onDestroy）移除监听器，避免内存泄漏
- 监听器回调在主题切换所在的线程执行，通常是主线程

changeCustomTheme
Link copied to clipboard
public static void changeCustomTheme(Context context, String customThemeType, String baseOnTheme)
切换自定义主题（基于内置主题扩展） 此方法用于切换到自定义主题，并指定一个基础主题。会先应用基础主题的配置，然后再应用自定义主题的配置，从而实现主题的叠加和扩展。 应用顺序：

1. 先应用 baseOnTheme 对应的所有主题配置
2. 再应用 customThemeType 对应的所有主题配置
3. 后应用的配置会覆盖先前的同名属性

使用示例：

    // 先添加自定义主题
    IMKitThemeManager.addTheme(
        "MY_BLUE_THEME",
        R.style.MyBlueLightTheme,
        R.style.MyBlueDarkTheme
    );

    // 基于欢快主题，应用自定义主题（先应用欢快主题，再应用自定义主题）
    IMKitThemeManager.changeCustomTheme(context, "MY_BLUE_THEME", IMKitThemeManager.LIVELY_THEME);

    // 基于传统主题，应用自定义主题
    IMKitThemeManager.changeCustomTheme(context, "MY_BLUE_THEME", IMKitThemeManager.TRADITION_THEME);

Content copied to clipboard
注意事项：

- baseOnTheme 只能传递 TRADITION_THEME 或 LIVELY_THEME
- customThemeType 必须是已通过 addTheme 方法添加的主题
- 主题切换是全局性的，会影响所有融云 SDK 的 UI 组件

changeInnerTheme
Link copied to clipboard
public static void changeInnerTheme(Context context, String themeType)
切换应用内置主题 切换到指定的内置主题并立即应用到当前上下文。该方法会自动处理以下操作：

- 验证并设置新的主题类型
- 根据系统深浅色模式自动选择对应的样式
- 应用主题到 Application Context（全局生效）
- 应用主题到当前 Context（立即生效）
- 通过 ActivityLifecycleCallbacks 自动应用到后续创建的所有 Activity

使用示例：

    // 切换到欢快主题（自动跟随系统深浅色）
    IMKitThemeManager.changeInnerTheme(context, IMKitThemeManager.LIVELY_THEME);

    // 切换到传统主题
    IMKitThemeManager.changeInnerTheme(context, IMKitThemeManager.TRADITION_THEME);

Content copied to clipboard
注意：主题切换是全局性的，会影响所有融云 SDK 的 UI 组件。如果当前已经是目标主题，方法会自动跳过，避免不必要的重复应用。
dynamicResource
Link copied to clipboard
public static int dynamicResource(int newVersionResId, int oldVersionResId)
动态获取主题资源（无需 Context 的快速版本） 此方法是 dynamicResource 的轻量级版本，适用于 XML 布局文件或无需立即解析的场景。

- 现代化主题：直接返回主题属性 ID，由系统在渲染时自动解析
- 传统主题：返回固定资源 ID

适用场景：

- XML 布局文件中需要根据主题类型选择资源的场景（如 android:background）
- 延迟解析场景，资源 ID 会在后续使用时由 Android 系统自动解析
- 无法获取 Context 但需要资源 ID 的场景

使用示例：

    // 在代码中使用（系统会自动解析属性）
    view.setBackgroundResource(
        IMKitThemeManager.dynamicResource(R.attr.rc_bg, R.drawable.rc_old_bg)
    );

    // 注意：返回的可能是属性 ID，需要系统自动解析
    // 如果需要立即获取解析后的资源，请使用带 Context 参数的版本

Content copied to clipboard
注意：此方法返回的是属性 ID（现代化主题）或资源 ID（传统主题），如果需要立即使用解析后的实际资源 ID，请使用 dynamicResource 方法。
public static int dynamicResource(Context context, int newVersionAttrId, int oldVersionResId)
动态获取主题资源（基于 Context 解析） 此方法根据当前激活的主题自动选择对应的资源：

- 现代化主题：从主题属性（Theme Attribute）中动态解析资源 ID，支持多主题切换
- 传统主题：直接返回指定的固定资源 ID，保持向后兼容

适用场景：

- 在 Java/Kotlin 代码中动态获取需要主题适配的资源
- 需要在运行时根据当前主题选择不同资源的场景
- 自定义 View 或 Adapter 中需要加载主题相关的图片、颜色等资源

使用示例：

    // 获取背景资源：现代化主题使用主题属性，传统主题使用固定资源
    int bgResId = IMKitThemeManager.dynamicResource(
        context,
        R.attr.rc_conversation_bg,      // 主题属性
        R.drawable.rc_old_bg            // 固定资源（用于传统主题）
    );
    view.setBackgroundResource(bgResId);

    // 获取颜色资源
    int colorResId = IMKitThemeManager.dynamicResource(
        context,
        R.attr.rc_text_primary,         // 主题属性
        R.color.rc_old_text_color       // 固定颜色（用于传统主题）
    );
    textView.setTextColor(getResources().getColor(colorResId));

Content copied to clipboard
getAttrResId
Link copied to clipboard
public static int getAttrResId(Context context, int attrId)
从主题属性中解析实际的资源 ID 此方法用于将主题属性（Theme Attribute）解析为实际的资源 ID。 使用场景：需要从主题属性中获取实际资源 ID 时使用。

    // 解析主题属性得到实际的资源 ID
    int drawableResId = IMKitThemeManager.getAttrResId(context, R.attr.rc_conversation_bg);
    // drawableResId 现在是实际的 R.drawable.xxx 值

Content copied to clipboard
getColorFromAttrId
Link copied to clipboard
public static int getColorFromAttrId(Context context, int attrId)
从主题属性中直接获取颜色值 此方法是获取颜色的便捷方法，会自动完成"属性→资源ID→颜色值"的完整解析过程。 使用场景：需要从主题属性中直接获取可用的颜色 int 值。

    // 直接获取主题颜色值
    int textColor = IMKitThemeManager.getColorFromAttrId(context, R.attr.rc_text_primary);
    textView.setTextColor(textColor);

    // 等价于以下两步操作：
    // int colorResId = IMKitThemeManager.getAttrResId(context, R.attr.rc_text_primary);
    // int textColor = context.getResources().getColor(colorResId);

Content copied to clipboard
getCurrentThemeName
Link copied to clipboard
public static String getCurrentThemeName()
获取当前激活的主题类型 使用示例：

    String theme = IMKitThemeManager.getCurrentThemeName();
    if (IMKitThemeManager.LIVELY_THEME.equals(theme)) {
        // 当前是欢快主题
    }

Content copied to clipboard
isSystemInDarkMode
Link copied to clipboard
public static boolean isSystemInDarkMode(Context context)
判断系统是否处于深色模式
isTraditionTheme
Link copied to clipboard
public static boolean isTraditionTheme()
判断当前是否为传统主题（内部使用，不推荐外部调用） ⚠️ 注意：此方法仅供 SDK 内部使用，外部不推荐直接调用。 推荐做法：外部应用请使用 getCurrentThemeName 获取当前主题名称，然后自行判断：

    String theme = IMKitThemeManager.getCurrentThemeName();
    if (IMKitThemeManager.TRADITION_THEME.equals(theme)) {
        // 传统主题逻辑
    } else if (IMKitThemeManager.LIVELY_THEME.equals(theme)) {
        // 欢快主题逻辑
    } else {
        // 自定义主题逻辑
    }

Content copied to clipboard
removeThemeListener
Link copied to clipboard
public static void removeThemeListener(IMKitThemeManager.OnThemeListener listener)
移除主题变化监听器 移除之前通过 addThemeListener 添加的监听器。 建议在 Activity/Fragment 的生命周期结束时（如 onDestroy）调用此方法，避免内存泄漏。 使用示例：

    @Override
    protected void onDestroy() {
        super.onDestroy();
        IMKitThemeManager.removeThemeListener(themeListener);
    }

Content copied to clipboard