Как _really_ программно изменить основной и акцентный цвет в Android Lollipop?


156

Прежде всего, этот вопрос задает очень похожий вопрос. Однако у моего вопроса есть небольшая разница.

Я хотел бы знать, можно ли программно изменить 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

Нет, вы не можете поместить произвольные данные в тему. Тем не менее, colorPrimary используется только для фона панели действий, цвета панели обновлений и цвета уведомлений, и вы можете изменять их все динамически.
alanv

2
Я думаю, что вы должны спросить «Как изменить атрибут стиля во время выполнения», и из того, что я увидел, вы не можете. Однако у меня есть идея, которая может вам помочь. Используйте пользовательский ContextWrapper и предоставляйте собственные ресурсы. Посмотрите на это: github.com/negusoft/holoaccent/blob/master/HoloAccent/src/com/… В целом этот проект может дать вам представление, как это сделать.
Mikooos

1
Просто мозги здесь, но все XML преобразуются в файлы .dex, которые загружаются в ваше приложение для Android как правильные java-объекты. Разве это не значит, что мы должны иметь возможность создавать и устанавливать целые темы из кода, а также генерировать темы из фабрики, которую еще предстоит написать? Похоже, много работы.
G_V

1
@NiekHaarman ты когда-нибудь придумывал способ?
gbhall

Ответы:


187

Темы неизменны, вы не можете.


8
Спасибо, Крис! Не тот ответ, который я искал, но, думаю, мне придется с этим
смириться

5
Привет @Chris Banes, но как приложение контакта изменило цвет строки состояния и панели инструментов в соответствии с цветом темы контакта? Если это возможно, я думаю, что Niek Haarman нуждается не так уж и далеко, так как ему нужно только хранить цветовой код, который хочет пользователь. Не могли бы вы объяснить подробнее об этом? Я также хочу создать что-то подобное, но меня это действительно смущает.
Лебедь

39
Цвет строки состояния можно динамически изменять с помощью Window.setStatusBarColor ().
Крис Бейнс

9
Можно ли создать тему программно?
Андрей Оробатор

3
И при динамическом изменении цвета строки состояния вы также можете изменить цвет панели навигации с помощью Window.setNavigationBarColor () - API 21 :)
user802421

65

Я читаю комментарии о приложении контактов и о том, как оно использует тему для каждого контакта.

Вероятно, приложение контактов имеет несколько предопределенных тем (для каждого основного цвета материала отсюда: http://www.google.com/design/spec/style/color.html ).

Вы можете применить тему перед методом setContentView внутри метода onCreate.

Затем приложение «Контакты» может применить тему произвольно для каждого пользователя.

Этот метод:

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>

Извините за мой английский.


1
Спасибо за ваш ответ. К сожалению, моя просьба была использовать произвольный цвет. Конечно, невозможно создавать темы для всех цветов.
nhaarman

1
@DanielGomezRico - AFAIK, вы можете переопределить предварительно заданную тему независимо от того, где она была изначально установлена ​​- при условии, что вы делаете это достаточно рано. В ответе говорится: «Вы можете применить тему перед методом setContentView внутри метода onCreate».
ToolmakerSteve

50

Вы можете использовать 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вашу деятельность.

Конечно, это не может использоваться для указания произвольного цвета, то есть одного из 16 миллионов (256 3 ) цветов. Но если вы напишете небольшую программу, которая генерирует определения стилей и код Java для вас, то должно быть возможно что-то вроде одной из 512 (8 3 ).

Что делает это интересным, так это то, что вы можете использовать разные стили наложения для разных аспектов вашей темы. Просто добавьте несколько определений наложения, colorAccentнапример. Теперь вы можете комбинировать разные значения основного цвета и цвета акцента практически произвольно.

Вы должны убедиться, что определения вашей оверлейной темы случайно не наследуют набор определений стилей из определения родительского стиля. Например, стиль, называемый AppTheme.OverlayRedнеявно, наследует все стили, определенные в, AppThemeи все эти определения также будут применены, когда вы исправите основную тему. Поэтому избегайте точек в именах наложенных тем или используйте что-то подобное Overlay.Redи определите Overlayпустой стиль.


11
Попробуйте позвонить, applyStyleпрежде чем звонить setContentViewв вашей деятельности, и это должно работать.
devconsole

1
да ладно, тогда это может сработать! я ищу способ изменить цвет в фрагменте niveau не активность! вот почему это не сработало для меня: к сожалению, я хочу использовать разные цвета для индикаторов FAB или вкладок при переключении с вкладок, которые начнут другой фрагмент!
Деннис Андерсон

Спасибо, это очень помогло! Однако я не могу сделать это несколько раз. (один для colorPrimary, один для colorAccent и т. д.). Не могли бы вы помочь мне? Спасибо. stackoverflow.com/questions/41779821/…
Томас Вос

1
Это тот ответ, для которого я хочу использовать второй аккаунт, чтобы еще раз +1. Спасибо.
Бенуа Даффес

Большое спасибо, это было то, что я искал .... я надеюсь, что смогу изменить цвет Accent текущей темы, используя этот подход.
Насиб

32

Я создал какое-то решение для создания любых цветовых тем, может быть, это кому-нибудь пригодится. 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


можете поделиться проектом как?
Эрселан Хан

Создайте новый проект, загрузите файл - "styles.xml" и используйте код выше. Удачи.
IQ.feature

1
Я не могу обернуть голову. Можете context.setTheme(context.getResources().getIdentifier("T_" + stringColor, "style", context.getPackageName()));ли вы дать мне объяснение или ссылку для продолжения этой темы?
Лангустен Густел

1
@ IQ.feature Я думаю, что отправка вашего кода в Github-репо больше подходит для совместного использования кода
Вадим Котов

1
Просто чтобы быть понятным для других, потому что я влюбился в это ... есть файл res-v9 / styles.xml, который объявляет целый список тем с разными цветами, и код по существу применяет одну из этих тем и воссоздает действие. Это то, чего также пытались достичь другие ответы ... т.е. это обходной путь, предопределяя темы, а не создавая темы программно или динамически.
frezq

3

Я использовал код Dahnark, но мне также нужно изменить фон панели инструментов:

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));
}

добавьте этот код: android: background = "? attr / colorPrimary" на панель инструментов (в файле .xml), чтобы вам не нужно было устанавливать фон в java.
ХавьерСеговияКордоба

Но у меня есть две разные панели инструментов, одна для светлой темы, другая для темной темы. Если я добавлю Android: background = "? Attr / colorPrimary", я должен использовать какой-то селектор.
lgallard

У меня было много тем, и я использую только этот код. Взгляните на это приложение: play.google.com/store/apps/…
JavierSegoviaCordoba

-1

Вы не можете изменить цвет 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

-2

из деятельности вы можете сделать:

getWindow().setStatusBarColor(i color);

2
Забавно, что этот ответ имеет -8 голосов, но решает мою проблему: D
Набин Бхандари

-4

ИСПОЛЬЗУЙТЕ ИНСТРУМЕНТ

Вы можете динамически установить цвет элемента панели инструментов , создав собственный класс панели инструментов:

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

а затем я отредактировал его, улучшил его и разместил здесь: GitHub: AndroidDynamicToolbarItemColor


Это не отвечает на вопрос. Особенно часть «Моя цель - в конечном итоге иметь ActionBar и все виджеты, такие как CheckBox, для использования этого основного цвета ».
nhaarman

Затем просто добавьте, чтобы включить флажок. Например, добавьте if (v instanceof CheckBox) {themeChexnoxWithColor (toolbarIconsColor); Я не понимаю, как это не отвечает на ваш вопрос честно
Майкл Керн

@nhaarman, вы можете динамически установить цвет панели действий, как этот stackoverflow.com/questions/23708637/change-actionbar-background-color-dynamically, я просто не совсем понимаю ваш вопрос
Майкл Керн

У меня есть приложение, где пользователь может выбрать цвет панели действий и цвета элемента панели действий. Я не вижу, что еще вам нужно
Майкл Керн

2
Это было очень полезно для меня.
Светлячок

-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"

при настройке вашей темы этот цвет будет представлять то же самое.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.