以编程方式全局更改应用程序主题

Change app theme globally programatically

我正在编写一个具有明暗模式的应用程序,如此处声明:

styles.xml

<style name="Noon" parent="Theme.AppCompat.NoActionBar">
    <item name="upper_bg">@drawable/day_sky_top</item>
    <item name="lower_bg">@drawable/day_sky</item>
    <item name="android:statusBarColor">@color/colorPrimaryDark</item>
</style>

<style name="Night" parent="Theme.AppCompat.NoActionBar">
    <item name="upper_bg">@drawable/night_sky_top</item>
    <item name="lower_bg">@drawable/night_sky</item>
    <item name="android:statusBarColor">@color/colorNightDark</item>
</style>

通过关注 this answer,我创建了以下文件:

/res/values/attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="customAttrs">
        <attr name="upper_bg" format="reference" />
        <attr name="lower_bg" format="reference" />
    </declare-styleable>
</resources>

并像这样自定义我的 ImageViews:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/top_bg"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="0.65"
            android:src="?attr/upper_bg"/>

        <ImageView
            android:id="@+id/bottom_bg"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="0.35"
            android:src="?attr/lower_bg"/>
    </LinearLayout>

(注意这是部分代码,所有标签都正确关闭。)

一切正常,前提是我有:

boolean night = true;
setTheme(night ? R.style.Night : R.style.Noon);
setContentView(R.layout.activity_main); // or whatever activity I'm in.

在我的应用程序的每个 Activity 上。 有没有办法 运行 此代码一次,以便我的主题在全球范围内改变?

您仍然可以添加 BaseActivity 以覆盖 override fun onCreate(),将 setTheme() 的责任委派给从它继承的任何其他 Activity

override fun onCreate(savedInstanceState: Bundle?) {
    val night = true
    setTheme(if (night) R.style.Night else R.style.Noon) 
}

如果您不喜欢BaseActivity,您可以在负责根据用户喜好设置主题的地方创建一个扩展功能:

fun Activity.setTheme() {
    val night = true
    // Or even have more than two theme styles
    this.setTheme(if (night) R.style.Night else R.style.Noon)
}

然后这样调用:

override fun onCreate(savedInstanceState: Bundle?) {
    setTheme()
}

更新:Java代码

这 class 将是您的基础 class 每个必需的 Activity

public abstract class BaseActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        boolean night = true;
        setTheme(night ? R.style.Night : R.style.Noon);
    }
}

现在,假设您必须实现“功能 1”和“功能 2”,这样您就可以从 BaseActivity.

继承它们

“功能 1”:

public class Feature1Activity extends BaseActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState); // <- BaseActivity's onCreate() will set theme for you
        setContentView(R.layout.activity_feature_1);
    }
}

“功能 2”:

public class Feature2Activity extends BaseActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState); // <- BaseActivity's onCreate() will set theme for you
        setContentView(R.layout.activity_feature_2);
    }
}

是的,你可以! 您必须使用 class AppCompatDelegate 的方法 setDefaultNightMode() 获取您要应用的主题的值,可用的值是 MODE_NIGHT_NO & MODE_NIGHT_YES & MODE_NIGHT_FOLLOW_SYSTEM for light , night 和 phone 分别是默认主题,你可以这样做:

AppCompatDelegate.setDefaultNightMode(THE_MODE_YOU_WANT);

重要的是要注意,从 AppCompat v1.0.0 开始,当调用此方法时,它会重新创建所有 运行 activity 以应用主题。

如果你运行 Api29以上,你应该考虑使用Force Dark,你可以在官方document中查看所有内容以更好地了解它。

尽情享受吧!