Comment changer _ vraiment_ par programme la couleur principale et d'accentuation dans Android Lollipop?

156

Tout d'abord, cette question pose une question très similaire. Cependant, ma question a une différence subtile.

Ce que j'aimerais savoir, c'est s'il est possible de changer par programme l' colorPrimaryattribut d'un thème en une couleur arbitraire ?

Ainsi par exemple, nous avons:

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

Au moment de l'exécution, l'utilisateur décide qu'il souhaite l'utiliser #ccffffcomme couleur principale. Bien sûr, je ne peux pas créer de thèmes pour toutes les couleurs possibles.

Cela ne me dérange pas si je dois faire des trucs piratés, comme compter sur les internes privés d'Android, tant que cela fonctionne en utilisant le SDK public.

Mon objectif est finalement d'avoir le ActionBar et tous les widgets comme un CheckBoxpour utiliser cette couleur primaire.

nhaarman
la source
1
Vous n'avez aucune idée de ce que sont les "composants internes privés d'Android" pour une version Android inexistante. Ne supposez pas que les «composants internes privés» de L sont les mêmes que ceux de tout ce que L devient en termes de version de production.
CommonsWare
Non, vous ne pouvez pas placer de données arbitraires dans un thème. Cela dit, colorPrimary n'est utilisé que pour l'arrière-plan de la barre d'action, la couleur de la barre récente et la couleur des notifications et vous pouvez tous les modifier dynamiquement.
alanv le
2
Je pense que vous devriez demander "Comment changer l'attribut de style dans le runtime", et d'après ce que j'ai vu, la réponse est que vous ne pouvez pas. Cependant, j'ai une idée qui pourrait vous aider. Utilisez ContextWrapper personnalisé et fournissez vos propres ressources. Regardez ceci: github.com/negusoft/holoaccent/blob/master/HoloAccent/src/com / ... Dans l'ensemble, ce projet pourrait vous donner une idée de la façon de procéder.
Mikooos
1
Juste un brainfarting ici, mais tout XML est converti en fichiers .dex qui sont chargés dans votre application Android en tant qu'objets java. Cela ne signifie-t-il pas que nous devrions être en mesure de créer et de définir des thèmes entiers à partir du code ainsi que de générer le thème à partir d'une usine encore à écrire? Ca m'a l'air d'être beaucoup de boulot.
G_V
1
@NiekHaarman avez-vous déjà trouvé un moyen?
gbhall

Réponses:

187

Les thèmes sont immuables, vous ne pouvez pas.

Chris Banes
la source
8
Merci Chris! Ce n'est pas la réponse que je cherchais, mais je suppose que je vais devoir vivre avec :)
nhaarman
5
Salut @Chris Banes, mais comment l'application de contact a-t-elle changé la couleur de la barre d'état et la couleur de la barre d'outils en fonction de la couleur du thème du contact? Si c'est possible, je pense que ce dont Niek Haarman a besoin n'est pas trop loin puisqu'il n'a besoin que de stocker le code couleur souhaité par l'utilisateur. Pourriez-vous s'il vous plaît expliquer plus à ce sujet? Je veux aussi créer quelque chose comme ça mais je suis vraiment confus avec ça.
Swan
39
La couleur de la barre d'état peut être modifiée dynamiquement via Window.setStatusBarColor ().
Chris Banes
9
Est-il possible de créer un thème par programmation?
Andrew Orobator
3
Et tout en modifiant dynamiquement la couleur de la barre d'état, vous pouvez également changer la couleur de la barre de navigation via Window.setNavigationBarColor () - API 21 :)
user802421
65

J'ai lu les commentaires sur l'application Contacts et comment elle utilise un thème pour chaque contact.

Probablement, l'application Contacts a des thèmes prédéfinis (pour chaque couleur primaire du matériau à partir d'ici: http://www.google.com/design/spec/style/color.html ).

Vous pouvez appliquer un thème avant une méthode setContentView dans la méthode onCreate.

Ensuite, l'application Contacts peut appliquer un thème au hasard à chaque utilisateur.

Cette méthode est:

setTheme(R.style.MyRandomTheme);

Mais cette méthode pose un problème, par exemple, elle peut changer la couleur de la barre d'outils, la couleur de l'effet de défilement, la couleur de l'ondulation, etc., mais elle ne peut pas changer la couleur de la barre d'état et la couleur de la barre de navigation (si vous voulez aussi la changer).

Ensuite, pour résoudre ce problème, vous pouvez utiliser la méthode avant et:

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

Ces deux méthodes modifient la navigation et la couleur de la barre d'état. N'oubliez pas que si vous définissez votre barre de navigation translucide, vous ne pouvez pas changer sa couleur.

Cela devrait être le code 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);

}

Vous pouvez utiliser un commutateur et générer un nombre aléatoire pour utiliser des thèmes aléatoires, ou, comme dans l'application Contacts, chaque contact a probablement un numéro prédéfini associé.

Un échantillon de thème:

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

Désolé pour mon anglais.

JavierSegoviaCordoue
la source
1
Merci pour votre réponse. Malheureusement, ma demande était d'utiliser une couleur arbitraire . Bien entendu, il n'est pas possible de créer des thèmes pour toutes les couleurs.
nhaarman
1
@DanielGomezRico - AFAIK, vous pouvez remplacer le thème prédéfini quel que soit l'endroit où il a été initialement défini - tant que vous le faites suffisamment tôt. Comme la réponse dit "Vous pouvez appliquer un thème avant la méthode setContentView dans la méthode onCreate."
ToolmakerSteve
50

Vous pouvez utiliser Theme.applyStyle pour modifier votre thème lors de l'exécution en lui appliquant un autre style.

Disons que vous avez ces définitions de style:

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

Vous pouvez maintenant patcher votre thème au moment de l'exécution comme ceci:

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

La méthode applyStyledoit être appelée avant que la mise en page ne soit gonflée! Donc, à moins que vous ne chargiez la vue manuellement, vous devez appliquer des styles au thème avant d'appeler setContentViewvotre activité.

Bien entendu, cela ne peut pas être utilisé pour spécifier une couleur arbitraire, c'est-à-dire une couleur sur 16 millions (256 3 ). Mais si vous écrivez un petit programme qui génère les définitions de style et le code Java pour vous, alors quelque chose comme un sur 512 (8 3 ) devrait être possible.

Ce qui rend cela intéressant, c'est que vous pouvez utiliser différentes superpositions de style pour différents aspects de votre thème. Ajoutez simplement quelques définitions de superposition colorAccentpar exemple. Vous pouvez désormais combiner des valeurs différentes pour la couleur primaire et la couleur d'accent presque arbitrairement.

Vous devez vous assurer que vos définitions de thème de superposition n'héritent pas accidentellement d'un ensemble de définitions de style d'une définition de style parent. Par exemple, un style appelé AppTheme.OverlayRedhérite implicitement de tous les styles définis dans AppThemeet toutes ces définitions seront également appliquées lorsque vous corrigez le thème principal. Donc, évitez les points dans les noms de thème de superposition ou utilisez quelque chose comme Overlay.Redet définissez-le Overlaycomme un style vide.

devconsole
la source
11
Essayez d'appeler applyStyleavant d'appeler setContentViewvotre activité et cela devrait fonctionner.
devconsole
1
oui okai, alors ça pourrait marcher! Je cherche un moyen de changer le coloraccent dans le niveau de fragment pas l'activité! c'est pourquoi cela n'a pas fonctionné pour moi malheureusement: <Mon cas d'utilisation est que je veux des couleurs différentes pour les indicateurs de FAB ou d'onglets lors du passage des onglets qui commenceront un fragment différent!
Dennis Anderson du
Merci, ça a beaucoup aidé! Cependant, je ne peux pas le faire plusieurs fois. (un pour colorPrimary, un pour colorAccent, etc.). Pourriez-vous m'aider? Merci. stackoverflow.com/questions/41779821/…
Thomas Vos
1
C'est le genre de réponse pour lequel je souhaite utiliser un deuxième compte pour +1 une fois de plus. Merci.
Benoit Duffez
Merci beaucoup, c'est ce que je recherchais .... j'espère pouvoir changer la couleur Accent du thème actuel en utilisant cette approche.
Nasib
32

J'ai créé une solution pour créer des thèmes de n'importe quelle couleur, peut-être que cela peut être utile pour quelqu'un. API 9+

1. créez d'abord " res / values-v9 / " et placez-y ce fichier: styles.xml et le "res / values" normal sera utilisé avec vos styles.

2. mettez ce code dans votre 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. dans AndroidManifest:

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

4. créez une nouvelle classe avec le nom "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);
    }
}

Pour changer de couleur, remplacez simplement Random par votre RVB, j'espère que cela vous aidera.

entrez la description de l'image ici

Il existe un exemple complet: ColorTest.zip

Fonctionnalité IQ.
la source
pouvez-vous partager un projet comme?
Erselan Khan
Créez un nouveau projet, téléchargez le fichier - "styles.xml" et utilisez le code ci-dessus. Bonne chance.
IQ.feature
1
Je ne peux pas comprendre, context.setTheme(context.getResources().getIdentifier("T_" + stringColor, "style", context.getPackageName()));pouvez-vous me donner une explication ou un lien pour suivre ce sujet?
Langusten Gustel
1
@ IQ.feature Je pense que pousser votre code vers le dépôt Github est plus approprié pour le partage de code
Vadim Kotov
1
Juste pour être clair pour les autres parce que j'ai craqué pour ça ... il y a un fichier res-v9 / styles.xml qui déclare toute une liste de thèmes avec des couleurs différentes et le code applique essentiellement l'un de ces thèmes et recrée l'activité. C'est ce que d'autres réponses ont également essayé de réaliser ... c'est-à-dire que c'est une solution de contournement en prédéfinissant des thèmes, ce n'est pas en créant des thèmes par programmation ou dynamiquement.
frezq le
3

J'ai utilisé le code de Dahnark mais j'ai également besoin de changer l'arrière-plan de 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
la source
ajoutez ce code: android: background = "? attr / colorPrimary", à votre barre d'outils (dans le fichier .xml), vous n'avez donc pas besoin de définir l'arrière-plan en java.
JavierSegoviaCordoba
Mais j'ai deux barres d'outils différentes, une pour un thème clair et une autre pour un thème sombre. Si j'ajoute l'android: background = "? Attr / colorPrimary" je dois utiliser une sorte de sélecteur.
lgallard
J'avais beaucoup de thèmes et je n'utilise que ce code. Jetez un œil à cette application: play.google.com/store/apps/…
JavierSegoviaCordoba
-1

Vous ne pouvez pas changer la couleur de colorPrimary, mais vous pouvez changer le thème de votre application en ajoutant un nouveau style avec une couleur différente

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

et à l'intérieur du thème de l'ensemble d'activités

 setTheme(R.style.AppTheme_NewTheme);
 setContentView(R.layout.activity_main);
varghesekutty
la source
Je vois qu'il existe déjà des réponses antérieures décrivant les styles de commutation. Dans quelle situation votre réponse est-elle plus appropriée que celles-là? Et pour être clair, la question dit "Au moment de l'exécution, l'utilisateur décide qu'il veut utiliser #ccffff comme couleur primaire. Bien sûr, je ne peux pas créer de thèmes pour toutes les couleurs possibles." Cela ne résout pas ce besoin; cependant, si personne n'avait décrit comment procéder, il serait utile de le savoir.
ToolmakerSteve
-2

à partir d'une activité que vous pouvez faire:

getWindow().setStatusBarColor(i color);
ouaisdixon
la source
2
Drôle que cette réponse ait -8 voix mais résout mon problème: D
Nabin Bhandari
-4

UTILISEZ UNE BARRE D'OUTILS

Vous pouvez définir dynamiquement une couleur d'élément de barre d'outils personnalisée en créant une classe de barre d'outils personnalisée:

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



}

puis faites-y référence dans votre fichier de mise en page. Vous pouvez maintenant définir une couleur personnalisée en utilisant

toolbar.setItemColor(Color.Red);

Sources:

J'ai trouvé les informations pour le faire ici: Comment changer dynamiquement la couleur des icônes de la barre d'outils Android

puis je l'ai édité, amélioré et posté ici: GitHub: AndroidDynamicToolbarItemColor

Michael Kern
la source
Cela ne répond pas à la question. Surtout la partie "Mon objectif est d'avoir finalement l'ActionBar et tous les widgets comme un CheckBox pour utiliser cette couleur primaire. ".
nhaarman
Ensuite, ajoutez simplement pour inclure une case à cocher. Par exemple, ajoutez if (v instanceof CheckBox) {themeChexnoxWithColor (toolbarIconsColor); Je ne vois pas comment cela ne répond pas honnêtement à votre question
Michael Kern
@nhaarman, vous pouvez définir dynamiquement la couleur de la barre d'action comme ceci stackoverflow.com/questions/23708637/change-actionbar-background-color-dynamically Je ne comprends tout simplement pas exactement votre question
Michael Kern
J'ai une application où l'utilisateur peut choisir la couleur de la barre d'action et les couleurs des éléments de la barre d'action. Je ne vois pas ce dont vous avez besoin d'autre
Michael Kern
2
Cela m'a été très utile.
Firefly
-6

Voici ce que vous POUVEZ faire:

écrire un fichier dans un dossier dessinable, appelons-le 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>

puis définissez votre mise en page (ou quel que soit le cas) android:background="@drawable/background"

lors de la définition de votre thème, cette couleur représenterait la même chose.

Ustaad
la source