Kształty z wieloma gradientami

177

Chciałbym utworzyć kształt podobny do poniższego obrazu:

tekst alternatywny

Zwróć uwagę na górne pół gradienty od koloru 1 do koloru 2, ale jest dolna połowa, która przechodzi od koloru 3 do koloru 4. Wiem, jak utworzyć kształt za pomocą pojedynczego gradientu, ale nie jestem pewien, jak podzielić kształt na dwie połówki i ułóż 1 kształt z 2 różnymi gradientami.

Jakieś pomysły?

Andrzej
źródło

Odpowiedzi:

383

Wydaje mi się, że nie da się tego zrobić w XML (przynajmniej nie w Androidzie), ale znalazłem zamieszczone tutaj dobre rozwiązanie , które wygląda na to, że byłoby to bardzo pomocne!

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

Zasadniczo tablica int umożliwia wybranie wielu przystanków koloru, a poniższa tablica zmiennoprzecinkowa określa, gdzie te przystanki się znajdują (od 0 do 1). Możesz wtedy, jak wspomniano, po prostu użyć tego jako standardowego Drawable.

Edycja: Oto, jak możesz to wykorzystać w swoim scenariuszu. Powiedzmy, że masz Button zdefiniowany w XML w następujący sposób:

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

Następnie umieściłbyś coś takiego w swojej metodzie 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);

Nie mogę tego w tej chwili przetestować, to jest kod z mojej głowy, ale w zasadzie po prostu zastąp lub dodaj stopery dla potrzebnych kolorów. Zasadniczo, w moim przykładzie, zacząłbyś od jasnozielonego, przechodzącego do białego nieco przed środkiem (aby uzyskać blaknięcie zamiast ostrego przejścia), zanikającego od białego do średniego zielonego między 45% a 55%, a następnie zanikającego od średnio zielony do ciemnozielonego od 55% do końca. To może nie wyglądać dokładnie tak, jak twój kształt (w tej chwili nie mam możliwości przetestowania tych kolorów), ale możesz to zmodyfikować, aby powtórzyć swój przykład.

Edycja: Również 0, 0, 0, theButton.getHeight()odnosi się do współrzędnych x0, y0, x1, y1 gradientu. Zasadniczo zaczyna się od x = 0 (lewa strona), y = 0 (na górze) i rozciąga się do x = 0 (chcemy mieć pionowy gradient, więc kąt od lewej do prawej nie jest konieczny), y = wysokość przycisku. Tak więc gradient przebiega pod kątem 90 stopni od góry przycisku do dołu przycisku.

Edycja: OK, więc mam jeszcze jeden pomysł, który działa, haha. Obecnie działa w języku XML, ale powinno być możliwe również w przypadku kształtów w Javie. To trochę skomplikowane i wyobrażam sobie, że istnieje sposób na uproszczenie go do jednego kształtu, ale na razie mam to:

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>

W porządku, więc w zasadzie utworzyłem gradient kształtu w XML dla poziomego zielonego gradientu, ustawiony pod kątem 0 stopni, przechodząc od lewego zielonego koloru górnego obszaru do prawego zielonego koloru. Następnie wykonałem prostokąt kształtu z półprzezroczystą szarością. Jestem prawie pewien, że można to wstawić do XML listy warstw, aby uniknąć tego dodatkowego pliku, ale nie jestem pewien, jak. Ale w porządku, w takim razie w pliku XML lista_warstw pojawia się rodzaj hackerskiej części. Umieszczam zielony gradient jako dolną warstwę, a następnie połowę nakładam jako drugą warstwę, przesuniętą od góry o 50 dp. Oczywiście chciałbyś, aby ta liczba zawsze była o połowę mniejsza od rozmiaru twojego widoku, a nie stałe 50 dp. Nie sądzę jednak, aby można było używać procentów. Stamtąd właśnie wstawiłem TextView do mojego układu test.xml, używając pliku layer_list.xml jako mojego tła.

tekst alternatywny

Tada!

Jeszcze jedna edycja : zdałem sobie sprawę, że możesz po prostu osadzić kształty na liście warstw jako elementy, co oznacza, że ​​nie potrzebujesz już 3 oddzielnych plików XML! Możesz osiągnąć ten sam efekt łącząc je w następujący sposób:

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>

W ten sposób możesz ułożyć dowolną liczbę elementów! Mogę spróbować się pobawić i zobaczyć, czy uda mi się uzyskać bardziej wszechstronny wynik dzięki Javie.

Myślę, że to ostatnia edycja ... : OK, więc zdecydowanie możesz naprawić pozycjonowanie za pomocą Java, jak poniżej:

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

Jednak! Prowadzi to do kolejnego irytującego problemu polegającego na tym, że nie można zmierzyć TextView, dopóki nie zostanie narysowany. Nie jestem jeszcze do końca pewien, jak możesz to osiągnąć ... ale ręczne wstawienie numeru do topInset działa.

Skłamałem, jeszcze jedna edycja

OK, dowiedziałem się, jak ręcznie zaktualizować tę warstwę do rysowania, aby pasowała do wysokości kontenera, pełny opis można znaleźć tutaj . Ten kod powinien znaleźć się w Twojej metodzie 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);
            }
        });

I gotowe! Uff! :)

Kevin Coppock
źródło
Problem polega na tym, że gradient jest pionowy. Potrzebuję kształtu oddzielonego w pionie (tj .: linii poziomej), ale potrzebuję, aby gradient był poziomy (tj .: przesuń od lewej do prawej). W tym przykładzie zarówno separator, jak i gradient są pionowe.
Andrew,
W porządku, rozumiem, co teraz mówisz. (Obrazy są blokowane w pracy, więc mogłem oglądać tylko twoje na telefonie, trudno mi dobrze określić szczegóły). Niestety nie mam rozwiązania tego od ręki, chociaż przypuszczam, że można stworzyć kształt do rysowania z dwoma prostokątami, jeden z gradientem poziomym, drugi z gradientem pionowym od białego do czarnego o połowę mniejszym przy połowie krycia, wyrównane do dołu. Jeśli chodzi o to, jak to zrobić, w tej chwili nie mam żadnych konkretnych przykładów.
Kevin Coppock,
Tak, to właśnie miałem nadzieję, że będę w stanie to zrobić, ale jeszcze tego nie rozgryzłem. Doceniam twoją pomoc
Andrew,
Nie ma za co! Spróbowałem jeszcze raz i myślę, że mam prawie to, czego szukasz. Być może będziesz musiał zrobić to dynamicznie przez Javę (zgodnie z komentarzami), ale teraz to załatwia sprawę dla stałych rozmiarów.
Kevin Coppock
17
Takiej odpowiedzi chcesz dać przynajmniej +10. Uwielbiam walkę w Twojej odpowiedzi: Ty kontra Android API, kto wygra? Czy kiedykolwiek się dowiemy? ;) Ponadto jest to bardzo przydatny materiał do nauki, ponieważ zachowałeś wszystkie dziwactwa, które napotkałeś.
andr
28

Kształty gradientu można nakładać na warstwy w pliku XML za pomocą listy warstw. Wyobraź sobie przycisk ze stanem domyślnym jak poniżej, w którym drugi element jest półprzezroczysty. Dodaje coś w rodzaju winietowania. (Proszę wybaczyć niestandardowe kolory).

<!-- 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
źródło
4

MOŻESZ to zrobić używając tylko kształtów XML - po prostu użyj listy warstw ORAZ dopełnienia ujemnego w następujący sposób:

    <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
źródło
1

Wypróbuj tę metodę, a będziesz mógł zrobić wszystko, co chcesz.
Jest jak stos, więc uważaj, który przedmiot jest pierwszy lub ostatni.

<?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
źródło
0

Czy próbowałeś nałożyć jeden gradient z prawie przezroczystym kryciem dla podświetlenia na inny obraz z nieprzezroczystym kryciem dla zielonego gradientu?

Greg D.
źródło
Nie, nie mam, chociaż nie jestem pewien, jak ułożyć jeden gradient na innym w kształcie. Używam kształtu jako tła LinearLayout przy użyciu android: background = "@ drawable / myshape"
Andrew,
Czy możliwe jest nałożenie na siebie dwóch kształtów o różnym stopniu krycia? (Nie wiem.)
Greg D