Formas multi-gradiente

177

Gostaria de criar uma forma semelhante à seguinte imagem:

texto alternativo

Observe a metade superior dos gradientes da cor 1 à cor 2, mas há uma metade inferior dos gradientes da cor 3 à cor 4. Eu sei como fazer uma forma com um único gradiente, mas não sei como dividir uma forma em duas metades e faça 1 forma com 2 gradientes diferentes.

Alguma ideia?

Andrew
fonte

Respostas:

383

Não acho que você possa fazer isso em XML (pelo menos não no Android), mas encontrei uma boa solução postada aqui que parece ser uma grande ajuda!

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

Basicamente, a matriz int permite selecionar várias paradas de cores, e a seguinte matriz flutuante define onde essas paradas são posicionadas (de 0 a 1). Você pode, como indicado, usar isso como um Drawable padrão.

Editar: veja como você pode usar isso no seu cenário. Digamos que você tenha um botão definido em XML da seguinte forma:

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

Você colocaria algo assim no 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);

Não posso testar isso no momento, esse é o código da minha cabeça, mas basicamente basta substituir ou adicionar paradas para as cores que você precisa. Basicamente, no meu exemplo, você começaria com um verde claro, desbotaria levemente para o branco antes do centro (para dar uma desbotamento, em vez de uma transição severa), desbotaria do branco para o verde médio entre 45% e 55% e depois desbotaria verde médio a verde escuro de 55% até o final. Isso pode não parecer exatamente com sua forma (no momento, não tenho como testar essas cores), mas você pode modificá-lo para replicar seu exemplo.

Editar: Além disso, 0, 0, 0, theButton.getHeight()refere-se às coordenadas x0, y0, x1, y1 do gradiente. Então, basicamente, começa em x = 0 (lado esquerdo), y = 0 (em cima) e se estende até x = 0 (estamos querendo um gradiente vertical, portanto, nenhum ângulo da esquerda para a direita é necessário), y = a altura do botão Portanto, o gradiente vai em um ângulo de 90 graus, da parte superior do botão até a parte inferior do botão.

Edit: Ok, então eu tenho mais uma idéia que funciona, haha. No momento, ele funciona em XML, mas também deve ser possível para formas em Java. É meio complexo, e imagino que há uma maneira de simplificá-lo em uma única forma, mas é isso que tenho por enquanto:

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>

Ok, basicamente criei um gradiente de forma em XML para o gradiente horizontal verde, definido em um ângulo de 0 grau, passando da cor verde esquerda da área superior para a cor verde direita. Em seguida, fiz um retângulo de forma com um meio cinza transparente. Tenho certeza de que isso pode estar embutido no XML da lista de camadas, evitando esse arquivo extra, mas não sei como. Mas tudo bem, então o tipo de parte hacky entra no arquivo XML layer_list. Coloquei o gradiente verde como camada inferior e depois coloque a meia sobreposição como segunda camada, deslocada do topo em 50dp. Obviamente, você deseja que esse número seja sempre metade do tamanho da visualização, e não 50dp fixo. Eu acho que você não pode usar porcentagens. A partir daí, eu apenas inseri um TextView no meu layout test.xml, usando o arquivo layer_list.xml como plano de fundo.

texto alternativo

Tada!

Mais uma edição : eu percebi que você pode incorporar as formas na lista de camadas que podem ser desenhadas como itens, o que significa que você não precisa mais de 3 arquivos XML separados! Você pode obter o mesmo resultado combinando-os da seguinte forma:

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>

Você pode colocar em camadas quantos itens quiser dessa maneira! Eu posso tentar brincar e ver se consigo um resultado mais versátil através do Java.

Acho que esta é a última edição ... : Ok, então você pode definitivamente corrigir o posicionamento através de Java, como o seguinte:

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

Contudo! Isso leva a outro problema irritante, pois você não pode medir o TextView até que ele tenha sido desenhado. Ainda não tenho certeza de como você pode fazer isso ... mas inserir manualmente um número para topInset funciona.

Eu menti, mais uma edição

Ok, descobri como atualizar manualmente essa camada desenhável para corresponder à altura do contêiner, descrição completa pode ser encontrada aqui . Este código deve ir no seu 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);
            }
        });

E eu terminei! Uau! :)

Kevin Coppock
fonte
O problema é que o gradiente é vertical. Preciso que a forma seja separada verticalmente (ou seja: uma linha horizontal), mas preciso que o gradiente seja horizontal (ou seja: mova da esquerda para a direita). Neste exemplo, o separador e o gradiente são verticais.
Andrew
Oh ok, entendi o que você está dizendo agora. (As imagens estão bloqueadas no trabalho, então eu só conseguia ver as suas no meu telefone, dificilmente para saber detalhes) Infelizmente, não tenho uma solução imediata, embora eu suponha que você possa criar uma forma desenhável com dois retângulos, um com gradiente horizontal, outro com gradiente vertical de branco a preto com metade do tamanho e meia opacidade, alinhado ao fundo. Quanto a como fazer isso, não tenho exemplos específicos no momento.
precisa
Sim, era o que eu esperava poder fazer, mas ainda não descobri. Agradeço a sua ajuda.
Andrew
De nada! Eu tentei mais uma vez e acho que tenho o que você está procurando. Talvez você precise fazer isso dinamicamente através do Java (conforme os comentários), mas isso faz o truque para tamanhos fixos no momento.
precisa
17
Esse é o tipo de resposta que você deseja dar +10 pelo menos. Adoro o sentimento de luta na sua resposta: você vs API do Android, quem vencerá? Algum dia saberemos? ;) Além disso, é um material de aprendizado super útil, pois você manteve todas as peculiaridades que encontrou.
andr
28

Você pode estratificar formas de gradiente no xml usando uma lista de camadas. Imagine um botão com o estado padrão como abaixo, onde o segundo item é semitransparente. Ele adiciona uma espécie de vinheta. (Por favor, desculpe as cores definidas automaticamente.)

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

Você pode fazê-lo usando apenas formas xml - basta usar a lista de camadas E o preenchimento negativo como este:

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

Experimente esse método e faça o que quiser.
É como uma pilha, portanto, tenha cuidado com o item que vem primeiro ou por ú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
fonte
0

Você tentou sobrepor um gradiente com uma opacidade quase transparente para o destaque em cima de outra imagem com uma opacidade opaca para o gradiente verde?

Greg D
fonte
Não, não tenho, embora não tenha muita certeza de como colocar um gradiente sobre outro em uma forma. Eu estou usando a forma como pano de fundo de um LinearLayout usando android: background = "@ drawable / myShape"
Andrew
É possível estratificar duas formas com diferentes opacidades umas sobre as outras? (Eu não sei.)
Greg D