Como _realmente_ alterar programaticamente as cores principal e de destaque no Android Lollipop?

156

Antes de tudo, esta pergunta faz uma pergunta muito semelhante. No entanto, minha pergunta tem uma diferença sutil.

O que eu gostaria de saber é se é possível alterar programaticamente o colorPrimaryatributo de um tema para uma cor arbitrária ?

Então, por exemplo, temos:

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

Em tempo de execução, o usuário decide que deseja usar #ccffffcomo uma cor primária. Claro que não há como criar temas para todas as cores possíveis.

Não me importo se eu tiver que fazer coisas hacky, como confiar nos internos privados do Android, desde que funcione usando o SDK público.

Meu objetivo é eventualmente ter os ActionBar e todos os widgets como um CheckBoxpara usar esta cor primária.

nhaarman
fonte
1
Você não tem idéia do que são os "internos privados do Android" para uma versão inexistente do Android. Não assuma que os "internos internos" de L são iguais aos de L que se transforma em termos de um release de produção.
CommonsWare
Não, você não pode colocar dados arbitrários em um tema. Dito isso, o colorPrimary é usado apenas para o fundo da barra de ação, a cor da barra recente e a cor das notificações, e você pode alterar tudo isso dinamicamente.
alanv
2
Eu acho que você deve perguntar "Como alterar o atributo de estilo em tempo de execução" e, pelo que vi, a resposta é que você não pode. No entanto, tenho uma ideia que pode ajudá-lo. Use o ContextWrapper personalizado e forneça recursos próprios. Veja o seguinte: github.com/negusoft/holoaccent/blob/master/HoloAccent/src/com/… No geral, este projeto pode lhe dar uma idéia de como fazer isso.
Mikooos 13/09/14
1
Apenas comecei a trabalhar aqui, mas todo o XML é convertido em arquivos .dex que são carregados no seu aplicativo Android como objetos java corretamente. Isso não significa que devemos ser capazes de criar e definir temas inteiros a partir do código, além de gerar o tema de uma fábrica ainda a ser escrita? Soa como um monte de trabalho.
G_V
1
@NiekHaarman você já descobriu uma maneira?
gbhall

Respostas:

187

Os temas são imutáveis, você não pode.

Chris Banes
fonte
8
Obrigado Chris! Não é a resposta que eu estava procurando, mas eu acho que vou ter que viver com ela :)
nhaarman
5
Olá @ Chris Banes, mas como o aplicativo de contato mudou a cor da barra de status e da barra de ferramentas de acordo com a cor do tema do contato? Se for possível, acho que a necessidade de Niek Haarman não está muito longe, pois ele só precisa armazenar o código de cores que o usuário deseja. Poderia explicar mais sobre isso? Eu também quero criar algo assim, mas estou realmente confuso com isso.
Swan
39
A cor da barra de status pode ser alterada dinamicamente via Window.setStatusBarColor ().
quer
9
É possível criar um tema programaticamente?
Andrew Orobator
3
E enquanto mudando o status de cor bar dinamicamente você também pode mudar a cor da barra de navegação via Window.setNavigationBarColor () - API 21 :)
user802421
65

Eu li os comentários sobre o aplicativo de contatos e como ele usa um tema para cada contato.

Provavelmente, o aplicativo de contatos tem alguns temas predefinidos (para cada cor primária do material a partir daqui: http://www.google.com/design/spec/style/color.html ).

Você pode aplicar um tema antes do método setContentView dentro do método onCreate.

Em seguida, o aplicativo de contatos pode aplicar um tema aleatoriamente a cada usuário.

Este método é:

setTheme(R.style.MyRandomTheme);

Mas esse método tem um problema, por exemplo, ele pode alterar a cor da barra de ferramentas, a cor do efeito de rolagem, a cor da ondulação, etc.

Em seguida, para resolver esse problema, você pode usar o método antes e:

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

Este método dois altera a cor da barra de navegação e status. Lembre-se, se você definir sua barra de navegação como translúcida, não poderá mudar sua cor.

Este deve ser o código final:

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

}

Você pode usar um comutador e gerar um número aleatório para usar temas aleatórios ou, como no aplicativo de contatos, cada contato provavelmente tem um número predefinido associado.

Uma amostra do tema:

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

Desculpe pelo meu Inglês.

JavierSegoviaCordoba
fonte
1
Obrigado pela sua resposta. Infelizmente, meu pedido foi usar uma cor arbitrária . Obviamente, não é possível criar temas para todas as cores.
Nhaarman 19/01/2015
1
@DanielGomezRico - AFAIK, você pode substituir o tema predefinido, independentemente de onde foi definido inicialmente - desde que seja feito com antecedência. Como a resposta diz "Você pode aplicar um tema antes do método setContentView dentro do método onCreate".
Página
50

Você pode usar Theme.applyStyle para modificar seu tema em tempo de execução, aplicando outro estilo a ele.

Digamos que você tenha essas definições de estilo:

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

Agora você pode corrigir seu tema em tempo de execução da seguinte forma:

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

O método applyStyledeve ser chamado antes que o layout seja inflado! Portanto, a menos que você carregue a visualização manualmente, aplique estilos ao tema antes de chamar setContentViewsua atividade.

Obviamente, isso não pode ser usado para especificar uma cor arbitrária, ou seja, uma entre 16 milhões (256 3 ) cores. Mas se você escrever um pequeno programa que gere as definições de estilo e o código Java para você, algo como um em 512 (8 3 ) deve ser possível.

O que torna isso interessante é que você pode usar sobreposições de estilos diferentes para diferentes aspectos do seu tema. Basta adicionar algumas definições de sobreposição, colorAccentpor exemplo. Agora você pode combinar valores diferentes para cor primária e cor de destaque quase arbitrariamente.

Você deve garantir que suas definições de tema de sobreposição não herdam acidentalmente várias definições de estilo de uma definição de estilo pai. Por exemplo, um estilo chamado AppTheme.OverlayRedimplicitamente herda todos os estilos definidos AppThemee todas essas definições também serão aplicadas quando você corrigir o tema principal. Portanto, evite pontos nos nomes dos temas de sobreposição ou use algo como Overlay.Rede defina Overlaycomo um estilo vazio.

devconsole
fonte
11
Tente ligar applyStyleantes de ligar setContentViewpara sua atividade e ela deve funcionar.
devconsole 27/09/16
1
sim okai, então pode funcionar! Estou procurando uma maneira de mudar o coloraccent no fragmento niveau não atividade! por isso, infelizmente, não funcionou para mim: <Meu caso de uso é que desejo cores diferentes para os indicadores FAB ou tab ao mudar de tab que iniciarão um fragmento diferente!
Dennis Anderson
Obrigado, isso ajudou muito! No entanto, não consigo fazer isso várias vezes. (um para colorPrimary, outro para colorAccent, etc). Você poderia me ajudar? Obrigado. stackoverflow.com/questions/41779821/…
Thomas Vos
1
Esse é o tipo de resposta para a qual desejo usar uma segunda conta apenas para marcar com +1 mais uma vez. Obrigado.
Benoit Duffez 25/03
Muito obrigado, era isso que eu estava procurando ... espero poder alterar a cor de destaque do tema atual usando essa abordagem.
Nasib
32

Eu criei uma solução para criar temas de qualquer cor, talvez isso possa ser útil para alguém. API 9+

1. primeiro crie " res / values-v9 / " e coloque este arquivo: styles.xml e a pasta regular "res / values" será usada com seus estilos.

2. coloque esse código em seu 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. no AndroidManifest:

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

4) crie uma nova classe com o nome "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);
    }
}

Para mudar a cor, substitua o Random pelo RGB. Espero que ajude.

insira a descrição da imagem aqui

Há um exemplo completo: ColorTest.zip

IQ.feature
fonte
você pode compartilhar projeto como?
precisa saber é o seguinte
Crie um novo projeto, faça o download do arquivo - "styles.xml" e use o código acima. Boa sorte.
IQ.feature
1
Não consigo entender, context.setTheme(context.getResources().getIdentifier("T_" + stringColor, "style", context.getPackageName()));você pode me dar uma explicação ou um link para acompanhar esse tópico?
Langusten Gustel
1
@ IQ.feature Eu acho que empurrar o seu código para Github repo é mais adequado para partilha de códigos
Vadim Kotov
1
Só para deixar claro para os outros porque eu me apaixonei por isso ... existe um arquivo res-v9 / styles.xml que declara uma lista completa de temas com cores diferentes e o código essencialmente aplica um desses temas e recria a atividade. É o que outras respostas também tentaram alcançar. Ou seja, é uma solução alternativa, predefinindo temas que não estão criando temas de forma programática ou dinâmica.
frezq 25/06
3

Usei o código do Dahnark, mas também preciso alterar o plano de fundo da barra de ferramentas:

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
fonte
adicione este código: android: background = "? attr / colorPrimary", à sua barra de ferramentas (no arquivo .xml), para que você não precise definir o plano de fundo em java.
JavierSegoviaCordoba
Mas eu tenho duas barras de ferramentas diferentes, uma para um tema claro e outra para um tema sombrio. Se eu adicionar o android: background = "? Attr / colorPrimary", tenho que usar algum tipo de seletor.
Lgallard 19/05
Eu tinha muitos temas e só uso esse código. Dê uma olhada neste aplicativo: play.google.com/store/apps/…
JavierSegoviaCordoba
-1

Você não pode alterar a cor do colorPrimary, mas pode alterar o tema do seu aplicativo adicionando um novo estilo com uma cor diferente

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

e dentro do tema do conjunto de atividades

 setTheme(R.style.AppTheme_NewTheme);
 setContentView(R.layout.activity_main);
varghesekutty
fonte
Vejo que já existem respostas anteriores que descrevem estilos de alternância. Em que situação sua resposta é mais apropriada do que essas? E, para ficar claro, a pergunta diz "Em tempo de execução, o usuário decide que deseja usar #ccffff como uma cor primária. É claro que não há como criar temas para todas as cores possíveis". Isso não resolve essa necessidade; no entanto, se ninguém tivesse descrito como fazer isso, seria útil saber.
Home
-2

de uma atividade que você pode fazer:

getWindow().setStatusBarColor(i color);
yeahdixon
fonte
2
Engraçado que esta resposta tem -8 votos, mas resolve o meu problema: D
Nabin Bhandari
-4

USE UMA BARRA DE FERRAMENTAS

Você pode definir uma cor de item da barra de ferramentas personalizada dinamicamente criando uma classe de barra de ferramentas personalizada:

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



}

depois consulte-o no seu arquivo de layout. Agora você pode definir uma cor personalizada usando

toolbar.setItemColor(Color.Red);

Fontes:

Encontrei as informações para fazer isso aqui: Como alterar dinamicamente a cor dos ícones da Barra de Ferramentas Android

e depois editei, aprimorei e publiquei aqui: GitHub: AndroidDynamicToolbarItemColor

Michael Kern
fonte
Isso não responde à pergunta. Especialmente a parte "Meu objetivo é, eventualmente, ter o ActionBar e todos os widgets, como um CheckBox, para usar essa cor primária. ".
Nhaarman 21/05
Em seguida, basta adicionar para incluir uma caixa de seleção. Por exemplo, adicione if (v instanceof CheckBox) {themeChexnoxWithColor (toolbarIconsColor); Eu não vejo como isso não responder à sua pergunta honestamente
Michael Kern
@nhaarman você pode definir dinamicamente a cor ActionBar assim stackoverflow.com/questions/23708637/change-actionbar-background-color-dynamically Eu só não entendo exatamente a sua pergunta
Michael Kern
Eu tenho um aplicativo em que o usuário pode escolher a cor da barra de ação e as cores dos itens da barra de ação. Não vejo o que mais você precisa
Michael Kern
2
Isso foi muito útil para mim.
Firefly
-6

Isto é o que você PODE fazer:

escrever um arquivo na pasta drawable, vamos chamá-lo de 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>

em seguida, defina o layout (ou o que quer que seja o caso) android:background="@drawable/background"

ao definir seu tema, essa cor representaria a mesma.

Ustaad
fonte