Android進階——一閃而過結合觀察者模式靈活利用Framework層自帶的“日夜”間模式實現兩套皮膚的簡單切換

引言

相信Android的日間/夜間模式切換相信大家在平時使用 APP 的過程中都遇到過,實現日間/夜間模式切換的方案也有許多種(比如說在setContentView之前setTheme、遍歷ViewTree動態設置、),在我們Android 5.0版本之後Framework 層下的android.support.v7包中就自帶日夜間模式的切換框架,可以幫助我們靈活實現切換,但但僅僅適用於僅僅支持固定兩套皮膚的切換,而且需要重啓App或者Activity,所以呢,對於用戶體驗來說可能不那麼好,但是在一些特定的需求下是成本最低實現換膚的思路之一,所以瞭解下也無妨,

一、系統自帶換膚框架概述

在Android 5.0之後Framework 在以前原有res 目錄的自動適配的基礎上,新增了名爲values-nightdrawable-night等目錄的自動適配支持,並且通過AppCompatDelegate 提供了切換日夜間模式的API——AppCompatDelegate.setDefaultNightMode(int mode),其中mode有四個可選值:

  • MODE_NIGHT_NO—— 使用亮色(light)主題,不使用夜間模式
  • MODE_NIGHT_YES——使用暗色(dark)主題,使用夜間模式
  • MODE_NIGHT_AUTO——根據當前時間自動切換 亮色(light)/暗色(dark)主題
  • MODE_NIGHT_FOLLOW_SYSTEM(默認選項)——設置爲跟隨系統,通常爲MODE_NIGHT_NO

通過調用這個API告知系統當前希望是以何種模式,在App重啓或者Activity recreate時系統會像根據適配屏幕分辨率一樣,自動去對應的目錄下加載資源,從而完成了換膚。

二、實現換膚

在這裏插入圖片描述

1、引入support V7的依賴

implementation 'com.android.support:appcompat-v7:28.0.0'

2、在res目錄下新建兩套資源目錄

在res目錄下,新建名爲 values-night 目錄,當然還可以新建其他類型的目錄,比如說drawable-night等,總之就根據系統預訂的規範來命名,也可以直接在styles文件下配置,在加載資源的時候系統就會自動去進行適配。
在這裏插入圖片描述

3、調用AppCompatDelegate.setDefaultNightMode(int mode)

調用AppCompatDelegate.setDefaultNightMode(int mode)設置模式,調用之後還需要重啓App或者recreate Activity。

4、使用觀察者模式實現"即時"更新

通過以上三步,就可以完成Activity更新了,但是呢,如何去讓多個Activity 都能在我點擊換膚的時候,都能讓多個Activity同時更新呢,也有很多方案,通過廣播、通過監聽Activity的週期、通過註解等等,這裏我使用觀察者模式,但是並不意味着這是最佳的方案。

4.1、創建被觀察者角色

package com.crazymo.replaceskin.core;

import android.app.AlarmManager;
import android.app.Application;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;

import com.crazymo.replaceskin.MainActivity;

import java.util.Observable;

/**
 * @author : Crazy.Mo
 */
public class SkinManager extends Observable {
    private static SkinManager instance;
    /**
     * Activity生命週期回調
     */
    private MoActivityLifecycleCallbacks skinActivityLifecycle;
    private Application mContext;

    /**
     * 初始化 必須在Application中先進行初始化
     *
     * @param application
     */
    public static void init(Application application) {
        if (instance == null) {
            synchronized (SkinManager.class) {
                if (instance == null) {
                    instance = new SkinManager(application);
                }
            }
        }
    }

    public static SkinManager getInstance() {
        return instance;
    }


    private SkinManager(Application application) {
        mContext = application;
        //註冊Activity生命週期
        skinActivityLifecycle = new MoActivityLifecycleCallbacks();
        mContext.registerActivityLifecycleCallbacks(skinActivityLifecycle);
    }

    /**
     * 在需要更新皮膚時, 通知其他觀察者,進行更新
     */
    public void updateSkin(){
        setChanged();
        notifyObservers(null);
    }

    /**
     * 直接重啓
     */
    public void restart(){
        Intent intent = new Intent(mContext, MainActivity.class);
        PendingIntent restartIntent = PendingIntent.getActivity(
                mContext.getApplicationContext(), 0, intent, PendingIntent.FLAG_ONE_SHOT);
        AlarmManager mgr = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
        mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100,restartIntent);
        /** 1秒鐘後重啓應用 */
        /** 結束線程,一般與finishAll()一起使用 */
        android.os.Process.killProcess(android.os.Process.myPid());
    }
}

4.2、創建觀察者角色

package com.crazymo.replaceskin.core;

import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.AppCompatDelegate;
import java.util.Observable;
import java.util.Observer;

/**
 * @author : Crazy.Mo
 */
public class BaseSkinActivity extends AppCompatActivity implements Observer {

    @Override
    public void update(Observable o, Object arg) {
        if((boolean) Util.SharePreference.get(this,"config_skin","isNight",false)) {
            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
            Util.SharePreference.save(this,"config_skin","isNight",true);
        }else {
            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
            Util.SharePreference.save(this,"config_skin","isNight",false);
        }
        recreate();
        /////SkinManager.getInstance().restart();
    }
}

4.3、註冊觀察者

此處我是通過Application.ActivityLifecycleCallbacks監聽Activity的生命週期的,並且在onCreate時添加觀察者,在onDestroy時移除觀察者。

package com.crazymo.replaceskin.core;

import android.app.Activity;
import android.app.Application;
import android.os.Bundle;

/**
 * @author : Crazy.Mo
 */
public class MoActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        //添加觀察者
        SkinManager.getInstance().addObserver((BaseSkinActivity)activity);
    }

    @Override
    public void onActivityStarted(Activity activity) {
    }

    @Override
    public void onActivityResumed(Activity activity) {
    }

    @Override
    public void onActivityPaused(Activity activity) {
    }

    @Override
    public void onActivityStopped(Activity activity) {
    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
    }

    @Override
    public void onActivityDestroyed(Activity activity) {
        //移除觀察者
        SkinManager.getInstance().deleteObserver((BaseSkinActivity)activity);
    }
}

4.4、使用

  • 初始化
package com.crazymo.replaceskin;

import android.app.Application;
import android.support.v7.app.AppCompatDelegate;
import android.util.Log;

import com.crazymo.replaceskin.core.SkinManager;
import com.crazymo.replaceskin.core.Util;

/**
 * @author : Crazy.Mo
 */
public class MoApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        SkinManager.init(this);
        if((Boolean) Util.SharePreference.get(this,"config_skin","isNight",false)) {
            Log.e("cmskin","日間");
            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
            Util.SharePreference.save(this,"config_skin","isNight",false);
        }else {
            AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
            Log.e("cmskin","夜間");
            Util.SharePreference.save(this,"config_skin","isNight",true);
        }
    }
}
  • 通知觀察者
    public void replace(View view) {
        Util.SharePreference.save(this,"config_skin","isNight",false);
        SkinManager.getInstance().updateSkin();
    }

以上就是換膚的核心代碼了,不過這種方案有一些缺點,比如說以上代碼使用recreate進行刷新的話,只會更新在執行換膚前的打開過且未被銷燬回收的Activity(因爲我這裏偷懶是在Activity註冊觀察者的,你也可以通過註解來註冊觀察者),只能接收兩套皮膚;如果使用重啓App方式的話,可能用戶體驗不太良好,總之謹慎直接使用,原本不打算寫這篇的,但是發現有些文章把換膚說得糊里糊塗的,亂七八糟,就分享下,順便再一次介紹下觀察者模式,至於類似雲村、酷狗那種支持皮膚包的換膚,無侵入的原理,也很簡單,後面看情況,我也寫個系列文章總結並且手寫實現吧。源碼傳送門或者碼雲

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章