Мультиградиентные формы

177

Я хотел бы создать форму, похожую на следующее изображение:

альтернативный текст

Обратите внимание на верхнюю половину градиентов от цвета 1 до цвета 2, но есть нижняя половина, градиенты от цвета 3 до цвета 4. Я знаю, как сделать форму с одним градиентом, но я не уверен, как разбить форму на две половинки и сделать 1 форму с 2 различными градиентами.

Любые идеи?

Андрей
источник

Ответы:

383

Я не думаю, что вы можете сделать это в XML (по крайней мере, в Android), но я нашел хорошее решение, опубликованное здесь, которое выглядит так, как будто это очень поможет!

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

По сути, массив int позволяет вам выбрать несколько цветовых остановок, а следующий массив с плавающей точкой определяет, где эти остановки расположены (от 0 до 1). Затем, как уже говорилось, вы можете просто использовать это как стандартный Drawable.

Изменить: Вот как вы могли бы использовать это в вашем сценарии. Допустим, у вас есть кнопка, определенная в XML следующим образом:

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

Затем вы бы поместили что-то вроде этого в свой метод 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);

Я не могу проверить это в данный момент, это код из моей головы, но в основном просто заменить или добавить остановки для цветов, которые вам нужны. В основном, в моем примере, вы начинаете со светло-зеленого, постепенно переходите в белый цвет перед центром (чтобы дать переход, а не резкий переход), переходите от белого к среднему зеленому между 45% и 55%, затем переходите от от среднего до темно-зеленого от 55% до конца. Возможно, это не совсем похоже на вашу форму (сейчас у меня нет возможности проверить эти цвета), но вы можете изменить это, чтобы повторить ваш пример.

Редактировать: Кроме того, 0, 0, 0, theButton.getHeight()ссылка относится к координатам градиента x0, y0, x1, y1. Таким образом, в основном, он начинается с x = 0 (левая сторона), y = 0 (верх) и простирается до x = 0 (мы хотим вертикальный градиент, поэтому не требуется угол слева направо), y = высота кнопки. Таким образом, градиент идет под углом 90 градусов от верхней части кнопки до нижней части кнопки.

Редактировать: Хорошо, у меня есть еще одна идея, которая работает, ха-ха. Прямо сейчас это работает в XML, но должно быть выполнимо и для фигур в Java. Это довольно сложно, и я думаю, что есть способ упростить его до единой формы, но вот что я получил сейчас:

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>

Итак, в основном я создал градиент формы в XML для горизонтального зеленого градиента, установленный под углом 0 градусов, переходящий от левого зеленого цвета верхней области к правому зеленому цвету. Затем я сделал прямоугольник с полупрозрачным серым цветом. Я почти уверен, что это может быть встроено в XML списка слоев, исключая этот дополнительный файл, но я не уверен, как это сделать. Но хорошо, тогда какая-то хакерская часть появляется в XML-файле layer_list. Я поставил зеленый градиент в качестве нижнего слоя, а затем наложил половину наложения в качестве второго слоя со смещением от вершины на 50dp. Очевидно, вы бы хотели, чтобы это число всегда составляло половину от размера вашего представления, а не фиксированного 50dp. Я не думаю, что вы можете использовать проценты, хотя. Оттуда я просто вставил TextView в свой макет test.xml, используя файл layer_list.xml в качестве фона.

альтернативный текст

Тада!

Еще одно редактирование : я понял, что вы можете просто вставить фигуры в список слоев, которые можно нарисовать как элементы, а это значит, что вам больше не нужно 3 отдельных файла XML! Вы можете достичь того же результата, комбинируя их примерно так:

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>

Вы можете наложить столько элементов, сколько хотите, таким образом! Я могу попытаться поиграть и посмотреть, смогу ли я получить более универсальный результат с помощью Java.

Я думаю, что это последнее редактирование ... : Хорошо, так что вы можете определенно исправить позиционирование через Java, как показано ниже:

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

Тем не мение! Это приводит к еще одной досадной проблеме, заключающейся в том, что вы не можете измерить TextView, пока он не будет нарисован. Я не совсем уверен, как вы можете сделать это ... но ручная вставка числа для topInset работает.

Я соврал, еще один править

Хорошо, узнал, как вручную обновить этот слой для рисования, чтобы он соответствовал высоте контейнера, полное описание можно найти здесь . Этот код должен идти в вашем методе 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);
            }
        });

И я сделал! Уф! :)

Кевин Коппок
источник
Проблема в том, что градиент вертикальный. Мне нужна форма, разделенная по вертикали (т.е. горизонтальная линия), но мне нужно, чтобы градиент был горизонтальным (то есть: двигаться слева направо). В этом примере и разделитель, и градиент расположены вертикально.
Андрей
О, хорошо, я понимаю, что вы говорите сейчас. (Изображения заблокированы на работе, поэтому я мог только просматривать ваши с моего телефона, трудно сказать подробности). К сожалению, у меня нет решения этой проблемы, хотя я бы предположил, что вы могли бы создать нарисованную фигуру с двумя прямоугольниками, один с горизонтальным градиентом, другой с вертикальным градиентом от белого к черному с половиной размера при половине непрозрачности, выровнены по дну. Что касается того, как это сделать, у меня нет конкретных примеров на данный момент.
Кевин Коппок
Да, это то, что я надеялся сделать, но я пока не понял этого. Я очень ценю вашу помощь
Эндрю
Пожалуйста! Я дал ему еще одну попытку, и я думаю, что в значительной степени получил то, что вы ищете. Возможно, вам придется делать это динамически через Java (согласно комментариям), но сейчас это помогает при фиксированных размерах.
Кевин Коппок
17
Такой ответ вы хотите дать +10, по крайней мере. Мне нравится чувство борьбы в твоем ответе: ты против Android API, кто победит? Узнаем ли мы когда-нибудь? ;) Плюс, это очень полезный учебный материал, так как вы сохранили все причуды, с которыми столкнулись.
Andr
28

Вы можете наложить градиентные фигуры в XML, используя список слоев. Представьте себе кнопку с состоянием по умолчанию, как показано ниже, где второй элемент является полупрозрачным. Это добавляет своего рода виньетирование. (Пожалуйста, извините за определенные пользователем цвета.)

<!-- 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
источник
4

Вы МОЖЕТЕ сделать это, используя только формы XML - просто используйте список слоев И отрицательный отступ, как это:

    <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
источник
1

Попробуйте этот метод, тогда вы можете делать все, что вы хотите.
Это похоже на стек, поэтому будьте осторожны, какой элемент идет первым или последним.

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

Вы пытались наложить один градиент с почти прозрачной непрозрачностью для выделения поверх другого изображения с непрозрачной непрозрачностью для зеленого градиента?

Грег Д
источник
Нет, не знаю, хотя я не совсем уверен, как наложить один градиент на другой в форме. Я использую форму в качестве фона LinearLayout, используя android: background = "@ drawable / myshape"
Эндрю
Можно ли наложить две фигуры с разной непрозрачностью друг на друга? (Я не знаю.)
Грег Д