Jak _ naprawdę_ programowo zmienić kolor podstawowy i akcentujący w Android Lollipop?

156

Przede wszystkim to pytanie stawia bardzo podobne pytanie. Jednak moje pytanie ma subtelną różnicę.

Chciałabym wiedzieć, czy można programowo zmienić colorPrimaryatrybut motywu na dowolny kolor?

Na przykład mamy:

<style name="AppTheme" parent="android:Theme.Material.Light">
    <item name="android:colorPrimary">#ff0000</item>
    <item name="android:colorAccent">#ff0000</item>
</style>

W czasie wykonywania użytkownik decyduje, że chce użyć #ccffffkoloru podstawowego. Oczywiście nie ma możliwości tworzenia motywów dla wszystkich możliwych kolorów.

Nie mam nic przeciwko temu, że muszę robić hakerskie rzeczy, takie jak poleganie na prywatnych wewnętrznych elementach Androida, o ile działa on przy użyciu publicznego SDK.

Moim celem jest, aby w końcu mieć ActionBar i wszystkie widżety, jak CheckBoxkorzystać z tego koloru podstawowego.

nhaarman
źródło
1
Nie masz pojęcia, czym są „prywatne elementy wewnętrzne Androida” dla nieistniejącej wersji Androida. Nie zakładaj, że „prywatne elementy wewnętrzne” L są takie same, jak te dla tego, w co L zamienia się pod względem wydania produkcyjnego.
CommonsWare
Nie, nie możesz umieścić w motywie dowolnych danych. To powiedziawszy, colorPrimary jest używany tylko do tła paska akcji, koloru ostatnio wyświetlanego paska i koloru powiadomień, a wszystkie te elementy można zmieniać dynamicznie.
alanv
2
Myślę, że powinieneś zapytać „Jak zmienić atrybut stylu w czasie wykonywania” iz tego, co widziałem, odpowiedź brzmi: nie możesz. Mam jednak pomysł, który może ci pomóc. Użyj niestandardowego ContextWrapper i zapewnij własne zasoby. Spójrz na to: github.com/negusoft/holoaccent/blob/master/HoloAccent/src/com/… Ogólnie ten projekt może dać ci pomysł, jak to zrobić.
Mikooos
1
To tylko rozwodzenie mózgów, ale wszystkie pliki XML są konwertowane na pliki .dex, które są ładowane do aplikacji na Androida jako obiekty java. Czy to nie oznacza, że ​​powinniśmy być w stanie tworzyć i ustawiać całe motywy z kodu, a także generować motyw z fabryki, która jeszcze nie została napisana? Brzmi jak dużo pracy.
G_V
1
@NiekHaarman czy kiedykolwiek wymyśliłeś sposób?
gbhall

Odpowiedzi:

187

Motywy są niezmienne, nie możesz.

Chris Banes
źródło
8
Dzięki, Chris! Nie jest to odpowiedź, której szukałem, ale myślę, że będę musiał z nią żyć :)
nhaarman
5
Cześć @Chris Banes, ale w jaki sposób aplikacja do kontaktów zmieniła kolor paska stanu i paska narzędzi zgodnie z kolorem motywu kontaktu? Jeśli to możliwe, myślę, że to, czego Niek Haarman potrzebuje, nie jest zbyt dalekie, ponieważ musi on tylko przechowywać kod koloru, którego chce użytkownik. Czy mógłbyś wyjaśnić więcej na ten temat? Chcę też stworzyć coś takiego, ale jestem z tym bardzo zdezorientowany.
Swan,
39
Kolor paska stanu można zmieniać dynamicznie poprzez Window.setStatusBarColor ().
Chris Banes
9
Czy można programowo utworzyć motyw?
Andrew Orobator
3
Podczas dynamicznej zmiany koloru paska stanu można również zmienić kolor paska nawigacji za pomocą Window.setNavigationBarColor () - API 21 :)
user802421
65

Czytałem komentarze na temat aplikacji kontaktów i tego, jak używa ona motywu dla każdego kontaktu.

Prawdopodobnie aplikacja do kontaktów ma pewne predefiniowane motywy (dla każdego koloru podstawowego z tego materiału: http://www.google.com/design/spec/style/color.html ).

Motyw można zastosować przed metodą setContentView w metodzie onCreate.

Następnie aplikacja do kontaktów może losowo zastosować motyw dla każdego użytkownika.

Ta metoda to:

setTheme(R.style.MyRandomTheme);

Ale ta metoda ma problem, na przykład może zmienić kolor paska narzędzi, kolor efektu przewijania, kolor tętnienia itp., Ale nie może zmienić koloru paska stanu i koloru paska nawigacji (jeśli chcesz go również zmienić).

Następnie, aby rozwiązać ten problem, możesz użyć metody przed i:

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

Te dwie metody zmieniają kolor paska nawigacji i stanu. Pamiętaj, że jeśli ustawisz pasek nawigacji jako półprzezroczysty, nie możesz zmienić jego koloru.

To powinien być ostateczny kod:

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

}

Możesz użyć przełącznika i wygenerować losową liczbę, aby użyć losowych motywów lub, tak jak w aplikacji kontaktów, każdy kontakt prawdopodobnie ma przypisany wstępnie zdefiniowany numer.

Próbka motywu:

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

Przepraszam za mój angielski.

JavierSegoviaCordoba
źródło
1
Dziękuję za Twoją odpowiedź. Niestety moją prośbą było użycie dowolnego koloru. Oczywiście nie jest możliwe tworzenie motywów dla wszystkich kolorów.
nhaarman
1
@DanielGomezRico - AFAIK możesz nadpisać wstępnie ustawiony motyw niezależnie od tego, gdzie został ustawiony początkowo - o ile zrobisz to wystarczająco wcześnie. Jak mówi odpowiedź: „Motyw można zastosować przed metodą setContentView w metodzie onCreate”.
ToolmakerSteve
50

Możesz użyć Theme.applyStyle, aby zmodyfikować swój motyw w czasie wykonywania, stosując do niego inny styl.

Załóżmy, że masz te definicje stylu:

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

Teraz możesz załatać swój motyw w czasie wykonywania, na przykład:

getTheme().applyStyle(R.style.OverlayPrimaryColorGreen, true);

Metoda applyStylemusi zostać wywołana, zanim układ zostanie zawyżony! Jeśli więc nie załadujesz widoku ręcznie, powinieneś zastosować style do motywu przed wywołaniem setContentViewdziałania.

Oczywiście nie można tego użyć do określenia dowolnego koloru, tj. Jednego z 16 milionów (256 3 ) kolorów. Ale jeśli napiszesz mały program, który generuje dla ciebie definicje stylów i kod Javy, to coś w rodzaju jednego z 512 (8 3 powinno być możliwe ).

To, co sprawia, że ​​jest to interesujące, to fakt, że możesz używać różnych nakładek stylu dla różnych aspektów motywu. Po prostu dodaj na przykład kilka definicji nakładek colorAccent. Teraz możesz niemal dowolnie łączyć różne wartości koloru podstawowego i koloru akcentującego .

Należy się upewnić, że definicje motywów nakładek nie dziedziczą przypadkowo wielu definicji stylów z definicji stylu nadrzędnego. Na przykład styl o nazwie AppTheme.OverlayRedniejawnie dziedziczy wszystkie style zdefiniowane w programie, AppThemea wszystkie te definicje zostaną również zastosowane podczas łatania motywu głównego. Więc albo unikaj kropek w nazwach motywów nakładki, albo użyj czegoś podobnego Overlay.Redi zdefiniuj Overlayjako pusty styl.

devconsole
źródło
11
Spróbuj zadzwonić applyStyleprzed wezwaniem setContentViewswojej aktywności i powinno działać.
devconsole
1
tak okai, to może zadziałać! Szukam sposobu na zmianę akcentu kolorystycznego we fragmencie niveau, a nie aktywności! dlatego niestety nie zadziałało: <Moim przypadkiem jest to, że chcę różnych kolorów dla wskaźników FAB lub tabulatorów podczas przełączania z zakładek, które uruchamiają inny fragment!
Dennis Anderson
Dzięki temu bardzo pomogło! Jednak nie mogę tego zrobić wielokrotnie. (jeden dla colorPrimary, jeden dla colorAccent itp.). Czy mógłbyś mi pomóc? Dzięki. stackoverflow.com/questions/41779821/…
Thomas Vos
1
To jest rodzaj odpowiedzi, dla której chcę użyć drugiego konta, aby jeszcze raz dać +1. Dzięki.
Benoit Duffez,
Dziękuję bardzo, właśnie tego szukałem ... mam nadzieję, że mogę zmienić kolor akcentu bieżącego motywu, korzystając z tego podejścia.
Nasib
32

Stworzyłem rozwiązanie do tworzenia motywów w dowolnym kolorze, może to się komuś przyda. API 9+

1. Najpierw utwórz " res / values-v9 / " i umieść tam ten plik: styles.xml i zwykły folder "res / values" będą używane z Twoimi stylami.

2. Umieść ten kod w swoim 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. w AndroidManifest:

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

4. utwórz nową klasę o nazwie „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);
    }
}

Aby zmienić kolor, po prostu zastąp Random swoim RGB, mam nadzieję, że to pomoże.

wprowadź opis obrazu tutaj

Oto pełny przykład: ColorTest.zip

IQ. Funkcja
źródło
czy możesz udostępnić projekt jak?
Erselan Khan
Utwórz nowy projekt, pobierz plik - „styles.xml” i użyj powyższego kodu. Powodzenia.
IQ. Pojawi się
1
Nie mogę context.setTheme(context.getResources().getIdentifier("T_" + stringColor, "style", context.getPackageName()));się otrząsnąć, czy możesz podać mi wyjaśnienie lub link do dalszych działań na ten temat?
Langusten Gustel
1
@ IQ.feature Myślę, że przekazanie kodu do repozytorium Github jest bardziej odpowiednie do udostępniania kodu
Vadim Kotov,
1
Żeby było jasne dla innych, bo na to wpadłem ... jest plik res-v9 / styles.xml, który deklaruje całą listę motywów w różnych kolorach, a kod zasadniczo stosuje jeden z tych motywów i odtwarza aktywność. To jest to, co inne odpowiedzi również próbowały osiągnąć ... tj. Jest to obejście poprzez predefiniowanie motywów, a nie tworzenie programowych lub dynamicznych motywów.
frezq
3

Użyłem kodu Dahnark, ale muszę też zmienić tło 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));
}
lgallard
źródło
dodaj ten kod: android: background = "? attr / colorPrimary" do paska narzędzi (w pliku .xml), aby nie trzeba było ustawiać tła w java.
JavierSegoviaCordoba
Ale mam dwa różne paski narzędzi, jeden dla motywu jasnego, a drugi dla motywu ciemnego. Jeśli dodam android: background = "? Attr / colorPrimary", muszę użyć jakiegoś selektora.
lgallard
Miałem dużo motywów i używam tylko tego kodu. Spójrz na tę aplikację: play.google.com/store/apps/ ...
JavierSegoviaCordoba
-1

Nie możesz zmienićkoloru colorPrimary, ale możesz zmienić motyw swojej aplikacji, dodając nowy styl z innym koloremKolor podstawowy

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

i wewnątrz motywu zestawu czynności

 setTheme(R.style.AppTheme_NewTheme);
 setContentView(R.layout.activity_main);
varghesekutty
źródło
Widzę, że istnieją już wcześniejsze odpowiedzi, które opisują style przełączania. W jakiej sytuacji twoja odpowiedź jest bardziej odpowiednia niż te? Aby było jasne, pytanie brzmi: „W czasie wykonywania użytkownik decyduje, że chce użyć #ccffff jako koloru podstawowego. Oczywiście nie ma możliwości tworzenia motywów dla wszystkich możliwych kolorów”. To nie rozwiązuje tej potrzeby; gdyby jednak nikt nie opisał, jak to zrobić, warto wiedzieć.
ToolmakerSteve
-2

z aktywności, którą możesz wykonać:

getWindow().setStatusBarColor(i color);
yeahdixon
źródło
2
Zabawne, że ta odpowiedź ma -8 głosów, ale rozwiązuje mój problem: D
Nabin Bhandari
-4

UŻYJ PASKA NARZĘDZIOWEGO

Możesz ustawić niestandardowy pasek narzędzi dynamicznie kolor elementu tworząc niestandardową klasę paska narzędzi:

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



}

następnie odwołaj się do tego w pliku układu. Teraz możesz ustawić własny kolor za pomocą

toolbar.setItemColor(Color.Red);

Źródła:

Informacje, jak to zrobić, znalazłem tutaj: Jak dynamicznie zmieniać kolor ikon paska narzędzi Android

a potem go wyedytowałem, poprawiłem i opublikowałem tutaj: GitHub: AndroidDynamicToolbarItemColor

Michael Kern
źródło
To nie odpowiada na pytanie. Zwłaszcza część „Moim celem jest ostatecznie mieć ActionBar i wszystkie widżety, takie jak CheckBox, aby używać tego podstawowego koloru. ”.
nhaarman
Następnie po prostu dodaj, aby dodać pole wyboru. Na przykład dodaj if (v instanceof CheckBox) {themeChexnoxWithColor (toolbarIconsColor); Nie rozumiem, jak to nie odpowiada szczerze na twoje pytanie
Michael Kern
@nhaarman, możesz dynamicznie ustawić kolor paska akcji, tak jak ten stackoverflow.com/questions/23708637/change-actionbar-background-color-dynamically Po prostu nie do końca rozumiem twoje pytanie
Michael Kern
Mam aplikację, w której użytkownik może wybrać kolor paska akcji i kolory elementów paska działań. Nie widzę, czego jeszcze potrzebujesz
Michael Kern
2
To było dla mnie bardzo pomocne.
Firefly
-6

Oto, co MOŻESZ zrobić:

napisz plik w folderze do rysowania, nazwijmy go 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>

następnie ustaw swój układ (lub cokolwiek tak jest) android:background="@drawable/background"

przy ustawianiu motywu ten kolor będzie reprezentował to samo.

Ustaad
źródło