【官方推荐方式】【原创】安卓换肤踩坑纯色模式app使用attr轻松实现定制颜色主题和深色主题

如果在网上搜换肤,方案五花八门,但是根据app的需求,以及无设计师的情况下,基本上简约风格app,这种风格下只需要几个颜色就行了,根本不需要动态从磁盘加载皮肤apk,而且通过反射操作侵入性太强,因此attr大法才是最适合目前的我所做的app实现。

网上的换肤方法侵入性太强,而纯色app不需要各种花式的皮肤,基本上2三套颜色就行了,主色,次色,而其他则非黑即白。深色模式实现就更简单了,用着色tint就实现了。

经过了几天的研究发现,动态修改setTheme是有bug的, bug就是状态栏颜色和actionbar在未在activity定义attr背景的情况下实现修改actionbar的就有这个bug,2015年在stackoverflow网站上就有人提出解决方法,这个解决方法是没有办法的方法,需要在baseactivity进行判断,如果有actionbar就设置actionbar颜色,

以及设置状态栏颜色。

关于偏见

偏见源于他们对主题的掌握度不够,因为所有的地方都需要设置颜色,实际上基本是不需要单独设置的,只有个别控件的着色要单独设置除外,也就是基本上可以在全局主题里面几行代码可以搞定,网上却吐槽非常麻烦需要单独设置attr?,大言不惭的贬低官方的技术,试问自己对主题的各种属性的作用又了解多少呢?

达到的效果

在颜色选择器中是可以直接使用实现的属性?attr
而在drawable,则需要包裹一层drawable引用color,直接使用color属性也是会报错的,
正常的按钮,对话框,控件不需要再通过代码,xml设置颜色,

而网上的操作性太麻烦,正规的写法就是官方的写法,能不在view xml定义的就不在view xml定义
这样侵入性比较小。

颜色主题需要注意的事项

drawable要使用颜色属性只能包裹一层shape





第三方自定义ui 有的是只支持颜色不支持attr的。则需要进行额外适配处理,
其处理方式就是根据属性查找对应的颜色 或者id (colorres)
颜色属性找颜色

    public static int attrFetchColor(Context context,int attrColor) {
        int[] attribute = new int[]{attrColor};
        TypedArray array =context.getTheme().obtainStyledAttributes(attribute);
        int color = array.getColor(0, AppContext.getInstance().getResources().getColor(R.color.themeColor));
        array.recycle();
        return color;
    }

查找id


  public static int attrFetchColorId(Context context,int attrColor) {
        int[] attribute = new int[]{attrColor};
        TypedArray array = context.obtainStyledAttributes(attribute);
        int colorID = array.getResourceId(0, R.color.themeColor);
        array.recycle();
        return colorID;
    }


在baseactivity onCreate中修改状态栏颜色


    public static void updateActionBarAndStatusColor(Activity activity) {
        if (activity instanceof AppCompatActivity) {
            AppCompatActivity appCompatActivity = (AppCompatActivity) activity;
            ActionBar supportActionBar = appCompatActivity.getSupportActionBar();
            if (supportActionBar != null) {
                int i = AppUtils.attrFetchColor(activity,R.attr.defaultThemeColor);
                supportActionBar.setBackgroundDrawable(new ColorDrawable(i));
            }

        }
        android.app.ActionBar actionBar = activity.getActionBar();
        if (actionBar != null) {
            int color = AppUtils.attrFetchColor(activity,R.attr.defaultThemeColor);
            actionBar.setBackgroundDrawable(new ColorDrawable(color));
        }

        if (Build.VERSION.SDK_INT >= 21) {
//            getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
//            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            int statusBarBackground = AppUtils.attrFetchColor(activity,R.attr.colorPrimaryDark);


            Window window = activity.getWindow();
//            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
//            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
//            window.setStatusBarColor(backgroundColor);
            window.setStatusBarColor(statusBarBackground);
        }


    }

主题修改代码

 getBinding().rlMyThemeSetting.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
                int currentMode = NightModeUtils.Companion.getSpfThemeMode(AppContext.getInstance());
                ThemeMode[] values = ThemeMode.values();
                CharSequence[] items=new CharSequence[values.length];
                for (int i = 0; i < values.length; i++) {
                    String title = AppContext.getInstance().getString(values[i].getStringValueId());
                    items[i]=title;
                }
                builder.setSingleChoiceItems(items, currentMode, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        if (which != currentMode) {
                            dialog.dismiss();
                            ThemeMode value = values[which];
                            NightModeUtils.getInstance(AppContext.getInstance()).setThemeMode(value);
                            EventBus.getDefault().post(new ThemeChangeEvent().themeChange());

                        }

                    }

                });
                builder.setTitle(AppContext.getStr(R.string.deep_color_theme_setting));
                builder.show();

            }
        });

收到事件进行recreate(); ,我这里是在mainact,mainact可直接recreate,无需finish
适配起来挺快的,通过各种批量替换
但是需要注意的是,第三方对颜色适配做的很烂,哪怕大家众所周知的smartrefresh也不支持属性引用颜色。 因此主要的适配就是处理第三方框架的问题,
这个适配工作基本上一天就搞定了。
另外花的实际是纠结研究为啥actionbar和状态栏颜色通过setTheme无效花费了很多时间,在attach里面替换,以及研究theme的各种方法,以及引用再引用的方式也解决不了问题,
最后既然无效我只能另外走方法了,强制设置。

attr.xml定义

    
    

其实现在每一个主题都实现了,

下面附上theme.xml 仔细看 都实现了defaultThemeColor defaultThemeColorSecond 属性,




    

    



    





    





    

    






    








    




    
    

    


    

    





    



    

    


    



    
    

    
    

    

    
    



root.

    

color实现

 #5768FE
    #f2c4c4

    #5766FF

    

    @color/themeColor
    #4F60FE
    #536DFE
    @color/themeColor
    @color/white


    @color/light_blue_disable
    @color/teal_700
    @color/black

    @color/purple_300
    #E040FB
    @color/purple_300
    @color/purple_300
    #7B1FA2
    @color/purple_300
    @color/white



    #FF9800
    #FF9800
    #F57C00
    #FF9800
    #FF9800
    #FF9800
    @color/white



    @color/green_700
    @color/green_700
    @color/green_700
    #388E3C
    #4CAF50
    @color/green_700
    @color/white




    @color/blue_900
    @color/blue_900
    @color/blue_900
    @color/blue_900
    #448AFF
    @color/blue_900
    @color/white


    @color/quantum_black_100
    @color/quantum_black_100
    @color/quantum_black_100
    #5D4037
    @color/quantum_black_100
    @color/white



    #795548
    #795548
    #9E9E9E
    #5D4037
    @color/black_overlay
    @color/black


    #009688
    #757575
    #00BCD4
    #00796B
    @color/black_overlay
    @color/black

    #00BCD4
    #0097A7
    #009688
    #0097A7
    #B2EBF2
    #FFFFFF

    #FFC107
    #FFC107
    #FFC107
    #FFA000
    #212121
    #212121

    #FF5722
    #FF5722
    #FF4081
    #E64A19
    #212121
    #212121
    #E64A19

    #607D8B
    #757575
    #9E9E9E
    #455A64
    @color/black_overlay
    @color/black



image.png

image.png

深色主题values-night themes.xml
把正常颜色的拷贝过来然后指定颜色就行了,
修改主题的代码 baseactivityonCreate调用在setContentView之前

  fun applySetting(context: Context?): Int {
        var themeId = 0;
        // 检查主题
        val themeModeInt = getSpfThemeMode()
        val themeMode = ThemeMode.parseOfInt(themeModeInt)
        var disableActionBar: Boolean = false;
        var actName: String = "";
        var needActionBar:Int =-1;
        if (context is BaseActivity) {
            needActionBar = context.needActionBar()
            if( needActionBar == 1) {
                disableActionBar = false;
            }else if( needActionBar == 0) {
                disableActionBar = true;
            }
        }
            actName =  context?.javaClass?.simpleName + " ,是否禁用actionBar " + disableActionBar;
        if (context is AppCompatActivity) {
            if (needActionBar==-1) {
                var act: AppCompatActivity = context;
                disableActionBar = if(act.supportActionBar!=null) true else true;//act.supportActionBar is WindowDecorActionBar
            }
        }
        Log.w("ThemeStyle", "主题模式 ${themeMode.name} act $actName");

        if (themeMode == ThemeMode.BLUE_THEME) {
            if (disableActionBar) {
                themeId = R.style.Theme_MyApplication_Blue_NoActionBar
            } else {
                themeId = R.style.Theme_MyApplication_Blue
            }
        } else if (themeMode == ThemeMode.YELLOW_THEME) {
            if (disableActionBar) {
                themeId = R.style.Theme_MyApplication_Yellow_NoActionBar
            } else {
                themeId = R.style.Theme_MyApplication_Yellow
            }
        } else if (themeMode == ThemeMode.PURPLE_THEME) {
            if (disableActionBar) {
                themeId = R.style.Theme_MyApplication_PURPLE_NoActionBar
            } else {
                themeId = R.style.Theme_MyApplication_PURPLE
            }
        } else if (themeMode == ThemeMode.GREEN_THEME) {
            if (disableActionBar) {
                themeId = R.style.Theme_MyApplication_GREEN_NoActionBar;
            } else {
                themeId = R.style.Theme_MyApplication_GREEN
            }
        } else if (themeMode == ThemeMode.GREY_THEME) {
            if (disableActionBar) {
                themeId = R.style.Theme_MyApplication_Grey_NoActionBar;
            } else {
                themeId = R.style.Theme_MyApplication_Grey
            }
        } else if (themeMode == ThemeMode.BROWN_THEME) {
            if (disableActionBar) {
                themeId = R.style.Theme_MyApplication_Brown_NoActionBar
            } else {
                themeId = R.style.Theme_MyApplication_Brown
            }
        } else if (themeMode == ThemeMode.DEEP_ORANGE_THEME) {
            if (disableActionBar) {
                themeId = R.style.Theme_MyApplication_DeepOrange_NoActionBar
            } else {
                themeId = R.style.Theme_MyApplication_DeepOrange
            }
        } else if (themeMode == ThemeMode.AMBER_THEME) {
            if (disableActionBar) {
                themeId = R.style.Theme_MyApplication_Amber_NoActionBar
            } else {
                themeId = R.style.Theme_MyApplication_Amber
            }
        } else if (themeMode == ThemeMode.CYAN_THEME) {
            if (disableActionBar) {
                themeId = R.style.Theme_MyApplication_Cyan_NoActionBar
            } else {
                themeId = R.style.Theme_MyApplication_Cyan
            }
        } else if (themeMode == ThemeMode.BLACK_THEME) {
            if (disableActionBar) {
                themeId = R.style.Theme_MyApplication_BLACK_NoActionBar;
            } else {
                themeId = R.style.Theme_MyApplication_BLACK;
            }
        } else {
            if (disableActionBar) {//如果appcontext设置actionbar,act manifest.xml没有声明解除actionbar,然后调用setSupportBar则会抛出异常,因此需要处理适配。
                themeId = R.style.Theme_MyApplication_DefaultBlue_NoActionBar
            } else {
                themeId = R.style.Theme_MyApplication_DefaultBlue
            }
        }

        if (themeId != 0) {
            context?.setTheme(themeId)
//            return themeId;
        }
        AppCompatDelegate.setDefaultNightMode(
            when (themeMode) {
                ThemeMode.MODE_ALWAYS_ON -> AppCompatDelegate.MODE_NIGHT_YES
                ThemeMode.MODE_ALWAYS_OFF -> AppCompatDelegate.MODE_NIGHT_NO
                ThemeMode.MODE_FOLLOW_SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
                else -> {
                    AppCompatDelegate.MODE_NIGHT_NO
                }
            }


        )
        return themeId

    }

如果不为0,

    if (color != 0) {
//            getTheme().applyStyle(color, true);
            AppUtils.updateActionBarAndStatusColor(this);
        }

另外补充一点,按钮这些一般我都没有修改在每一个activity xml设置的,遵循material design以及theme的写法,全局就可以进行操作的,在就完成了适配,
主要的适配工作还是处理selector的颜色以及第三方框架引用了颜色的情况,另外处理深色兼容的适配。

另外关于重启生效的事情,recreate也是只能修改部分颜色而在我自己曾经写的一款app没有这样的问题,经过比较发现,我的那个app没有一个app是额外指定了主题的,所以状态栏颜色等都能自动马上生效,而且不需要通过代码修改状态栏颜色,因此这个bug是act的xml定义的主题和代码设置的主题冲突bug.

关于This Activity already has an action bar supplie

 This Activity already has an action bar supplied by the window decor. Do not request Window.FEATURE_SUPPORT_ACTION_BAR and set windowActionBar to false in your theme to use a Toolbar instead.
                                                                                                        at androidx.appcompat.app.AppCompatDelegateImpl.setSupportActionBar(Ap

如果在actionbar已经在xml中指定的情况(如果act没有指定则从application节点继承),在代码里面继续设置是会报错的,可在setContentView代码之前调用setTheme指定不包含actionbar的解除actionbar
则调用setSupportActionBar不会抛出已设置的错误。
setSupportActionBar的作用是把布局xml中的toolbar 依附到act中。

关于状态栏通过setTheme recreate有些东西不生效 ,重启不生效的原因 最根本原因是使用了MaterialComponents 主题而不是Theme.AppCompat,但是不使用MaterialComponents主题会导致google的组件样式无法全局生效,这是个矛盾,而且导致我的app大量崩溃,都是因为google的组件问题。
然后我继续找问题,还是改成了MaterialComponents主题,发现recreate又没有bug了,可以不重启全部更新。 之前又不行,我搞的精神崩溃了,也许是编译缓存问题??。。
总之最后通过recreate可以实现当前界面更新状态栏,不过第二个界面则不行,需要代码设置。不过体验比之前好了,不需要彻底重启app,由于保存了状态,基本上切换主题就感觉没有重启act的感觉,实际上是重建了的。

最后的最后我发现深色切换 回来或者切换过去,会导致很多地方不生效,比如状态栏,部分背景,
在recreate执行之前重新设置一遍主题可以解决此问题

    if (which != currentMode) {
                            dialog.dismiss();
                            ThemeMode value = values[which];
                            NightModeUtils.getInstance(AppContext.getInstance()).setThemeMode(value);
                            if (currentMode == 0 || which == 0) {//如果是从深色切换过来的就需要先执行setTheme,也就是执行了2次。
                                NightModeUtils.getInstance(getContext()).applySetting(getContext());
                                getActivity().recreate();
                            } else {
                                EventBus.getDefault().post(new ThemeChangeEvent().themeChange());

                            }

                        }

最后的最后是这样的

 ThemeMode value = values[which];
                            NightModeUtils.getInstance(AppContext.getInstance()).setThemeMode(value);
                            if (currentMode == 0 || which == 0) {
                                //call setTheme
                                NightModeUtils.getInstance(getContext()).applySetting(getContext());
                                //如果重启会发现部分没生效,深色模式的切换是不需要重启就能生效的
                            } else {
                                getActivity().recreate();
                            }

完整代码


import android.app.Activity
import android.content.Context
import android.content.res.TypedArray
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import com.sotrun.app.R
import com.sotrun.app.base.BaseActivity
import com.sotrun.app.utils.DateUtils
import java.io.Serializable
import java.util.*

open class NightModeUtils constructor(context: Context) {

    private val mSpf =
        context.applicationContext.getSharedPreferences(SP_THEME_NAME, Context.MODE_PRIVATE)

    companion object {

        @Volatile
        private var INSTANCE: NightModeUtils? = null

        fun getSpfThemeMode(context: Context): Int =
            context.getSharedPreferences(SP_THEME_NAME, Context.MODE_PRIVATE)
                .getInt(SPF_THEME_MODE, ThemeMode.MODE_FOLLOW_SYSTEM.intValue)

        @JvmStatic
        fun getInstance(context: Context): NightModeUtils =
            INSTANCE ?: synchronized(this) {
                INSTANCE ?: NightModeUtils(context).also {
                    INSTANCE = it
                }
            }

        private const val SPF_THEME_MODE = "theme_mode"
        private const val SP_THEME_NAME = "config_data"

        private const val SPF_THEME_TIMER = "theme_timer"
    }


    /** 获取设置 */
    private fun getSpfThemeMode(): Int =
        mSpf.getInt(SPF_THEME_MODE, ThemeMode.MODE_FOLLOW_SYSTEM.intValue)

    /** 设置 */
    private fun setSpfThemeMode(mode: Int) {
        /*
        注意,当两个编辑器同时修改首选项时
*时间,最后一个调用commit的人获胜。
*
         */
        mSpf.edit().putInt(SPF_THEME_MODE, mode).commit()
    }

    /** 获取时间设置 */
    private fun getSpfThemeTimer(): String = mSpf.getString(SPF_THEME_TIMER, null) ?: "18:00~08:00"

    /** 设置时间 */
    private fun saveSpfThemeTimer(timer: String) {
        mSpf.edit().putString(SPF_THEME_TIMER, timer).apply()
    }

    /** 获取设置好的ThemeMode */
    fun getThemeMode(): ThemeMode = ThemeMode.parseOfInt(getSpfThemeMode())

    /** 设置模式 */
    fun setThemeMode(mode: ThemeMode) {
        setSpfThemeMode(mode.intValue)
    }

    /** 获取设置好的定时时间 */
    fun getThemeTime(): ThemeTime {
        val timeStr = getSpfThemeTimer()
        return ThemeTime(
            beginHour = timeStr.split("~")[0].split(":")[0].toInt(),
            beginMinute = timeStr.split("~")[0].split(":")[1].toInt(),
            endHour = timeStr.split("~")[1].split(":")[0].toInt(),
            endMinute = timeStr.split("~")[1].split(":")[1].toInt()
        )
    }

    /** 设置定时时间 */
    fun setThemeTime(time: ThemeTime) {
        saveSpfThemeTimer(time.toTimerString())
    }

    /**
     * 应用存储的设置
     */
    fun applySetting() {
        applySetting(null);
    }

    fun applySetting(context: Context?): Int {
        var themeId = 0;
        // 检查主题
        val themeModeInt = getSpfThemeMode()
        val themeMode = ThemeMode.parseOfInt(themeModeInt)
        var disableActionBar: Boolean = false;
        var actName: String = "";
        var needActionBar:Int =-1;
        if (context is BaseActivity) {
            needActionBar = context.needActionBar()
            if( needActionBar == 1) {

                disableActionBar = false;
            }else if( needActionBar == 0) {
                disableActionBar = true;
            }
        }
            actName =  context?.javaClass?.simpleName + " ,是否禁用actionBar " + disableActionBar;
        if (context is AppCompatActivity) {
            if (needActionBar==-1) {
                var act: AppCompatActivity = context;
                val a: TypedArray = act.obtainStyledAttributes(R.styleable.AppCompatTheme)
                val needActionBar = a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)
                disableActionBar = !needActionBar

//                disableActionBar = if(act.supportActionBar!=null) true else true;//act.supportActionBar is WindowDecorActionBar
            }
        }
        Log.w("ThemeStyle", "主题模式 ${themeMode.name} act $actName");

        if (themeMode == ThemeMode.BLUE_THEME) {
            if (disableActionBar) {
                themeId = R.style.Theme_MyApplication_Blue_NoActionBar
            } else {
                themeId = R.style.Theme_MyApplication_Blue
            }
        } else if (themeMode == ThemeMode.YELLOW_THEME) {
            if (disableActionBar) {
                themeId = R.style.Theme_MyApplication_Yellow_NoActionBar
            } else {
                themeId = R.style.Theme_MyApplication_Yellow
            }
        } else if (themeMode == ThemeMode.PURPLE_THEME) {
            if (disableActionBar) {
                themeId = R.style.Theme_MyApplication_PURPLE_NoActionBar
            } else {
                themeId = R.style.Theme_MyApplication_PURPLE
            }
        } else if (themeMode == ThemeMode.GREEN_THEME) {
            if (disableActionBar) {
                themeId = R.style.Theme_MyApplication_GREEN_NoActionBar;
            } else {
                themeId = R.style.Theme_MyApplication_GREEN
            }
        } else if (themeMode == ThemeMode.GREY_THEME) {
            if (disableActionBar) {
                themeId = R.style.Theme_MyApplication_Grey_NoActionBar;
            } else {
                themeId = R.style.Theme_MyApplication_Grey
            }
        } else if (themeMode == ThemeMode.BROWN_THEME) {
            if (disableActionBar) {
                themeId = R.style.Theme_MyApplication_Brown_NoActionBar
            } else {
                themeId = R.style.Theme_MyApplication_Brown
            }
        } else if (themeMode == ThemeMode.DEEP_ORANGE_THEME) {
            if (disableActionBar) {
                themeId = R.style.Theme_MyApplication_DeepOrange_NoActionBar
            } else {
                themeId = R.style.Theme_MyApplication_DeepOrange
            }
        } else if (themeMode == ThemeMode.AMBER_THEME) {
            if (disableActionBar) {
                themeId = R.style.Theme_MyApplication_Amber_NoActionBar
            } else {
                themeId = R.style.Theme_MyApplication_Amber
            }
        } else if (themeMode == ThemeMode.CYAN_THEME) {
            if (disableActionBar) {
                themeId = R.style.Theme_MyApplication_Cyan_NoActionBar
            } else {
                themeId = R.style.Theme_MyApplication_Cyan
            }
        } else if (themeMode == ThemeMode.BLACK_THEME) {
            if (disableActionBar) {
                themeId = R.style.Theme_MyApplication_BLACK_NoActionBar;
            } else {
                themeId = R.style.Theme_MyApplication_BLACK;
            }
        } else {
            if (disableActionBar) {//如果appcontext设置actionbar,act manifest.xml没有声明解除actionbar,然后调用setSupportBar则会抛出异常,因此需要处理适配。
                themeId = R.style.Theme_MyApplication_DefaultBlue_NoActionBar
            } else {
                themeId = R.style.Theme_MyApplication_DefaultBlue
            }
        }

        if (themeId != 0) {
            if(context is Activity){
            context?.setTheme(themeId)

            }else{

//                context?.theme?.applyStyle(themeId, true)
            }
//            return themeId;
        }
        AppCompatDelegate.setDefaultNightMode(
            when (themeMode) {
                ThemeMode.MODE_ALWAYS_ON -> AppCompatDelegate.MODE_NIGHT_YES
                ThemeMode.MODE_ALWAYS_OFF -> AppCompatDelegate.MODE_NIGHT_NO
                ThemeMode.MODE_FOLLOW_SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
                else -> {
                    AppCompatDelegate.MODE_NIGHT_NO
                }
            }


        )
        return themeId

    }

    /**
     * 定时开启
     */
    private fun clock(): Int {
        val time = getThemeTime()
        val startTime = time.startTime.replace(":", ".").toFloat()
        val stopTime = time.stopTime.replace(":", ".").toFloat()
        val curTime = DateUtils.getFormatDate("HH.mm", Date()).toFloat()
        return if (stopTime > startTime) {
            // 结束时间和开始时间都在同一天
            if (curTime > startTime && curTime < stopTime) {
                AppCompatDelegate.MODE_NIGHT_YES
            } else {
                AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
            }
        } else {
            // 结束时间在开始时间的后一天
            if (curTime > startTime || curTime < stopTime) {
                AppCompatDelegate.MODE_NIGHT_YES
            } else {
                AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
            }
        }
    }
}

/**
 * 主题设置时间
 */
data class ThemeTime(
    // 开始小时
    val beginHour: Int,
    // 开始分钟
    val beginMinute: Int,
    // 结束小时
    val endHour: Int,
    // 结束分钟
    val endMinute: Int,
    // 开始时间
    var startTime: String = "00:00",
    // 结束时间
    var stopTime: String = "00:00"
) : Serializable {

    init {
        startTime = "${upTo2String(beginHour)}:${upTo2String(beginMinute)}"
        stopTime = "${upTo2String(endHour)}:${upTo2String(endMinute)}"
    }

    fun toTimerString(): String = "$startTime~$stopTime"

    // 保存成长度为2的字符串
    private fun upTo2String(value: Int): String {
        val valueStr = "$value"
        val sb = StringBuilder()
        if (valueStr.length < 2) {
            for (i in 0 until 2 - valueStr.length) {
                sb.append("0")
            }
        }
        sb.append(valueStr)
        return sb.toString().substring(sb.length - 2)
    }
}

设置代码

  getBinding().rlMyThemeSetting.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
                int currentMode = NightModeUtils.Companion.getSpfThemeMode(AppContext.getInstance());
                ThemeMode[] values = ThemeMode.values();
                ThemeColorSelectAdapter.Bean[] items = new ThemeColorSelectAdapter.Bean[values.length];
                for (int i = 0; i < values.length; i++) {
                    String title = AppContext.getInstance().getString(values[i].getStringValueId());
                    ThemeColorSelectAdapter.Bean bean = new ThemeColorSelectAdapter.Bean();
                    bean.title = title;
                    bean.color = values[i].getColor();
                    items[i] = bean;
                }
                ThemeColorSelectAdapter arrayAdapter = new ThemeColorSelectAdapter(
                        getActivity(), R.layout.view_color_check_item, items);
                builder.setSingleChoiceItems(arrayAdapter, currentMode, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        if (which != currentMode) {
                            dialog.dismiss();
                            ThemeMode value = values[which];
                            NightModeUtils.getInstance(AppContext.getInstance()).setThemeMode(value);
                            if (currentMode == 0 || which == 0) {
                                //call setTheme
                                NightModeUtils.getInstance(getContext()).applySetting(getContext());
                                //如果重启会发现部分没生效,深色模式的切换是不需要重启就能生效的,如果不先调用切换模式代码,调用recreate也是没用的。
                            } else {//setTheme没用
                                getActivity().recreate();
                            }
                            updateThemeStatusTextView();

                        }

                    }

                });
                builder.setTitle(AppContext.getStr(R.string.deep_color_theme_setting));
                builder.show();

            }
        });

thememode

enum class ThemeMode(val intValue: Int, val stringValueId: Int,val color:Int) {
    MODE_ALWAYS_ON(0, R.string.theme_mode_darkmode,R.color.black_no_dark),
    MODE_ALWAYS_OFF(1, R.string.theme_mode_default_blue,R.color.themeColorNoDark),
    MODE_FOLLOW_SYSTEM(2, R.string.theme_mode_dark_follow_system,R.color.themeColorNoDark),
    BLUE_THEME(3, R.string.theme_blue,R.color.themeColorBlueNoDark),
    PURPLE_THEME(4, R.string.theme_purple,R.color.themeColorPurpleNoDark),
    GREEN_THEME(5, R.string.theme_green,R.color.themeColorGreenNoDark),
    YELLOW_THEME(6, R.string.yellow,R.color.themeColorYellowNoDark),
    BROWN_THEME(7, R.string.theme_brown,R.color.themeColorBrownNoDark),
    GREY_THEME(8, R.string.theme_grey,R.color.themeColorGreyNoDark),
    AMBER_THEME(9, R.string.theme_amber,R.color.themeColorAmberNoDark),
    CYAN_THEME(10, R.string.theme_cyan,R.color.themeColorCyanNoDark),
    DEEP_ORANGE_THEME(11, R.string.theme_deep_orange,R.color.themeColorDeepOrangeNoDark),
    BLACK_THEME(12, R.string.theme_black,R.color.black_no_dark);
//    MODE_TIMER(30, R.string.theme_mode_3);

    companion object {


        @JvmStatic
        fun parseOfInt(intValue: Int) : ThemeMode {
            return when(intValue) {
                MODE_ALWAYS_ON.intValue -> MODE_ALWAYS_ON
                MODE_ALWAYS_OFF.intValue -> MODE_ALWAYS_OFF
                MODE_FOLLOW_SYSTEM.intValue -> MODE_FOLLOW_SYSTEM
//                MODE_TIMER.intValue -> MODE_TIMER
                BLUE_THEME.intValue -> BLUE_THEME
                YELLOW_THEME.intValue -> YELLOW_THEME
                PURPLE_THEME.intValue -> PURPLE_THEME
                GREEN_THEME.intValue -> GREEN_THEME
                BROWN_THEME.intValue -> BROWN_THEME
                GREY_THEME.intValue -> GREY_THEME
                CYAN_THEME.intValue -> CYAN_THEME
                AMBER_THEME.intValue -> AMBER_THEME
                DEEP_ORANGE_THEME.intValue -> DEEP_ORANGE_THEME
                BLACK_THEME.intValue -> BLACK_THEME
//              BLACK_THEME.intValue,!in 0.. 30-> BLACK_THEME
                else -> MODE_FOLLOW_SYSTEM
            }
        }
    }
}

最后的总结就是 更换主题 需要重启然后调用setTheme,但是更换深色模式则需要调用 AppCompatDelegate.setDefaultNightMode不能重启.
配色参考
http://mcg.mbitson.com/#!?mcgpalette0=%235768fe
https://www.materialpalette.com/light-blue/blue-grey
https://codecrafted.net/randommaterial

你可能感兴趣的:(【官方推荐方式】【原创】安卓换肤踩坑纯色模式app使用attr轻松实现定制颜色主题和深色主题)