Як _реально_ програматично змінити основний та акцентний колір в 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
Спасибі, Кріс! Не відповідь, яку я шукав, але, мабуть, мені доведеться з цим жити :)
nhaarman

5
Привіт @Chris Banes, але як контактна програма змінила колір рядка стану та колір панелі інструментів відповідно до кольору теми контакту? Якщо це можливо, я думаю, що потреба Ніка Хаармана не надто далеко, оскільки йому потрібно лише зберігати кольоровий код, який хоче користувач. Не могли б ви пояснити більше про це? Я також хочу створити щось подібне, але я дуже заплутаний у цьому.
Лебідь

39
Колір рядка стану можна динамічно змінювати за допомогою Window.setStatusBarColor ().
Кріс Бейнс

9
Чи можливо створити тему програмно?
Андрій Оробатор

3
І динамічно змінюючи колір рядка стану, ви також можете змінити колір панелі навігації через Window.setNavigationBarColor () - API 21 :)
користувач802421

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. Дякую.
Бенуа Дюффес

Дякую тобі, це було те, що я шукав .... я сподіваюся, що я можу змінити колір акценту поточної теми, використовуючи такий підхід.
Насіб

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. Основнаактивність:

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()));можете ви дати мені пояснення чи посилання, щоб продовжити цю тему?
Langusten Gustel

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

1
Просто для того, щоб бути зрозумілим для інших, тому що я потрапив за це ... є файл res-v9 / styles.xml, який оголошує цілий список тем з різними кольорами, і код по суті застосовує одну з цих тем і відтворює активність. Це те, чого намагалися досягти інші відповіді. Тобто це рішення шляхом попередньо визначених тем, це не програмно чи динамічно створює теми.
frezq

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

додайте цей код: android: background = "? attr / colorPrimary" на панель інструментів (у файл .xml), тому вам не потрібно встановлювати фон у Java.
JavierSegoviaCordoba

Але у мене є дві різні Панелі інструментів: одна для світлої теми, а інша - для темної. Якщо я додаю android: background = "? Attr / colorPrimary", я повинен використовувати якийсь селектор.
lgallard

У мене було багато тем, і я використовую лише цей код. Погляньте на цей додаток: play.google.com/store/apps/…
JavierSegoviaCordoba

-1

Ви не можете змінити колір colorPrimary, але ви можете змінити тему програми, додавши новий стиль з іншим кольоромPrimary color

<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 (панель інструментівIconsColor); Я не бачу, як це не відповідає на ваше питання чесно
Майкл Керн

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

У мене є додаток, де користувач може вибрати колір панелі дій та кольори елементів панелі дій. Я не бачу, що ще потрібно
Майкл Керн

2
Це мені дуже допомогло.
Firefly

-6

Ось що МОЖЕТЕ зробити:

записати файл у папку, що витягується, назвати його 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.