日夜模式切换

    xiaoxiao2021-03-25  142

    日/夜模式切换作为一个App的基本功能经常会被使用到,接下来就举出一些常用的日/夜模式切换的方法

    使用UIMode的方法 这种方式操作起来比较简单,就是将不同模式下的资源分开存放,然后调用方法切换资源即可

    资源存放的路径

    切换资源的方法 if (isNight) { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); } else { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); } recreate();

    其中AppCompatDelegate.MODE_NIGHT_YES代表切换到夜间模式,AppCompatDelegate.MODE_NIGHT_NO代表切换到日间模式 如果是在新的Activity中切换日/夜模式则需要用RxBus(关于RxBus的用法可以参照我上篇博客)通知在后台的Activity调用recreate()重启Activity

    最终效果

    存在问题

    由于需要recreate(),会重绘Activity导致屏幕闪烁,重新加载Avtivity时需要注意Activity内元素的保存

    使用Theme 通过切换不同的主题来实现切换日/夜模式的效果

    在attrs.xml中设置主题中需要替换资源 <resources> <attr name="bg" format="color"></attr> <attr name="button_bg" format="color"></attr> <attr name="button_tv" format="color"></attr> </resources> 设置日/夜模式主题 <style name="Day" parent="AppTheme"> <item name="bg">#FFFFFF</item> <item name="button_bg">#A9A9A9</item> <item name="button_tv">#000000</item> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> </style> <style name="Night" parent="AppTheme"> <item name="bg">#4F4F4F</item> <item name="button_bg">#000000</item> <item name="button_tv">#FFFFFF</item> <item name="colorPrimary">#828282</item> <item name="colorPrimaryDark">#828282</item> </style> 在layout文件中使用arrts中的资源属性 android:backgroundTint="?attr/button_bg" 在Activity中切换主题 if(isNight){ setTheme(R.style.Night); }else{ setTheme(R.style.Day); }

    设置主题需要放在setContentView()之前,所以每次切换完日/夜模式后都需要重新加载Activity

    存在问题

    与UIMode方法相同由于需要recreate(),会重绘Activity导致屏幕闪烁,并且在有比较多的属性需要修改时会导致style比较复杂

    为了决解改变日/夜模式后屏幕闪烁的问题,我看了不少博客,终于找到了一个比较符合要求的项目,能够实现uiMode方法的不重建Activity切换日/夜模式。 项目地址:https://github.com/geminiwen/SkinSprite

    效果:

    具体思路是在Activity创建View的过程中注入自己的代码。 接下来分析一下这个lib的具体代码

    首先是SkinnableActivity,继承自AppCompatActivity,在这个Activity中对View的创建进行拦截,我们主要关注三个方法

    1)

    @Override protected void onCreate(@Nullable Bundle savedInstanceState) { LayoutInflater layoutInflater = LayoutInflater.from(this); LayoutInflaterCompat.setFactory(layoutInflater, this); super.onCreate(savedInstanceState); }

    注入自己的LayoutInflatorFactory,使inflate在这个LayoutInflaterFactory中执行

    2)

    @Override public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { if (mSkinnableViewInflater == null) { mSkinnableViewInflater = new SkinnableViewInflater(); } final boolean isPre21 = Build.VERSION.SDK_INT < 21; final boolean inheritContext = isPre21 && shouldInheritContext((ViewParent) parent); return mSkinnableViewInflater.createView(parent, name, context, attrs, inheritContext, isPre21, /* Only read android:theme pre-L (L+ handles this anyway) */ true, /* Read read app:theme as a fallback at all times for legacy reasons */ VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */ ); }

    这是具体需要创建View的方法,可以看到他将具体的创建逻辑放到了SkinnableViewInflater中,这个类之后在做分析

    3)

    public void setDayNightMode(@AppCompatDelegate.NightMode int nightMode) { final boolean isPost21 = Build.VERSION.SDK_INT >= 21; getDelegate().setLocalNightMode(nightMode); if (isPost21) { applyDayNightForStatusBar(); applyDayNightForActionBar(); } View decorView = getWindow().getDecorView(); applyDayNightForView(decorView); }

    这是一个我们之后需要切换日夜模式需要调用的方法,其中主要调用逻辑有

    getDelegate().setLocalNightMode(nightMode);

    对系统日/夜模式的资源进行切换

    if (isPost21) { applyDayNightForStatusBar(); applyDayNightForActionBar(); }

    如果api等级大于等于21(即5.0及以上版本)则更换状态栏和标题栏资源

    applyDayNightForView(decorView);

    对于内容中的日/夜资源进行切换,这个方法我们可以看下他的具体实现

    private void applyDayNightForView(View view) { if (view instanceof Skinnable) { Skinnable skinnable = (Skinnable) view; if (skinnable.isSkinnable()) { skinnable.applyDayNight(); } } if (view instanceof ViewGroup) { ViewGroup parent = (ViewGroup)view; int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { applyDayNightForView(parent.getChildAt(i)); } } }

    可以看到这是一个递归的方法,功能是遍历了view下所有的子view,对实现了Skinnable接口并且isSkinnable()返回true的view调用applyDayNight()。可以猜想到这些View就是自定义的View,这个applyDayNight()就是刷新View中资源的方法。

    接下来可以看下上面的SkinnableViewInflater这个类 public final View createView(View parent, final String name, @NonNull Context context,@NonNull AttributeSet attrs, boolean inheritContext,boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) { final Context originalContext = context; // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy // by using the parent's context if (inheritContext && parent != null) { context = parent.getContext(); } if (readAndroidTheme || readAppTheme) { // We then apply the theme on the context, if specified context = themifyContext(context, attrs, readAndroidTheme, readAppTheme); } if (wrapContext) { context = TintContextWrapper.wrap(context); } View view = null; // We need to 'inject' our tint aware Views in place of the standard framework versions switch (name) { case "TextView": view = new SkinnableTextView(context, attrs); break; case "ImageView": view = new AppCompatImageView(context, attrs); break; case "Button": view = new SkinnableButton(context, attrs); break; case "EditText": view = new AppCompatEditText(context, attrs); break; case "Spinner": view = new AppCompatSpinner(context, attrs); break; case "ImageButton": view = new AppCompatImageButton(context, attrs); break; case "CheckBox": view = new AppCompatCheckBox(context, attrs); break; case "RadioButton": view = new AppCompatRadioButton(context, attrs); break; case "CheckedTextView": view = new AppCompatCheckedTextView(context, attrs); break; case "AutoCompleteTextView": view = new AppCompatAutoCompleteTextView(context, attrs); break; case "MultiAutoCompleteTextView": view = new AppCompatMultiAutoCompleteTextView(context, attrs); break; case "RatingBar": view = new AppCompatRatingBar(context, attrs); break; case "SeekBar": view = new AppCompatSeekBar(context, attrs); break; case "LinearLayout": view = new SkinnableLinearLayout(context, attrs); break; case "FrameLayout": view = new SkinnableFrameLayout(context, attrs); break; case "RelativeLayout": view = new SkinnableRelativeLayout(context, attrs); break; case "android.support.v7.widget.Toolbar": view = new SkinnableToolbar(context, attrs); break; } if (view == null && originalContext != context) { // If the original context does not equal our themed context, then we need to manually // inflate it using the name so that android:theme takes effect. view = createViewFromTag(context, name, attrs); } if (view != null) { // If we have created a view, check it's android:onClick checkOnClickListener(view, attrs); } return view; }

    这个是SkinnableViewInflater中最主要的方法,根据name创建出不同的View,即自定义的View,这里并没有把所有的view都做出来,但是都大同小异,如果不够用还可以自己添加

    最后只剩下View的没有看了,由于View比较多而且其中的内容都相似,我们拿SkinnableLinearLayout作为案例进行研究 public SkinnableLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mAttrsHelper = new AttrsHelper(); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SkinnableView, defStyleAttr, 0); mAttrsHelper.storeAttributeResource(a, R.styleable.SkinnableView); a.recycle(); } @Override public void applyDayNight() { Context context = getContext(); int key; key = R.styleable.SkinnableView[R.styleable.SkinnableView_android_background]; int backgroundResource = mAttrsHelper.getAttributeResource(key); if (backgroundResource > 0) { Drawable background = ContextCompat.getDrawable(context, backgroundResource); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { setBackgroundDrawable(background); } else { setBackground(background); } } }

    SkinnableLinearLayout中的思路也比较简单,在构造方法中向mAttrsHelper添加如属性,在需要刷新是再从mAttrsHelper中取出。对于不同的View也只是属性的内容不同而已

    至此就是这个项目的大致源码,然后只需要将Activity继承SkinnableActivity,将uiMode中的 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); 改为 setDayNightMode(AppCompatDelegate.MODE_NIGHT_YES); 并且不需要调用recreate()方法。需要注意的是还需要在该Activity中添加上android:configChanges=”uiMode”。

    结语:这是我第一次写关于阅读源码的博客,尽管选了一个比较简单的lib但还是表达得比较凌乱。我之后还是会多多尝试写这方面的博客,努力提高自己的水平。

    参考博客: android 实现【夜晚模式】的另外一种思路 Android通过改变主题实现夜间模式 Android实现日夜间模式的深入理解

    转载请注明原文地址: https://ju.6miu.com/read-38662.html

    最新回复(0)