【问题标题】:How to programmatically create an Android theme style?如何以编程方式创建 Android 主题样式?
【发布时间】:2015-09-26 12:55:41
【问题描述】:

有很多关于通过 XML 创建或自定义 Android 主题样式的文档和教程,但还没有找到在代码中创建它的方法。关于如何在代码而不是 xml 中创建样式的任何想法?

这是示例 XML,需要在代码中以编程方式创建:

<resources>
  <style name="AppTheme" parent="android:Theme.Material">
    <item name="android:colorPrimary">@color/primary</item>
    <item name="android:colorPrimaryDark">@color/primary_dark</item>
    <item name="android:colorAccent">@color/accent</item>
  </style>
</resources>

【问题讨论】:

  • 您希望以编程方式完成的任何理由?如果您只想在运行时动态更改样式,可以查看此链接stackoverflow.com/questions/3241729/…。如果您只想在代码中检索样式属性 - 请检查:stackoverflow.com/questions/13719103/…
  • @random 没有具体原因,厌倦了 xml 所以随便玩玩看看是否可以动态创建主题
  • @random 我有一个理由。我需要动态地给颜色,目前颜色还没有决定。所以,我在 xml 中写了上面的主题,但我只想在第一个屏幕上动态地给出颜色。这样之后它将应用于所有屏幕。
  • 我对此也很感兴趣。原因与 ajay 相同。不过好像没办法。
  • @AjaySharma 但是您可以在 xml 中设置样式并从代码中加载来解决您的问题

标签: android android-styles


【解决方案1】:

简短回答:不可能以编程方式 创建一个主题并设置为应用程序主题(即使我们实现了创建 Theme 对象),但没有主题资源 ID。

详情:

当您调用setTheme 时,函数会影响ContextWrapper 的方法, 最后用资源id指针调用AssetManagerAssetManager类持有应用程序主题的方法,即JNI调用

native static final void applyThemeStyle(long theme, int res, boolean force);

如上所述,我们只能传递一个资源 id 来应用主题样式。但可能的选择是

  1. 虽然它仅限于Window 类特征常量。我们可以用 setFeatureDrawable & 特征常量来设置一些可绘制对象 比如FEATURE_ACTION_BARFEATURE_CONTEXT_MENU 等。
  2. 使用activity中的setTheme函数,我们可以从样式资源中设置主题,这将解决AjaySharma&Nathan在cmets中提到的问题

【讨论】:

  • 这是 2020 年。这还不可能吗?
  • @andylamax,我对 OP 的回答也适用于您的问题。
  • 使用 Jetpack Compose,您可以在运行时动态更改主题属性,所以是的……2020 年并没有让我们失望。 XML 和 UIKit 已死,Compose 和 SwiftUI 万岁
【解决方案2】:

TL;DR:不,你不能。这就是 Android 的工作原理,永远不会改变。


以编程方式创建 Android 主题永远不可能有一个简单明了的原因。也就是说,当应用程序启动时,Android 会创建一个显示应用程序android:windowBackground 的虚拟窗口。这时候app进程还在初始化中,当app开始执行,被启动的activity的Activity.onCreate方法返回时,dummy window被app window替换,显示那个activity。
因此,事实是,由于android:windowBackground是由主题设置的,但Android必须在应用程序启动之前访问它,所以主题必须是资源,以便可以从任何进程(当然包括系统进程)访问它们.)

此外,主题是资源,因此它们是完全不可变的。这就是安卓的工作原理。它不能突然改变,而且永远不可能改变。资源永远无法动态修改的另一个原因是直接暗示 APK 本身也需要修改 - 这也不是 APK 的本意。

有人可能会争辩说“所有事情都必须在后台以编程方式完成”。嗯,这是正确的。然而,android.content.res.Resources.Theme 类是 final 是有原因的,即确保没有任何内容被覆盖,从而保证其行为反映资源所说的内容,这对于访问活动的android:windowBackground 的系统进程是基础的主题与应用程序启动后的行为方式保持一致。对于 android.content.Context.obtainStyledResources 方法也是如此,这也是最终的。实际上,如果应用程序可以覆盖该方法,它将能够返回与资源不匹配的值,这是一个问题,因为只有在应用程序进程启动时才会发生这种情况,而原始的真实 android:windowBackground 已经显示。

【讨论】:

  • 这个论点通常是有道理的,但它让我想知道为什么存在以下方法:developer.android.com/reference/android/content/res/…Resources 公开了一个newTheme() 方法。
  • [1/2] @ZachSperske,虽然我不知道 newTheme() 方法,但我所描述的是 Android 实际工作方式的基础,所以我的回答应该保持正确。我猜你提到的方法是为Resources 类的实现创建一个Theme 对象。
  • [2/2] 这样,应该可以实现一个自定义的Resources 类,为属性提供自定义值,并为它创建一个Theme 对象,您将能够将其设置为活动的主题,从而在运行时实际创建主题。但是,清单中指定的活动主题必须是在 XML 中定义的主题,当应用程序的进程尚未创建时,活动仍将显示 XML 主题,而以编程方式创建的自定义主题只能显示一次活动已满载
  • 酷!这就说得通了。我一直在试图弄清楚如何在运行时将主题应用于 Drawable 并且正在苦苦挣扎。我能够通过使用newTheme() 方法创建一个Theme 来实现它,通过programmaticTheme.applyStyle(R.style.IconPrimaryTheme, force = true) 将我想要的样式应用于主题,然后使用ResourcesCompat.getDrawable(resources, R.drawable.myIcon, programmaticTheme) 在我想要的主题中获得一个drawable。跨度>
  • [1/2] @ZachSperske,我很高兴听到你回复说你已经想出了如何去做你一直希望做的事情。然而,我认为它本可以用不同的方式做得更好——而不是以编程方式从 XML 样式创建不同的主题并将其直接应用于Drawable,将 XML 样式应用为主题会更明智到实际持有可绘制对象的View
猜你喜欢
  • 1970-01-01
  • 2014-01-30
  • 1970-01-01
  • 2021-10-04
  • 1970-01-01
  • 1970-01-01
  • 2016-04-05
  • 2011-09-24
  • 2012-01-14
相关资源
最近更新 更多