Android开发 关于避免切换主题时闪屏的几种方式

Android开发

关于避免切换主题时闪屏的几种方式


在activity中调用setTheme来切换夜间模式的方法可能大家有看过相关的文章了,但是调用setTheme设置的主题后界面并没有变化,这时需要调用activity的recreate方法另设置的主题生效,但是试过的朋友们都知道,activity调用recreate方法以后会有一瞬间的闪屏

今天写这篇文章的主题主要是关于如何避免setTheme切换主题后调用recreate的闪屏
关于如何通过改变theme更换主题的文章如果您还没有看过的话可以看一下这篇文章或者自行搜索一下

recreate
recreate.gif

效果实现

1.属性动画 实现

使用属性动画配合ArgbEvaluator这个类来对所有需要变换颜色的View设置一个渐变动画

属性动画
anim.gif

|


这个方法的缺点有一下几个:

  • 只要是需要有颜色变化的View要设置id并通过findViewById获取其对象,增加代码量,大家都知道设置id写findViewById有多蛋疼吧(笔者最讨厌设置控件的id了)
  • 每一个设置的attr的color就需要写一个属性动画,代码量又增多了
  • RecyclerView或ListView还有某些特殊控件如何进行颜色改变(如MD风格的Button RadioButton Switch 等不能通过设置background改变颜色的控件)

接下来来看一下代码中如何实现吧

通过以下方法获取主题中设置的attrs对应颜色

/**
 * @param theme 需要获取attrs颜色的theme
 * @param id 需要获取的attrs颜色id
 * @return color
 */
public static int getColorFromTheme(Resources.Theme theme, @AttrRes int id) 
    {
        TypedValue typedValue = new TypedValue();
        theme.resolveAttribute(id, typedValue, true);
        return typedValue.data;
    }

我们要做的是获取到当前颜色和更换后的主题颜色
activity中使用getTheme获取到Theme对象

int startColorPrimary = ThemeUtil.getColorFromTheme(getTheme()R.attr.colorPrimary);
setTheme(R.style.NightTheme);
int endColorPrimary = ThemeUtil.getColorFromTheme(getTheme(),R.attr.colorPrimary);

接下来就是设置一个属性动画配合ArgbEvaluator实现颜色的渐变

ValueAnimator animator = ValueAnimator
                .ofObject(new ArgbEvaluator(), 
                startColorPrimary, endColorPrimary)
                .setDuration(300);
                 
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int color = (int) animation.getAnimatedValue();
                //设置背景色的控件(Toolbar,背景layout等)
                mView.setBackgroundColor(color);
                //TextView字体颜色
                mTextView.setTextColor(color);
                //ImageView设置Tint
                mImageView.setColorFilter(color);
                //设置状态栏或导航栏颜色(API>=21)
                getWindow().setStatusBarColor(color);
                getWindow().setNavigationBarColor(color);
            }
        });
        
animator.start();

对所有需要变换颜色的控件进行操作就实现了切换效果

下面是关于RecyclerView和MD风格的Button RadioButton Switch更变颜色的方法
(其中RadioButton Switch的实现并不完美 如果有知道的欢迎补充)

通过获取RecyclerView当前在屏幕上显示的Item改变其颜色

int childCount = mRecyclerView.getChildCount();
for (int childIndex = 0; childIndex < childCount; childIndex++) {
                ViewGroup childView = (ViewGroup) mRecyclerView.getChildAt(childIndex);
                //这里的childView是RecyclerView每一个item的最外层view
                //可以通过id获取每一个item里的控件
                View mView = childView.findViewById(R.id.item_view);
                //这里设置属性动画改变view的颜色
                ............
            }

让 RecyclerView 缓存在 Pool 中的 Item 失效
这里的思路是通过反射拿到 AbsListView 类中的 RecycleBin 对象,然后同样再用反射去调用 clear 方法
此方法选取自知乎和简书的夜间模式实现套路

Class<RecyclerView> recyclerViewClass = RecyclerView.class;
            try {
                Field declaredField = recyclerViewClass.getDeclaredField("mRecycler");
                declaredField.setAccessible(true);
                Method declaredMethod = Class.forName(RecyclerView.Recycler.class.getName()).getDeclaredMethod("clear", (Class<?>[]) new Class[0]);
                declaredMethod.setAccessible(true);
                declaredMethod.invoke(declaredField.get(mRecyclerView), new Object[0]);
                RecyclerView.RecycledViewPool recycledViewPool = mRecyclerView.getRecycledViewPool();
                recycledViewPool.clear();
            } catch (Exception e) {
                e.printStackTrace();
            }

Button RadioButton Switch Progressbar通过设置Tint改变其颜色

//Switch(不完美,会改变未选择时thumb的颜色 默认为灰白色)
mSwitch.setThumbTintList(ColorStateList.valueOf(color));
//RadioButton(不完美,会改变未选中时圆圈的颜色 默认为灰色)
CompoundButtonCompat.setButtonTintList(mRadioButton, ColorStateList.valueOf(color));
//Button Progressbar
ViewCompat.setBackgroundTintList(mBotton, ColorStateList.valueOf(color));

通过属性动画实现的缺点比较明显,布局越复杂编写的难度以及代码量就越多,最终实现的效果也不是非常完美,难点在于特殊的控件如何改变颜色,这里有兴趣的读者可以自行研究一下,下面介绍一种更简单的方式


2.startActivity 实现

此方法原理和调用recreate是相似的,通过创建一个相同的activity并加上动画可以避免闪屏

startActivity
startActivity.gif

|

这个方法的难点在于:

  • 如何还原上一个activity的状态让用户感觉不到控件的变化

下面上代码

创建一个新的相同activity并设置渐入渐出动画然后结束当前activity

startActivity(new Intent(this, MainActivity.class));
overridePendingTransition(R.anim.start_anim, R.anim.out_anim);
finish();

这里通过启动activity创建的intent来传递以前旧界面的数据
比如EditText的输入内容RecyclerView的数据以及滑动距离

这里列出保存RecyclerView滑动距离 具体需要保存的数据需要根据界面的内容来编写

//获取RecyclerView的滑动距离
//调用getScrollY获取到的数据为0,也可以通过监听滑动事件保存滑动距离
private int getScrollYDistance() {
        LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
        int position = layoutManager.findFirstVisibleItemPosition();
        View firstVisibleChildView = layoutManager.findViewByPosition(position);
        int itemHeight = firstVisibleChildView.getHeight();
        return (position) * itemHeight - firstVisibleChildView.getTop();
    }
    
//通过intent传递数据到新activity
Intent intent = new Intent(this, MainActivity.class);
intent.putExtra("scrollY",getcrollYDistance());

//在activity的onCreate方法中还原数据
//RecyclerView的setScrollBy方法只有在view测量完毕后调用才能生效
mRecyclerView.post(new Runnable() {
            @Override
            public void run() {
              mRecyclerView.scrollBy(0,getIntent().getIntExtra("scrollY", 0));
            }
        });

使用startActivity这种方式实现的效果是不是比较简单,代码量相对也较小
难点就在于网络请求的list数据如何进行传递、保存,intent里面传递过多数据容易引起崩溃
因为是创建的新activity,最终实现的效果也比较完美


总结

以上就是我总结的如何避免切换主题时闪屏的方法,这是我第一次写技术总结,肯定有不少问题,希望需要的朋友们能够从种学习到新的知识
最后附上Demo链接https://github.com/Misutesu/NightModeDemo
(此Demo选择切换模式的RadioButton的选中状态好像有点问题,我暂时没有找到原因,如果有发现了的朋友欢迎留言告诉我)

感谢您的阅读

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容