¿Cómo _realmente_ cambiar programáticamente el color primario y el acento en Android Lollipop?

156

En primer lugar, esta pregunta hace una pregunta muy similar. Sin embargo, mi pregunta tiene una sutil diferencia.

Lo que me gustaría saber es si es posible cambiar mediante programación el colorPrimaryatributo de un tema a un color arbitrario .

Entonces, por ejemplo, tenemos:

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

En tiempo de ejecución, el usuario decide que quiere usarlo #ccffffcomo color primario. Por supuesto, no hay forma de que pueda crear temas para todos los colores posibles.

No me importa si tengo que hacer cosas extravagantes, como confiar en los elementos internos privados de Android, siempre que funcione con el SDK público.

Mi objetivo es tener eventualmente los ActionBar y todos los widgets como CheckBoxpara usar este color primario.

nhaarman
fuente
1
No tiene idea de qué son los "componentes internos privados de Android" para una versión de Android inexistente. No asuma que las "partes internas privadas" de L son las mismas que para cualquier cosa que L se convierta en términos de un lanzamiento de producción.
CommonsWare
No, no puede colocar datos arbitrarios en un tema. Dicho esto, colorPrimary solo se usa para el fondo de la barra de acción, el color de la barra reciente y el color de las notificaciones, y puede cambiar todo esto dinámicamente.
alanv
2
Creo que debería preguntar "Cómo cambiar el atributo de estilo en tiempo de ejecución", y por lo que vi la respuesta es que no puede. Sin embargo, tengo una idea que podría ayudarte. Utilice ContextWrapper personalizado y proporcione recursos propios. Mira esto: github.com/negusoft/holoaccent/blob/master/HoloAccent/src/com/… En general, este proyecto puede darte una idea de cómo hacerlo.
Mikooos
1
Simplemente haciendo una lluvia de ideas aquí, pero todo XML se convierte en archivos .dex que se cargan en su aplicación de Android como objetos Java correctamente. ¿No significa eso que deberíamos ser capaces de crear y configurar temas enteros a partir del código, así como generar el tema desde una fábrica aún por escribir? Suena como mucho trabajo.
G_V
1
@NiekHaarman, ¿alguna vez encontraste una manera?
gbhall

Respuestas:

187

Los temas son inmutables, no puedes.

Chris Banes
fuente
8
Gracias Chris! No es la respuesta que estaba buscando, pero supongo que tendré que vivir con ella :)
nhaarman
55
Hola @Chris Banes, pero ¿cómo cambió la aplicación de contacto el color de la barra de estado y el color de la barra de herramientas de acuerdo con el color del tema del contacto? Si es posible, creo que la necesidad de Niek Haarman no está demasiado lejos, ya que solo necesita almacenar el código de color que el usuario desea. ¿Podría por favor explicar más sobre esto? También quiero crear algo como esto, pero estoy realmente confundido con eso.
Swan
39
El color de la barra de estado se puede cambiar dinámicamente a través de Window.setStatusBarColor ().
Chris Banes
9
¿Es posible crear un tema mediante programación?
Andrew Orobator
3
Y al cambiar dinámicamente el color de la barra de estado, también puede cambiar el color de la barra de navegación a través de Window.setNavigationBarColor () - API 21 :)
user802421
65

Leí los comentarios sobre la aplicación de contactos y cómo usa un tema para cada contacto.

Probablemente, la aplicación de contactos tiene algunos temas predefinidos (para cada color primario del material desde aquí: http://www.google.com/design/spec/style/color.html ).

Puede aplicar un tema antes del método setContentView dentro del método onCreate.

Luego, la aplicación de contactos puede aplicar un tema al azar a cada usuario.

Este método es:

setTheme(R.style.MyRandomTheme);

Pero este método tiene un problema, por ejemplo, puede cambiar el color de la barra de herramientas, el color del efecto de desplazamiento, el color de ondulación, etc., pero no puede cambiar el color de la barra de estado y el color de la barra de navegación (si también desea cambiarlo).

Luego, para resolver este problema, puede usar el método antes y:

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

Estos dos métodos cambian el color de la barra de navegación y estado. Recuerde, si configura la barra de navegación como translúcida, no puede cambiar su color.

Este debería ser el 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);

}

Puede usar un interruptor y generar un número aleatorio para usar temas aleatorios o, como en la aplicación de contactos, cada contacto probablemente tenga un número predefinido asociado.

Una muestra de 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>

Lo siento por mi ingles.

JavierSegoviaCordoba
fuente
1
Gracias por su respuesta. Lamentablemente, mi solicitud fue utilizar un color arbitrario . Por supuesto, no es factible crear temas para todos los colores.
nhaarman
1
@DanielGomezRico: AFAIK puede anular el tema preestablecido independientemente de dónde se configuró inicialmente, siempre que lo haga lo suficientemente temprano. Como dice la respuesta "Puede aplicar un tema antes del método setContentView dentro del método onCreate".
ToolmakerSteve
50

Puede usar Theme.applyStyle para modificar su tema en tiempo de ejecución al aplicarle otro estilo.

Digamos que tiene estas definiciones 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>

Ahora puedes parchear tu tema en tiempo de ejecución de esta manera:

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

¡Se applyStyledebe llamar al método antes de que se infle el diseño! Por lo tanto, a menos que cargue la vista manualmente, debe aplicar estilos al tema antes de llamar setContentViewa su actividad.

Por supuesto, esto no se puede usar para especificar un color arbitrario, es decir, uno de cada 16 millones (256 3 ) colores. Pero si escribe un pequeño programa que genera las definiciones de estilo y el código Java para usted, entonces debería ser posible algo así como uno de 512 (8 3 ).

Lo que hace que esto sea interesante es que puede usar diferentes superposiciones de estilo para diferentes aspectos de su tema. Simplemente agregue algunas definiciones de superposición, colorAccentpor ejemplo. Ahora puede combinar diferentes valores para el color primario y el color de acento casi arbitrariamente.

Debe asegurarse de que las definiciones de tema de superposición no hereden accidentalmente un montón de definiciones de estilo de una definición de estilo principal. Por ejemplo, un estilo llamado AppTheme.OverlayRedimplícitamente hereda todos los estilos definidos AppThemey todas estas definiciones también se aplicarán cuando parchee el tema maestro. Por lo tanto, evite los puntos en los nombres de los temas superpuestos o use algo como Overlay.Redy defina Overlaycomo un estilo vacío.

devconsole
fuente
11
Intente llamar applyStyleantes de llamar setContentViewa su actividad y debería funcionar.
devconsole
1
sí okai, ¡entonces podría funcionar! Estoy buscando una manera de cambiar el coloraccente en el fragmento niveau no actividad! por eso no me funcionó tristemente: <¡Mi caso de uso es que quiero diferentes colores para FAB o indicadores de pestañas cuando cambio de pestañas, lo que comenzará un fragmento diferente!
Dennis Anderson
¡Gracias, esto ayudó mucho! Sin embargo, no puedo hacerlo varias veces. (uno para colorPrimary, uno para colorAccent, etc.). ¿Usted me podría ayudar? Gracias. stackoverflow.com/questions/41779821/…
Thomas Vos
1
Ese es el tipo de respuesta para la que quiero usar una segunda cuenta solo para +1 una vez más. Gracias.
Benoit Duffez
Muchas gracias, esto era lo que estaba buscando ... espero poder cambiar el color de acento del tema actual con este enfoque.
Nasib
32

He creado alguna solución para crear temas de cualquier color, tal vez esto pueda ser útil para alguien. API 9+

1. primero cree " res / values-v9 / " y coloque allí este archivo: styles.xml y la carpeta regular "res / values" se usará con sus estilos.

2. ponga este código en su 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. en AndroidManifest:

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

4. cree una nueva clase con el nombre "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. Actividad principal:

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 cambiar el color, simplemente reemplace Aleatorio con su RGB, espero que esto ayude.

ingrese la descripción de la imagen aquí

Hay un ejemplo completo: ColorTest.zip

IQ.feature
fuente
¿puedes compartir proyecto como?
Erselan Khan
Cree un nuevo proyecto, descargue el archivo - "styles.xml" y use el código anterior. Buena suerte.
IQ.feature
1
No puedo entenderlo, context.setTheme(context.getResources().getIdentifier("T_" + stringColor, "style", context.getPackageName()));¿puede darme una explicación o un enlace para seguir este tema?
Langusten Gustel
1
@ IQ.feature Creo que empujar su código al repositorio de Github es más adecuado para compartir código
Vadim Kotov
1
Solo para que quede claro para los demás porque me enamoré de esto ... hay un archivo res-v9 / styles.xml que declara una lista completa de temas con diferentes colores y el código esencialmente aplica uno de esos temas y recrea la actividad. Es lo que otras respuestas también han tratado de lograr ... es decir, es una solución al predefinir temas, no está creando temas de forma dinámica o programática.
Frezq
3

Utilicé el código de Dahnark pero también necesito cambiar el fondo de la barra de herramientas:

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
fuente
agregue este código: android: background = "? attr / colorPrimary", a su barra de herramientas (en el archivo .xml), por lo que no necesita establecer el fondo en java.
JavierSegoviaCordoba
Pero tengo dos barras de herramientas diferentes, una para un tema claro y otra para un tema oscuro. Si agrego el android: background = "? Attr / colorPrimary" tengo que usar algún tipo de selector.
lgallard
Tenía muchos temas y solo uso ese código. Echa un vistazo a esta aplicación: play.google.com/store/apps/…
JavierSegoviaCordoba
-1

No puede cambiar el color de colorPrimary, pero puede cambiar el tema de su aplicación agregando un nuevo estilo con un color diferente Color primario

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

y dentro del tema del conjunto de actividades

 setTheme(R.style.AppTheme_NewTheme);
 setContentView(R.layout.activity_main);
varghesekutty
fuente
Veo que ya hay respuestas anteriores que describen los estilos de cambio. ¿Bajo qué situación es su respuesta más apropiada que esas? Y para ser claros, la pregunta dice "En tiempo de ejecución, el usuario decide que quiere usar #ccffff como color primario. Por supuesto, no hay forma de que pueda crear temas para todos los colores posibles". Esto no resuelve esa necesidad; sin embargo, si nadie hubiera descrito cómo hacer esto, sería útil saberlo.
ToolmakerSteve
-2

de una actividad que puedes hacer:

getWindow().setStatusBarColor(i color);
yeahdixon
fuente
2
Es curioso que esta respuesta tenga -8 votos pero resuelva mi problema: D
Nabin Bhandari
-4

UTILICE UNA BARRA DE HERRAMIENTAS

Puede establecer un color de elemento de barra de herramientas personalizado dinámicamente creando una clase de barra de herramientas 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);
//                                  }
//                              });
                        }
                    }
                }
            }
        }
    }



}

luego consúltelo en su archivo de diseño. Ahora puede establecer un color personalizado usando

toolbar.setItemColor(Color.Red);

Fuentes:

Encontré la información para hacer esto aquí: Cómo cambiar dinámicamente el color de los iconos de la barra de herramientas de Android

y luego lo edité , lo mejoré y lo publiqué aquí: GitHub: AndroidDynamicToolbarItemColor

Michael Kern
fuente
Esto no responde la pregunta. Especialmente la parte "Mi objetivo es tener eventualmente la ActionBar y todos los widgets como un CheckBox para usar este color primario ".
nhaarman
Luego solo agregue para incluir una casilla de verificación. Por ejemplo, agregue if (v instanceof CheckBox) {themeChexnoxWithColor (toolbarIconsColor); No veo cómo esto no responde a su pregunta honestamente
Michael Kern
@nhaarman puedes establecer dinámicamente el color de la barra de acción como este stackoverflow.com/questions/23708637/change-actionbar-background-color-dynamically Simplemente no entiendo exactamente tu pregunta
Michael Kern
Tengo una aplicación donde el usuario puede elegir el color de la barra de acción y los colores de los elementos de la barra de acción. No veo qué más necesitas
Michael Kern
2
Esto fue muy útil para mí.
Firefly
-6

Esto es lo que puedes hacer:

escribir un archivo en una carpeta dibujable, nombremoslo 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>

luego configure su diseño (o lo que sea el caso) android:background="@drawable/background"

al configurar su tema, este color representaría lo mismo.

Ustaad
fuente