Forme multi-gradiente


177

Vorrei creare una forma simile alla seguente immagine:

testo alternativo

Nota le mezze pendenze superiori dal colore 1 al colore 2, ma c'è una metà inferiore che fa sfumature dal colore 3 al colore 4. So come creare una forma con un singolo gradiente, ma non sono sicuro di come dividere una forma in due metà e crea 1 forma con 2 gradienti diversi.

Qualche idea?


Risposte:


383

Non penso che tu possa farlo in XML (almeno non in Android), ma ho trovato una buona soluzione pubblicata qui che sembra essere di grande aiuto!

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

Fondamentalmente, l'array int consente di selezionare più interruzioni di colore e il seguente array float definisce la posizione di tali interruzioni (da 0 a 1). Quindi, come detto, puoi semplicemente usarlo come un Drawable standard.

Modifica: ecco come potresti usarlo nel tuo scenario. Diciamo che hai un pulsante definito in XML in questo modo:

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

Quindi inseriresti qualcosa del genere nel tuo metodo 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);

Non posso provarlo al momento, questo è il codice dalla mia testa, ma sostanzialmente basta sostituire o aggiungere stop per i colori di cui hai bisogno. Fondamentalmente, nel mio esempio, inizieresti con un verde chiaro, sfumerai in bianco leggermente prima del centro (per dare una dissolvenza, piuttosto che una dura transizione), sfumerai dal bianco al verde medio tra il 45% e il 55%, quindi sfumerai da da metà verde a verde scuro dal 55% alla fine. Potrebbe non apparire esattamente come la tua forma (in questo momento, non ho modo di testare questi colori), ma puoi modificarlo per replicare il tuo esempio.

Modifica: inoltre, si 0, 0, 0, theButton.getHeight()riferisce alle coordinate x0, y0, x1, y1 del gradiente. Quindi sostanzialmente, inizia con x = 0 (lato sinistro), y = 0 (in alto) e si estende fino a x = 0 (vogliamo un gradiente verticale, quindi non è necessario un angolo da sinistra a destra), y = l'altezza del pulsante. Quindi il gradiente va ad un angolo di 90 gradi dalla parte superiore del pulsante alla parte inferiore del pulsante.

Modifica: Okay, quindi ho un'altra idea che funziona, ahah. In questo momento funziona in XML, ma dovrebbe essere fattibile anche per le forme in Java. È un po 'complesso, e immagino che ci sia un modo per semplificarlo in un'unica forma, ma questo è quello che ho per ora:

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>

Va bene, quindi in pratica ho creato un gradiente di forma in XML per il gradiente verde orizzontale, impostato con un angolo di 0 gradi, passando dal colore verde sinistro dell'area superiore, al colore verde destro. Successivamente, ho creato un rettangolo di forma con un mezzo grigio trasparente. Sono abbastanza sicuro che potrebbe essere incorporato nell'XML dell'elenco dei livelli, ovviando a questo file aggiuntivo, ma non sono sicuro di come. Ma va bene, allora il tipo di parte caotica arriva nel file XML layer_list. Metto il gradiente verde come livello inferiore, quindi metto la mezza sovrapposizione come secondo livello, sfalsando dall'alto di 50dp. Ovviamente vorresti che questo numero fosse sempre la metà di qualunque sia la dimensione della tua vista, e non un fisso di 50dp. Non penso che tu possa usare le percentuali, comunque. Da lì, ho appena inserito un TextView nel mio layout test.xml, usando il file layer_list.xml come sfondo.

testo alternativo

Tada!

Ancora una modifica : ho capito che puoi semplicemente incorporare le forme nell'elenco dei livelli come elementi disegnabili, il che significa che non hai più bisogno di 3 file XML separati! Puoi ottenere lo stesso risultato combinandoli così:

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>

Puoi sovrapporre tutti gli elementi che vuoi in questo modo! Potrei provare a giocare e vedere se riesco a ottenere un risultato più versatile tramite Java.

Penso che questa sia l'ultima modifica ... : Va bene, quindi puoi sicuramente correggere il posizionamento tramite Java, come il seguente:

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

Però! Ciò porta a un altro fastidioso problema in quanto non è possibile misurare TextView fino a quando non è stato disegnato. Non sono ancora sicuro di come puoi farlo ... ma l'inserimento manuale di un numero per topInset funziona.

Ho mentito, un'altra modifica

Bene, ho scoperto come aggiornare manualmente questo layer disegnabile per adattarlo all'altezza del contenitore, la descrizione completa è disponibile qui . Questo codice dovrebbe andare nel tuo metodo 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);
            }
        });

E ho finito! Meno male! :)


Il problema è che il gradiente è verticale. Ho bisogno che la forma sia separata verticalmente (cioè: una linea orizzontale), ma ho bisogno che il gradiente sia orizzontale (cioè: muovi da sinistra a destra). Con questo esempio, sia il separatore che il gradiente sono verticali.
Andrew,

Oh okay, capisco quello che stai dicendo ora. (Le immagini sono bloccate al lavoro, quindi ho potuto vedere le tue solo dal mio telefono, difficile da dire bene i dettagli). Sfortunatamente non ho una soluzione a questo, ma suppongo che potresti creare una forma disegnabile con due rettangoli, uno con un gradiente orizzontale, uno con un gradiente verticale da bianco a nero a metà delle dimensioni a metà opacità, allineato al fondo. Per quanto riguarda come fare, non ho esempi specifici al momento.
Kevin Coppock,

Sì, è quello che speravo di poter fare, ma non l'ho ancora capito. Apprezzo molto il tuo aiuto
Andrew,

Prego! Ho provato ancora una volta e penso di avere praticamente quello che stai cercando. Potrebbe essere necessario eseguirli dinamicamente tramite Java (come da commenti), ma questo è il trucco per dimensioni fisse in questo momento.
Kevin Coppock,

17
Questo è il tipo di risposta che vuoi dare almeno a +10. Adoro la sensazione di lotta nella tua risposta: tu vs Android API, chi vincerai? Sapremo mai? ;) Inoltre, è un materiale di apprendimento super utile poiché hai conservato tutte le stranezze che hai incontrato.
Andr

28

È possibile sovrapporre forme sfumate nell'xml utilizzando un elenco di livelli. Immagina un pulsante con lo stato predefinito come di seguito, in cui il secondo elemento è semi-trasparente. Aggiunge una sorta di vignettatura. (Scusate i colori personalizzati).

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


4

Puoi farlo usando solo forme xml - usa solo la lista dei livelli E il riempimento negativo in questo modo:

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

1

Prova questo metodo quindi puoi fare tutto quello che vuoi.
È come una pila, quindi fai attenzione a quale oggetto arriva per primo o per ultimo.

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


0

Hai provato a sovrapporre una sfumatura con un'opacità quasi trasparente per l'evidenziazione sopra un'altra immagine con un'opacità opaca per la sfumatura verde?


No non l'ho fatto, anche se non sono del tutto sicuro di come posizionare una sfumatura su un'altra in una forma. Sto usando la forma come sfondo di un LinearLayout usando Android: background = "@ drawable / myshape"
Andrew

È possibile sovrapporre due forme con opacità diverse l'una sull'altra? (Non lo so.)
Greg D,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.