Formas de gradiente múltiple

177

Me gustaría crear una forma similar a la siguiente imagen:

texto alternativo

Observe los gradientes de la mitad superior del color 1 al color 2, pero hay una mitad inferior que se gradúa del color 3 al color 4. Sé cómo hacer una forma con un solo degradado, pero no estoy seguro de cómo dividir una forma en dos mitades y forma 1 forma con 2 gradientes diferentes.

¿Algunas ideas?

Andrés
fuente

Respuestas:

383

No creo que pueda hacer esto en XML (al menos no en Android), pero he encontrado una buena solución publicada aquí que parece que sería de gran ayuda.

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

Básicamente, la matriz int le permite seleccionar múltiples paradas de color, y la siguiente matriz flotante define dónde se ubican esas paradas (de 0 a 1). Luego, como se indicó, puede usar esto como un Drawable estándar.

Editar: así es como podría usar esto en su escenario. Digamos que tiene un botón definido en XML así:

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

Luego pondrías algo como esto en tu método 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);

No puedo probar esto en este momento, este es el código de mi cabeza, pero básicamente solo reemplazo o agrego paradas para los colores que necesita. Básicamente, en mi ejemplo, comenzaría con un verde claro, se desvanecería a blanco ligeramente antes del centro (para dar una transición más desvanecida, en lugar de una dura), se desvanecería de blanco a verde medio entre 45% y 55%, luego se desvanecería de verde medio a verde oscuro desde 55% hasta el final. Es posible que esto no se vea exactamente como su forma (en este momento, no tengo forma de probar estos colores), pero puede modificar esto para replicar su ejemplo.

Editar: también, se 0, 0, 0, theButton.getHeight()refiere a las coordenadas x0, y0, x1, y1 del gradiente. Básicamente, comienza en x = 0 (lado izquierdo), y = 0 (arriba) y se extiende hasta x = 0 (queremos un gradiente vertical, por lo que no es necesario un ángulo de izquierda a derecha), y = la altura del botón Entonces el gradiente va en un ángulo de 90 grados desde la parte superior del botón hasta la parte inferior del botón.

Editar: Bueno, tengo una idea más que funciona, jaja. En este momento funciona en XML, pero también debería ser factible para formas en Java. Es un poco complejo, e imagino que hay una manera de simplificarlo en una sola forma, pero esto es lo que tengo por ahora:

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>

Bien, entonces básicamente he creado un degradado de forma en XML para el degradado verde horizontal, establecido en un ángulo de 0 grados, que va desde el color verde izquierdo del área superior hasta el color verde derecho. A continuación, hice un rectángulo de forma con un gris medio transparente. Estoy bastante seguro de que podría incluirse en el XML de la lista de capas, obviando este archivo adicional, pero no estoy seguro de cómo. Pero bueno, entonces el tipo de parte hacky viene en el archivo XML de layer_list Puse el degradado verde como la capa inferior, luego puse la mitad superpuesta como la segunda capa, compensada desde la parte superior por 50dp. Obviamente, querrás que este número sea siempre la mitad del tamaño de tu vista, y no un 50dp fijo. Sin embargo, no creo que puedas usar porcentajes. A partir de ahí, acabo de insertar un TextView en mi diseño test.xml, usando el archivo layer_list.xml como fondo.

texto alternativo

Tada!

Una edición más : me he dado cuenta de que puede incrustar las formas en la lista de capas dibujables como elementos, lo que significa que ya no necesita 3 archivos XML separados. Puedes lograr el mismo resultado combinándolos así:

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>

¡Puedes colocar tantos elementos como quieras de esta manera! Puedo intentar jugar y ver si puedo obtener un resultado más versátil a través de Java.

Creo que esta es la última edición ... : Ok, entonces definitivamente puedes arreglar el posicionamiento a través de Java, como el siguiente:

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

¡Sin embargo! Esto lleva a otro problema molesto, ya que no puede medir TextView hasta después de que se haya dibujado. Todavía no estoy seguro de cómo puede lograr esto ... pero insertar manualmente un número para topInset funciona.

Mentí, una edición más

Bien, descubrí cómo actualizar manualmente esta capa dibujable para que coincida con la altura del contenedor, la descripción completa se puede encontrar aquí . Este código debe ir en su método 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);
            }
        });

Y ya he terminado! ¡Uf! :)

Kevin Coppock
fuente
El problema con esto es que el gradiente es vertical. Necesito que la forma se separe verticalmente (es decir, una línea horizontal), pero necesito que el gradiente sea horizontal (es decir, mover de izquierda a derecha). Con este ejemplo, tanto el separador como el gradiente son verticales.
Andrew
Oh bien, entiendo lo que estás diciendo ahora. (Las imágenes están bloqueadas en el trabajo, por lo que solo pude ver las suyas desde mi teléfono, es difícil decir bien los detalles). Desafortunadamente, no tengo una solución para eso, aunque supongo que podrías crear una forma dibujable con dos rectángulos, uno con un gradiente horizontal, uno con un gradiente vertical de blanco a negro a la mitad del tamaño a la mitad de la opacidad, alineado a la parte inferior. En cuanto a cómo hacerlo, no tengo ningún ejemplo específico en este momento.
Kevin Coppock
Sí, eso era lo que esperaba poder hacer, pero todavía no lo he descubierto. Aprecio tu ayuda
Andrew
¡De nada! Lo he intentado una vez más, y creo que prácticamente tengo lo que estás buscando. Puede que tenga que hacer esto dinámicamente a través de Java (según los comentarios), pero esto hace el truco para los tamaños fijos en este momento.
Kevin Coppock
17
Ese es el tipo de respuesta que quieres dar +10 al menos. Me encanta la sensación de lucha en tu respuesta: tú vs Android API, ¿quién ganará? ¿Alguna vez lo sabremos? ;) Además, es un material de aprendizaje súper útil ya que retuviste todas las peculiaridades que has encontrado.
andr
28

Puede superponer formas de degradado en el xml utilizando una lista de capas. Imagine un botón con el estado predeterminado como se muestra a continuación, donde el segundo elemento es semitransparente. Agrega una especie de viñeteado. (Por favor, disculpe los colores personalizados).

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

PUEDE hacerlo usando solo formas xml, solo use la lista de capas Y el relleno negativo de esta manera:

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

Pruebe este método y luego puede hacer todo lo que quiera.
Es como una pila, así que tenga cuidado de qué elemento viene primero o último.

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

¿Has intentado superponer un degradado con una opacidad casi transparente para el resaltado en la parte superior de otra imagen con una opacidad opaca para el degradado verde?

Greg D
fuente
No, no lo he hecho, aunque no estoy muy seguro de cómo colocar un degradado sobre otro en una forma. Estoy usando la forma como fondo de un LinearLayout usando android: background = "@ drawable / myshape"
Andrew
¿Es posible superponer dos formas con diferentes opacidades una encima de la otra? (No lo sé)
Greg D