Formes multi-dégradés

177

Je voudrais créer une forme qui ressemble à l'image suivante:

texte alternatif

Remarquez les dégradés de la moitié supérieure de la couleur 1 à la couleur 2, mais il y a une moitié inférieure qui dégradé de la couleur 3 à la couleur 4. Je sais comment créer une forme avec un seul dégradé, mais je ne sais pas comment diviser une forme en deux moitiés et faites 1 forme avec 2 dégradés différents.

Des idées?

Andrew
la source

Réponses:

383

Je ne pense pas que vous puissiez le faire en XML (du moins pas sous Android), mais j'ai trouvé une bonne solution publiée ici qui semble être d'une grande aide!

ShapeDrawable.ShaderFactory sf = new ShapeDrawable.ShaderFactory() {
    @Override
    public Shader resize(int width, int height) {
        LinearGradient lg = new LinearGradient(0, 0, width, height,
            new int[]{Color.GREEN, Color.GREEN, Color.WHITE, Color.WHITE},
            new float[]{0,0.5f,.55f,1}, Shader.TileMode.REPEAT);
        return lg;
    }
};

PaintDrawable p=new PaintDrawable();
p.setShape(new RectShape());
p.setShaderFactory(sf);

Fondamentalement, le tableau int vous permet de sélectionner plusieurs arrêts de couleur, et le tableau flottant suivant définit où ces arrêts sont positionnés (de 0 à 1). Vous pouvez ensuite, comme indiqué, simplement l'utiliser comme un Drawable standard.

Edit: Voici comment vous pouvez utiliser cela dans votre scénario. Disons que vous avez un bouton défini en XML comme ceci:

<Button
    android:id="@+id/thebutton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Press Me!"
    />

Vous mettriez ensuite quelque chose comme ça dans votre méthode onCreate ():

Button theButton = (Button)findViewById(R.id.thebutton);
ShapeDrawable.ShaderFactory sf = new ShapeDrawable.ShaderFactory() {
    @Override
    public Shader resize(int width, int height) {
        LinearGradient lg = new LinearGradient(0, 0, 0, theButton.getHeight(),
            new int[] { 
                Color.LIGHT_GREEN, 
                Color.WHITE, 
                Color.MID_GREEN, 
                Color.DARK_GREEN }, //substitute the correct colors for these
            new float[] {
                0, 0.45f, 0.55f, 1 },
            Shader.TileMode.REPEAT);
         return lg;
    }
};
PaintDrawable p = new PaintDrawable();
p.setShape(new RectShape());
p.setShaderFactory(sf);
theButton.setBackground((Drawable)p);

Je ne peux pas tester cela pour le moment, c'est du code de ma tête, mais en gros, remplacez ou ajoutez des arrêts pour les couleurs dont vous avez besoin. Fondamentalement, dans mon exemple, vous commenceriez par un vert clair, un fondu au blanc légèrement avant le centre (pour donner un fondu, plutôt qu'une transition dure), un fondu du blanc au vert moyen entre 45% et 55%, puis un fondu de vert moyen à vert foncé de 55% à la fin. Cela peut ne pas ressembler exactement à votre forme (pour le moment, je n'ai aucun moyen de tester ces couleurs), mais vous pouvez le modifier pour reproduire votre exemple.

Edit: En outre, le 0, 0, 0, theButton.getHeight()fait référence aux coordonnées x0, y0, x1, y1 du dégradé. Donc, fondamentalement, cela commence à x = 0 (côté gauche), y = 0 (en haut) et s'étire jusqu'à x = 0 (nous voulons un dégradé vertical, donc aucun angle de gauche à droite n'est nécessaire), y = la hauteur du bouton. Ainsi, le dégradé va à un angle de 90 degrés du haut du bouton au bas du bouton.

Edit: D'accord, j'ai donc une autre idée qui fonctionne, haha. À l'heure actuelle, cela fonctionne en XML, mais devrait également être faisable pour les formes en Java. C'est un peu complexe, et j'imagine qu'il existe un moyen de le simplifier en une seule forme, mais c'est ce que j'ai pour le moment:

green_horizontal_gradient.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle"
    >
    <corners
        android:radius="3dp"
        />
    <gradient
        android:angle="0"
        android:startColor="#FF63a34a"
        android:endColor="#FF477b36"
        android:type="linear"
        />    
</shape>

half_overlay.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle"
    >
    <solid
        android:color="#40000000"
        />
</shape>

layer_list.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list
    xmlns:android="http://schemas.android.com/apk/res/android"
    >
    <item
        android:drawable="@drawable/green_horizontal_gradient"
        android:id="@+id/green_gradient"
        />
    <item
        android:drawable="@drawable/half_overlay"
        android:id="@+id/half_overlay"
        android:top="50dp"
        />
</layer-list>

test.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center"
    >
    <TextView
        android:id="@+id/image_test"
        android:background="@drawable/layer_list"
        android:layout_width="fill_parent"
        android:layout_height="100dp"
        android:layout_marginLeft="15dp"
        android:layout_marginRight="15dp"
        android:gravity="center"
        android:text="Layer List Drawable!"
        android:textColor="@android:color/white"
        android:textStyle="bold"
        android:textSize="26sp"     
        />
</RelativeLayout>

D'accord, j'ai donc créé un dégradé de forme en XML pour le dégradé vert horizontal, défini à un angle de 0 degré, allant de la couleur verte gauche de la zone supérieure à la couleur verte droite. Ensuite, j'ai fait un rectangle de forme avec un gris semi-transparent. Je suis presque sûr que cela pourrait être intégré dans le XML de la liste de couches, évitant ce fichier supplémentaire, mais je ne sais pas comment. Mais d'accord, alors le genre de partie piratée entre dans le fichier XML layer_list. J'ai mis le dégradé vert comme couche inférieure, puis la demi-superposition comme deuxième couche, décalée du haut de 50dp. De toute évidence, vous voudriez que ce nombre soit toujours la moitié de la taille de votre vue, et non un 50dp fixe. Je ne pense pas que vous puissiez utiliser des pourcentages, cependant. À partir de là, je viens d'insérer un TextView dans ma mise en page test.xml, en utilisant le fichier layer_list.xml comme arrière-plan.

texte alternatif

Tada!

Une dernière modification : j'ai réalisé que vous pouvez simplement incorporer les formes dans la liste des calques dessinables en tant qu'éléments, ce qui signifie que vous n'avez plus besoin de 3 fichiers XML séparés! Vous pouvez obtenir le même résultat en les combinant comme ceci:

layer_list.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list
    xmlns:android="http://schemas.android.com/apk/res/android"
    >
    <item>
        <shape
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:shape="rectangle"
            >
            <corners
                android:radius="3dp"
                />
            <gradient
                android:angle="0"
                android:startColor="#FF63a34a"
                android:endColor="#FF477b36"
                android:type="linear"
                />    
        </shape>
    </item>
    <item
        android:top="50dp" 
        >
        <shape
            android:shape="rectangle"
            >
            <solid
                android:color="#40000000"
                />
        </shape>            
    </item>
</layer-list>

Vous pouvez superposer autant d'éléments que vous le souhaitez de cette façon! Je peux essayer de jouer et voir si je peux obtenir un résultat plus polyvalent grâce à Java.

Je pense que c'est la dernière modification ... : D'accord, vous pouvez donc définitivement corriger le positionnement via Java, comme suit:

    TextView tv = (TextView)findViewById(R.id.image_test);
    LayerDrawable ld = (LayerDrawable)tv.getBackground();
    int topInset = tv.getHeight() / 2 ; //does not work!
    ld.setLayerInset(1, 0, topInset, 0, 0);
    tv.setBackgroundDrawable(ld);

Toutefois! Cela conduit à un autre problème ennuyeux en ce que vous ne pouvez pas mesurer le TextView avant qu'il n'ait été dessiné. Je ne sais pas encore comment vous pouvez accomplir cela ... mais l'insertion manuelle d'un numéro pour topInset fonctionne.

J'ai menti, encore une modification

OK, découvrez comment mettre à jour manuellement cette couche dessinable pour qu'elle corresponde à la hauteur du conteneur, une description complète peut être trouvée ici . Ce code devrait aller dans votre méthode onCreate ():

final TextView tv = (TextView)findViewById(R.id.image_test);
        ViewTreeObserver vto = tv.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                LayerDrawable ld = (LayerDrawable)tv.getBackground();
                ld.setLayerInset(1, 0, tv.getHeight() / 2, 0, 0);
            }
        });

Et j'ai fini! Ouf! :)

Kevin Coppock
la source
Le problème avec ceci est que le gradient est vertical. J'ai besoin de la forme séparée verticalement (c'est-à-dire: une ligne horizontale), mais j'ai besoin que le dégradé soit horizontal (c'est-à-dire: déplacez-vous de gauche à droite). Avec cet exemple, le séparateur et le dégradé sont tous deux verticaux.
Andrew
Oh d'accord, je comprends ce que vous dites maintenant. (Les images sont bloquées au travail, donc je ne pouvais voir les vôtres qu'à partir de mon téléphone, difficile de bien dire les détails). Malheureusement, je n'ai pas de solution à ce problème, même si je suppose que vous pourriez créer une forme dessinable avec deux rectangles, un avec un dégradé horizontal, un avec un dégradé vertical blanc-noir à la moitié de la taille à la moitié de l'opacité, aligné vers le bas. Quant à savoir comment faire cela, je n'ai pas d'exemples spécifiques pour le moment.
Kevin Coppock
Oui, c'est ce que j'espérais pouvoir faire, mais je n'ai tout simplement pas encore compris. J'apprécie votre aide
Andrew
Vous êtes les bienvenus! Je l'ai encore essayé et je pense avoir à peu près ce que vous cherchez. Vous devrez peut-être les créer dynamiquement via Java (comme indiqué dans les commentaires), mais cela fonctionne pour le moment pour les tailles fixes.
Kevin Coppock
17
C'est le genre de réponse que vous voulez donner au moins +10. J'adore la sensation de combat dans votre réponse: vous contre l'API Android, qui va gagner? Saurons nous un jour? ;) De plus, c'est un matériel d'apprentissage super utile puisque vous avez conservé toutes les bizarreries que vous avez rencontrées.
andr
28

Vous pouvez superposer des formes de dégradé dans le xml en utilisant une liste de calques. Imaginez un bouton avec l'état par défaut comme ci-dessous, où le deuxième élément est semi-transparent. Cela ajoute une sorte de vignettage. (Veuillez excuser les couleurs personnalisées.)

<!-- Normal state. -->
<item>
    <layer-list>
        <item>  
            <shape>
                <gradient 
                    android:startColor="@color/grey_light"
                    android:endColor="@color/grey_dark"
                    android:type="linear"
                    android:angle="270"
                    android:centerColor="@color/grey_mediumtodark" />
                <stroke
                    android:width="1dp"
                    android:color="@color/grey_dark" />
                <corners
                    android:radius="5dp" />
            </shape>
        </item>
        <item>  
            <shape>
                <gradient 
                    android:startColor="#00666666"
                    android:endColor="#77666666"
                    android:type="radial"
                    android:gradientRadius="200"
                    android:centerColor="#00666666"
                    android:centerX="0.5"
                    android:centerY="0" />
                <stroke
                    android:width="1dp"
                    android:color="@color/grey_dark" />
                <corners
                    android:radius="5dp" />
            </shape>
        </item>
    </layer-list>
</item>

LordParsley
la source
4

Vous POUVEZ le faire en utilisant uniquement des formes xml - utilisez simplement une liste de calques ET un remplissage négatif comme ceci:

    <layer-list>

        <item>
            <shape>
                <solid android:color="#ffffff" />

                <padding android:top="20dp" />
            </shape>
        </item>

        <item>
            <shape>
                <gradient android:endColor="#ffffff" android:startColor="#efefef" android:type="linear" android:angle="90" />

                <padding android:top="-20dp" />
            </shape>
        </item>

    </layer-list>
Konmik
la source
1

Essayez cette méthode, vous pouvez faire tout ce que vous voulez.
C'est comme une pile, alors faites attention à quel élément vient en premier ou en dernier.

<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:right="50dp" android:start="10dp" android:left="10dp">
    <shape
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
        <corners android:radius="3dp" />
        <solid android:color="#012d08"/>
    </shape>
</item>
<item android:top="50dp">
    <shape android:shape="rectangle">
        <solid android:color="#7c4b4b" />
    </shape>
</item>
<item android:top="90dp" android:end="60dp">
    <shape android:shape="rectangle">
        <solid android:color="#e2cc2626" />
    </shape>
</item>
<item android:start="50dp" android:bottom="20dp" android:top="120dp">
    <shape android:shape="rectangle">
        <solid android:color="#360e0e" />
    </shape>
</item>

Khalid Ali
la source
0

Avez-vous essayé de superposer un dégradé avec une opacité presque transparente pour la surbrillance sur une autre image avec une opacité opaque pour le dégradé vert?

Greg D
la source
Non, je ne l'ai pas fait, mais je ne sais pas trop comment superposer un dégradé à un autre dans une forme. J'utilise la forme comme arrière-plan d'un LinearLayout en utilisant android: background = "@ drawable / myshape"
Andrew
Est-il possible de superposer deux formes avec des opacités différentes? (Je ne sais pas.)
Greg D