如何在Android Lollipop中以编程方式真正更改主要和强调颜色?

179

首先,这个问题提出了一个非常类似的问题。然而,我的问题有一个微妙的差别。

我想知道是否可能以编程方式将主题的colorPrimary属性更改为任意颜色?

例如,我们有:

<style name="AppTheme" parent="android:Theme.Material.Light">
    <item name="android:colorPrimary">#ff0000</item>
    <item name="android:colorAccent">#ff0000</item>
</style>
在运行时,用户决定将#ccffff作为主要颜色。当然,我无法为所有可能的颜色创建主题。
我不介意使用一些技巧,比如依赖于Android的私有内部机制,只要它使用公共SDK就可以工作。
我的目标是最终使ActionBar和所有小部件(如CheckBox)都使用这种主要颜色。

1
你不知道“Android的私有内部机制”是什么,因为不存在所谓的Android版本。不要假设L版本的“私有内部机制”与正式发布时L版本的机制相同。 - CommonsWare
2
我认为你应该问“如何在运行时更改样式属性”,从我所看到的答案是你不能。然而,我有一个可能会帮助你的想法。使用自定义ContextWrapper并提供自己的资源。看看这个:https://github.com/negusoft/holoaccent/blob/master/HoloAccent/src/com/negusoft/holoaccent/AccentResources.java 总的来说,这个项目可能会给你一个如何做到这一点的想法。 - Mikooos
1
只是闪现灵感,但所有的XML都会被转换为.dex文件,这些文件将作为Java对象加载到您的Android应用程序中。这是否意味着我们应该能够从代码中创建和设置整个主题,以及从尚未编写的工厂生成主题?听起来像是一项艰巨的工作。 - G_V
1
@NiekHaarman,你有没有想到什么办法? - gbhall
你好,我能否通过Java代码设置colorPrimary而不是通过XML文件进行设置? - Akshay kumar
显示剩余7条评论
10个回答

220

主题是不可变的,你无法修改。


12
谢谢,克里斯!虽然不是我想要的答案,但我想我不得不接受它 :) - nhaarman
5
嗨,@Chris Banes,但联系人应用程序如何根据联系人的主题颜色更改状态栏颜色和工具栏颜色?如果可能的话,我认为Niek Haarman的需求并不太远,因为他只需要存储用户想要的颜色代码。你能否进一步解释一下?我也想创建这样的东西,但真的很困惑。 - Swan
42
可以通过调用Window.setStatusBarColor()来动态改变状态栏的颜色。 - Chris Banes
9
能否通过编程的方式创建一个主题? - Andrew Orobator
4
在动态更改状态栏颜色的同时,您也可以通过 Window.setNavigationBarColor() 方法(API 21)来更改导航栏颜色 :) - user802421
显示剩余4条评论

70

我已阅读关于联系人应用程序的评论,以及它是如何为每个联系人使用主题的。

很可能,联系人应用程序有一些预定义主题(每个主题都基于此处的材料主色:http://www.google.com/design/spec/style/color.html)。

您可以在onCreate方法中的setContentView方法之前应用主题。

然后联系人应用程序可以随机地向每个用户应用主题。

该方法是:

setTheme(R.style.MyRandomTheme);

但是这种方法有一个问题,例如它可以更改工具栏颜色、滚动效果颜色、涟漪颜色等,但它无法更改状态栏颜色和导航栏颜色(如果您也想更改的话)。

那么要解决这个问题,您可以使用之前提到的方法并:

if (Build.VERSION.SDK_INT >= 21) {
        getWindow().setNavigationBarColor(getResources().getColor(R.color.md_red_500));
        getWindow().setStatusBarColor(getResources().getColor(R.color.md_red_700));
    }

这两个方法可以改变导航栏和状态栏的颜色。 请注意,如果您将导航栏设置为半透明,您将无法更改其颜色。

以下是最终代码:


请注意,如果您将导航栏设置为半透明,您将无法更改其颜色。

这应该是最终的代码:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setTheme(R.style.MyRandomTheme);
    if (Build.VERSION.SDK_INT >= 21) {
        getWindow().setNavigationBarColor(getResources().getColor(R.color.myrandomcolor1));
        getWindow().setStatusBarColor(getResources().getColor(R.color.myrandomcolor2));
    }
    setContentView(R.layout.activity_main);

}

你可以使用开关和生成随机数来使用随机主题,或者像联系人应用程序中一样,每个联系人可能有一个预定义的编号。

主题示例:

<style name="MyRandomTheme" parent="Theme.AppCompat.NoActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/myrandomcolor1</item>
    <item name="colorPrimaryDark">@color/myrandomcolor2</item>
    <item name="android:navigationBarColor">@color/myrandomcolor1</item>
</style>

2
谢谢您的回答。不幸的是,我的请求是使用任意颜色。当然,为所有颜色创建主题是不可行的。 - nhaarman
1
据我所知,只要你足够早地进行设置,就可以覆盖预设主题,无论最初设置在哪里。正如答案所说:“您可以在onCreate方法内的setContentView方法之前应用主题。” - ToolmakerSteve

61
你可以使用 Theme.applyStyle 函数来在运行时修改你的主题,通过应用另一个样式到它上面。
假设你有以下这些样式定义:
<style name="DefaultTheme" parent="Theme.AppCompat.Light">
    <item name="colorPrimary">@color/md_lime_500</item>
    <item name="colorPrimaryDark">@color/md_lime_700</item>
    <item name="colorAccent">@color/md_amber_A400</item>
</style>

<style name="OverlayPrimaryColorRed">
    <item name="colorPrimary">@color/md_red_500</item>
    <item name="colorPrimaryDark">@color/md_red_700</item>
</style>

<style name="OverlayPrimaryColorGreen">
    <item name="colorPrimary">@color/md_green_500</item>
    <item name="colorPrimaryDark">@color/md_green_700</item>
</style>

<style name="OverlayPrimaryColorBlue">
    <item name="colorPrimary">@color/md_blue_500</item>
    <item name="colorPrimaryDark">@color/md_blue_700</item>
</style>
现在你可以像这样在运行时修补你的主题:
getTheme().applyStyle(R.style.OverlayPrimaryColorGreen, true);

在填充布局之前必须调用applyStyle方法!因此,除非您手动加载视图,否则应在调用setContentView之前将样式应用于主题。

当然,这不能用于指定任意颜色,即1600万(2563)种颜色之一。但是,如果您编写一个小程序来为您生成样式定义和Java代码,那么可以实现512种(83)左右的颜色。

这使得它非常有趣,因为您可以为主题的不同方面使用不同的样式叠加层。例如,只需添加一些关于colorAccent的覆盖定义。现在,您可以将主要颜色和强调色几乎任意组合。

您应确保您的覆盖主题定义不会意外地继承一堆父级样式定义。例如,称为AppTheme.OverlayRed的样式隐含地继承了在AppTheme中定义的所有样式,并且在修补主题时也将应用所有这些定义。因此,要么避免在覆盖主题名称中使用点,要么使用类似于Overlay.Red的东西,并将Overlay定义为空样式。


12
尝试在活动中调用setContentView之前先调用applyStyle,这样应该可以解决问题。 - devconsole
1
好的,那么它可能会起作用!我正在寻找一种在片段级别而不是活动级别更改颜色强调的方法!这就是为什么它对我没有起作用的原因,可惜:<我的使用情况是,当从选项卡切换并启动不同的片段时,我希望FAB或选项卡指示器具有不同的颜色! - Dennis Anderson
谢谢,这很有帮助!但是,我无法多次执行它(一次用于colorPrimary,一次用于colorAccent等)。你能帮我吗?谢谢。http://stackoverflow.com/questions/41779821/gettheme-applystyle-multiple-times-without-overwriting-the-previous-one - Thomas Vos
2
这就是我想要使用第二个账户再+1一次的答案。谢谢。 - Benoit Duffez
非常感谢,这正是我在寻找的内容……我希望能够使用这种方法更改当前主题的强调颜色。 - Nasib

42

我已经创建了一些解决方案,可以制作任意颜色的主题,也许这对某些人会有用。API 9+

1. 首先创建"res/values-v9/"文件夹并将此文件放在其中:styles.xml, 然后使用常规的"res/values"文件夹与您的样式。

2. 将此代码放入您的res/values/styles.xml中:

<resources>
    <style name="AppTheme" parent="Theme.AppCompat.Light">
        <item name="colorPrimary">#000</item>
        <item name="colorPrimaryDark">#000</item>
        <item name="colorAccent">#000</item>
        <item name="android:windowAnimationStyle">@style/WindowAnimationTransition</item>
    </style>

    <style name="AppThemeDarkActionBar" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="colorPrimary">#000</item>
        <item name="colorPrimaryDark">#000</item>
        <item name="colorAccent">#000</item>
        <item name="android:windowAnimationStyle">@style/WindowAnimationTransition</item>
    </style>

    <style name="WindowAnimationTransition">
        <item name="android:windowEnterAnimation">@android:anim/fade_in</item>
        <item name="android:windowExitAnimation">@android:anim/fade_out</item>
    </style>
</resources>

3. 添加到 AndroidManifest:

<application android:theme="@style/AppThemeDarkActionBar">

4.创建一个名为“ThemeColors.java”的新类。

public class ThemeColors {

    private static final String NAME = "ThemeColors", KEY = "color";

    @ColorInt
    public int color;

    public ThemeColors(Context context) {
        SharedPreferences sharedPreferences = context.getSharedPreferences(NAME, Context.MODE_PRIVATE);
        String stringColor = sharedPreferences.getString(KEY, "004bff");
        color = Color.parseColor("#" + stringColor);

        if (isLightActionBar()) context.setTheme(R.style.AppTheme);
        context.setTheme(context.getResources().getIdentifier("T_" + stringColor, "style", context.getPackageName()));
    }

    public static void setNewThemeColor(Activity activity, int red, int green, int blue) {
        int colorStep = 15;
        red = Math.round(red / colorStep) * colorStep;
        green = Math.round(green / colorStep) * colorStep;
        blue = Math.round(blue / colorStep) * colorStep;

        String stringColor = Integer.toHexString(Color.rgb(red, green, blue)).substring(2);
        SharedPreferences.Editor editor = activity.getSharedPreferences(NAME, Context.MODE_PRIVATE).edit();
        editor.putString(KEY, stringColor);
        editor.apply();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) activity.recreate();
        else {
            Intent i = activity.getPackageManager().getLaunchIntentForPackage(activity.getPackageName());
            i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            activity.startActivity(i);
        }
    }

    private boolean isLightActionBar() {// Checking if title text color will be black
        int rgb = (Color.red(color) + Color.green(color) + Color.blue(color)) / 3;
        return rgb > 210;
    }
}

5. MainActivity:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new ThemeColors(this);
        setContentView(R.layout.activity_main);
    }

    public void buttonClick(View view){
        int red= new Random().nextInt(255);
        int green= new Random().nextInt(255);
        int blue= new Random().nextInt(255);
        ThemeColors.setNewThemeColor(MainActivity.this, red, green, blue);
    }
}

只需将 Random 替换为您的 RGB 即可更改颜色,希望这有所帮助。

输入图片说明

这里有一个完整的示例:ColorTest.zip

您可以查看这个GitHub 项目,该项目由Rumit Patel创建。


你能分享一下项目吗? - Erselan Khan
1
我无法理解 context.setTheme(context.getResources().getIdentifier("T_" + stringColor, "style", context.getPackageName()));,你能给我一个解释或者一个相关链接吗? - Langusten Gustel
2
@IQ.feature 我认为将你的代码推送到Github仓库更适合于代码共享。 - Vadim Kotov
14
为了让其他人清楚,因为我也犯了这个错误……有一个名为res-v9/styles.xml的文件,它声明了一整个主题列表,其中包含不同的颜色,代码实质上应用其中一个主题并重新创建活动。这就是其他答案也试图实现的内容……也就是说,这是通过预定义主题进行的解决方法,而不是通过程序或动态创建主题。 - frezq
1
好的,我会在帖子中添加你的链接... - IQ.feature
显示剩余9条评论

3

我使用了Dahnark的代码,但是我还需要更改ToolBar的背景:

if (dark_ui) {
    this.setTheme(R.style.Theme_Dark);

    if (Build.VERSION.SDK_INT >= 21) {
        getWindow().setNavigationBarColor(getResources().getColor(R.color.Theme_Dark_primary));
        getWindow().setStatusBarColor(getResources().getColor(R.color.Theme_Dark_primary_dark));
    }
} else {
    this.setTheme(R.style.Theme_Light);
}

setContentView(R.layout.activity_main);

toolbar = (Toolbar) findViewById(R.id.app_bar);

if(dark_ui) {
    toolbar.setBackgroundColor(getResources().getColor(R.color.Theme_Dark_primary));
}

将以下代码添加到您的工具栏(在.xml文件中):android:background="?attr/colorPrimary",这样您就不需要在Java中设置背景。 - JavierSegoviaCordoba
但是我有两个不同的工具栏,一个用于浅色主题,另一个用于深色主题。如果我添加 android:background="?attr/colorPrimary",我必须使用某种选择器。 - lgallard
我有很多主题,但只使用了那段代码。请看这个应用程序:https://play.google.com/store/apps/details?id=com.videumcorp.desarrolladorandroid.materialdesignnavigationdrawer - JavierSegoviaCordoba

1
您可以更改定义自己的主题,或在res > values > themes中自定义现有的Android主题,找到其中显示主要颜色的位置,并将其指向color.xml中定义的颜色。

enter image description here

 <style name="Theme.HelloWorld" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
    <!-- Primary brand color. -->
    <item name="colorPrimary">@color/my_color</item>
    <item name="colorPrimaryVariant">@color/my_color</item>
    <item name="colorOnPrimary">@color/white</item>

1

从一个活动中你可以做:

getWindow().setStatusBarColor(i color);

3
有趣的是,这个回答有-8票,但解决了我的问题 :D。 - Nabin Bhandari

0

你不能改变colorPrimary的颜色,但是你可以通过添加一个新的样式来改变应用程序的主题,使用不同的colorPrimary颜色。

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
</style>

<style name="AppTheme.NewTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="colorPrimary">@color/colorOne</item>
    <item name="colorPrimaryDark">@color/colorOneDark</item>
</style>

在活动中设置主题

 setTheme(R.style.AppTheme_NewTheme);
 setContentView(R.layout.activity_main);

我看到早期的答案已经描述了如何切换样式。在什么情况下,您的答案比那些更合适?并且要明确,问题说:“在运行时,用户决定使用#ccffff作为主要颜色。当然,我无法为所有可能的颜色创建主题。”这并没有解决这个需求;但是如果没有人描述如何做到这一点,了解这一点将会很有用。 - ToolmakerSteve
@ToolmakerSteve 你有找到任何解决方案吗? - sejn
@sejn - 你可以尝试类似于这个答案的方法 - 然后必须移除(从导航栈中弹出)当前屏幕上显示的任何内容,并再次推入一个新实例 - 强制重新"膨胀",创建一个具有修订主题的新视图。具体细节取决于您是否使用片段,并且可以轻松替换当前片段,或者必须重新启动当前活动。在其他地方搜索帮助该步骤,或发布一个新问题,描述您需要执行的操作 - 参考此问答,解释您尚未解决的步骤。 - ToolmakerSteve

-2

使用工具栏

您可以通过创建自定义工具栏类动态设置自定义工具栏项目颜色:

package view;

import android.app.Activity;
import android.content.Context;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.support.v7.internal.view.menu.ActionMenuItemView;
import android.support.v7.widget.ActionMenuView;
import android.support.v7.widget.Toolbar;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AutoCompleteTextView;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;

public class CustomToolbar extends Toolbar{

    public CustomToolbar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // TODO Auto-generated constructor stub
    }

    public CustomToolbar(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    public CustomToolbar(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
        ctxt = context;
    }

    int itemColor;
    Context ctxt;

    @Override 
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        Log.d("LL", "onLayout");
        super.onLayout(changed, l, t, r, b);
        colorizeToolbar(this, itemColor, (Activity) ctxt);
    } 

    public void setItemColor(int color){
        itemColor = color;
        colorizeToolbar(this, itemColor, (Activity) ctxt);
    }



    /**
     * Use this method to colorize toolbar icons to the desired target color
     * @param toolbarView toolbar view being colored
     * @param toolbarIconsColor the target color of toolbar icons
     * @param activity reference to activity needed to register observers
     */
    public static void colorizeToolbar(Toolbar toolbarView, int toolbarIconsColor, Activity activity) {
        final PorterDuffColorFilter colorFilter
                = new PorterDuffColorFilter(toolbarIconsColor, PorterDuff.Mode.SRC_IN);

        for(int i = 0; i < toolbarView.getChildCount(); i++) {
            final View v = toolbarView.getChildAt(i);

            doColorizing(v, colorFilter, toolbarIconsColor);
        }

      //Step 3: Changing the color of title and subtitle.
        toolbarView.setTitleTextColor(toolbarIconsColor);
        toolbarView.setSubtitleTextColor(toolbarIconsColor);
    }

    public static void doColorizing(View v, final ColorFilter colorFilter, int toolbarIconsColor){
        if(v instanceof ImageButton) {
            ((ImageButton)v).getDrawable().setAlpha(255);
            ((ImageButton)v).getDrawable().setColorFilter(colorFilter);
        }

        if(v instanceof ImageView) {
            ((ImageView)v).getDrawable().setAlpha(255);
            ((ImageView)v).getDrawable().setColorFilter(colorFilter);
        }

        if(v instanceof AutoCompleteTextView) {
            ((AutoCompleteTextView)v).setTextColor(toolbarIconsColor);
        }

        if(v instanceof TextView) {
            ((TextView)v).setTextColor(toolbarIconsColor);
        }

        if(v instanceof EditText) {
            ((EditText)v).setTextColor(toolbarIconsColor);
        }

        if (v instanceof ViewGroup){
            for (int lli =0; lli< ((ViewGroup)v).getChildCount(); lli ++){
                doColorizing(((ViewGroup)v).getChildAt(lli), colorFilter, toolbarIconsColor);
            }
        }

        if(v instanceof ActionMenuView) {
            for(int j = 0; j < ((ActionMenuView)v).getChildCount(); j++) {

                //Step 2: Changing the color of any ActionMenuViews - icons that
                //are not back button, nor text, nor overflow menu icon.
                final View innerView = ((ActionMenuView)v).getChildAt(j);

                if(innerView instanceof ActionMenuItemView) {
                    int drawablesCount = ((ActionMenuItemView)innerView).getCompoundDrawables().length;
                    for(int k = 0; k < drawablesCount; k++) {
                        if(((ActionMenuItemView)innerView).getCompoundDrawables()[k] != null) {
                            final int finalK = k;

                            //Important to set the color filter in seperate thread, 
                            //by adding it to the message queue
                            //Won't work otherwise. 
                            //Works fine for my case but needs more testing

                            ((ActionMenuItemView) innerView).getCompoundDrawables()[finalK].setColorFilter(colorFilter);

//                              innerView.post(new Runnable() {
//                                  @Override
//                                  public void run() {
//                                      ((ActionMenuItemView) innerView).getCompoundDrawables()[finalK].setColorFilter(colorFilter);
//                                  }
//                              });
                        }
                    }
                }
            }
        }
    }



}

然后在您的布局文件中引用它。现在,您可以使用自定义颜色。

toolbar.setItemColor(Color.Red);

来源:

我在这里找到了动态更改Android工具栏图标颜色的信息:如何动态更改Android工具栏图标颜色

然后我对其进行了编辑,改进,并在此处发布:GitHub: AndroidDynamicToolbarItemColor


这并没有回答问题,特别是“我的目标是最终使ActionBar _和所有小部件(如CheckBox)都使用这个主要颜色_”这一部分。 - nhaarman
然后只需添加复选框即可。例如,添加 if (v instanceof CheckBox) {themeChexnoxWithColor(toolbarIconsColor) ; 我不明白这个回答如何不诚实地回答了你的问题。 - Michael Kern
@nhaarman,你可以像这样动态设置操作栏的颜色:stackoverflow.com/questions/23708637/change-actionbar-background-color-dynamically。我只是不太明白你的问题。 - Michael Kern
我有一个应用程序,用户可以选择操作栏颜色和操作栏项颜色。我不知道你还需要什么。 - Michael Kern
即使这只回答了你问题的一部分,它仍然是一个重要且关键的部分。 - Michael Kern
2
这对我非常有帮助。 - Firefly

-6

以下是您可以做的:

在drawable文件夹中编写一个文件,我们将其命名为background.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
    <solid android:color="?attr/colorPrimary"/>
</shape>

然后设置您的布局(或任何情况下)android:background="@drawable/background"

在设置主题时,此颜色将表示相同。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接