Bentuk multi-gradien

177

Saya ingin membuat bentuk yang seperti gambar berikut:

teks alternatif

Perhatikan gradien setengah atas dari warna 1 ke warna 2, tetapi ada setengah bagian bawah gradien dari warna 3 ke warna 4. Saya tahu cara membuat bentuk dengan gradien tunggal, tapi saya tidak yakin bagaimana membagi bentuk menjadi dua bagian dan membuat 1 bentuk dengan 2 gradien berbeda.

Ada ide?

Andrew
sumber

Jawaban:

383

Saya tidak berpikir Anda bisa melakukan ini dalam XML (setidaknya tidak di Android), tetapi saya telah menemukan solusi bagus yang diposting di sini yang sepertinya akan sangat membantu!

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

Pada dasarnya, array int memungkinkan Anda untuk memilih beberapa pemberhentian warna, dan array float berikut menentukan posisi pemberhentian tersebut (dari 0 hingga 1). Anda kemudian dapat, seperti yang dinyatakan, cukup gunakan ini sebagai Drawable standar.

Sunting: Inilah cara Anda dapat menggunakan ini dalam skenario Anda. Katakanlah Anda memiliki Tombol yang didefinisikan dalam XML seperti:

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

Anda kemudian memasukkan sesuatu seperti ini di metode onCreate () Anda:

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

Saya tidak dapat menguji ini saat ini, ini adalah kode dari kepala saya, tetapi pada dasarnya hanya mengganti, atau menambahkan berhenti untuk warna yang Anda butuhkan. Pada dasarnya, dalam contoh saya, Anda akan mulai dengan hijau muda, memudar menjadi putih sedikit sebelum pusat (untuk memberikan memudar, daripada transisi yang keras), memudar dari putih ke hijau tengah antara 45% dan 55%, kemudian memudar dari hijau tengah ke hijau gelap dari 55% hingga akhir. Ini mungkin tidak persis seperti bentuk Anda (Saat ini, saya tidak memiliki cara untuk menguji warna-warna ini), tetapi Anda dapat memodifikasi ini untuk meniru contoh Anda.

Sunting: Juga, 0, 0, 0, theButton.getHeight()merujuk pada koordinat gradien x0, y0, x1, y1. Jadi pada dasarnya, itu dimulai pada x = 0 (sisi kiri), y = 0 (atas), dan membentang ke x = 0 (kami menginginkan gradien vertikal, jadi tidak perlu sudut kiri ke kanan), y = tinggi dari tombol. Jadi gradien berjalan pada sudut 90 derajat dari bagian atas tombol ke bagian bawah tombol.

Sunting: Oke, jadi saya punya satu ide lagi yang berfungsi, haha. Saat ini berfungsi dalam XML, tetapi harus bisa dilakukan untuk bentuk di Jawa juga. Agak kompleks, dan saya membayangkan ada cara untuk menyederhanakannya menjadi satu bentuk, tapi inilah yang saya punya untuk saat ini:

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>

Oke, jadi pada dasarnya saya telah membuat gradien bentuk dalam XML untuk gradien hijau horizontal, ditetapkan pada sudut 0 derajat, pergi dari warna hijau kiri area atas, ke warna hijau kanan. Selanjutnya, saya membuat bentuk persegi panjang dengan abu-abu setengah transparan. Saya cukup yakin itu bisa dimasukkan ke dalam daftar lapisan XML, menghindarkan file tambahan ini, tapi saya tidak yakin bagaimana caranya. Tapi oke, maka jenis bagian hacky masuk pada file XML layer_list. Saya menempatkan gradien hijau sebagai lapisan bawah, lalu meletakkan setengah overlay sebagai lapisan kedua, diimbangi dari atas sebesar 50dp. Tentunya Anda ingin angka ini selalu setengah dari ukuran tampilan Anda, dan bukan 50dp tetap. Saya pikir Anda tidak dapat menggunakan persentase. Dari sana, saya baru saja memasukkan TextView ke tata letak test.xml saya, menggunakan file layer_list.xml sebagai latar belakang saya.

teks alternatif

Tada!

Satu lagi edit : Saya menyadari Anda hanya bisa menanamkan bentuk ke dalam daftar layer yang dapat digambar sebagai item, artinya Anda tidak perlu 3 file XML terpisah lagi! Anda dapat mencapai hasil yang sama dengan menggabungkannya seperti:

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>

Anda dapat melapisi item sebanyak yang Anda suka dengan cara ini! Saya dapat mencoba untuk bermain-main dan melihat apakah saya bisa mendapatkan hasil yang lebih fleksibel melalui Java.

Saya pikir ini adalah suntingan terakhir ... : Oke, jadi Anda pasti dapat memperbaiki pemosisian melalui Java, seperti berikut:

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

Namun! Ini mengarah ke masalah menjengkelkan lain di mana Anda tidak bisa mengukur TextView sampai setelah ditarik. Saya belum yakin bagaimana Anda bisa melakukan ini ... tetapi secara manual memasukkan nomor untuk topInset berfungsi.

Aku berbohong, satu lagi edit

Oke, temukan cara memperbarui lapisan ini secara manual agar sesuai dengan ketinggian wadah, uraian lengkap dapat ditemukan di sini . Kode ini harus masuk dalam metode onCreate () Anda:

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

Dan saya sudah selesai! Wah! :)

Kevin Coppock
sumber
Masalahnya adalah gradiennya vertikal. Saya membutuhkan bentuk yang dipisahkan secara vertikal (yaitu: garis horizontal), tetapi saya membutuhkan gradien menjadi horizontal (yaitu: bergerak ke kiri ke kanan). Dengan contoh ini, pemisah dan gradien adalah vertikal.
Andrew
Oh oke, saya mengerti apa yang Anda katakan sekarang. (Gambar diblokir di tempat kerja, jadi saya hanya bisa melihat milik Anda dari ponsel saya, sulit untuk menjelaskan dengan baik). Sayangnya saya tidak punya solusi untuk itu begitu saja, walaupun saya akan menganggap Anda dapat membuat bentuk yang dapat digambar dengan dua persegi panjang, satu dengan gradien horizontal, satu dengan gradien vertikal putih ke hitam di setengah ukuran setengah opacity, selaras ke bawah. Sejauh bagaimana melakukan itu, saya tidak memiliki contoh spesifik saat ini.
Kevin Coppock
Ya, itulah yang saya harapkan dapat saya lakukan, tetapi saya belum menemukan jawabannya. Saya menghargai bantuan Anda
Andrew
Sama-sama! Saya sudah mencobanya sekali lagi, dan saya pikir saya sudah mendapatkan apa yang Anda cari. Anda mungkin harus membuatnya secara dinamis melalui Java (sesuai komentar) tetapi ini melakukan trik untuk ukuran tetap sekarang.
Kevin Coppock
17
Itulah jenis jawaban yang paling tidak ingin Anda beri +10. Saya suka perasaan bertarung dalam jawaban Anda: Anda vs Android API, siapa yang akan menang? Akankah kita tahu? ;) Plus, ini materi pembelajaran yang sangat berguna karena Anda mempertahankan semua keanehan yang Anda temui.
andr
28

Anda bisa melapisi bentuk gradien dalam xml menggunakan lapisan-daftar. Bayangkan sebuah tombol dengan status default seperti di bawah ini, di mana item kedua semi-transparan. Ini menambahkan semacam vignetting. (Maafkan warna yang ditentukan khusus.)

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

Anda BISA melakukannya hanya menggunakan bentuk xml - cukup gunakan lapisan-daftar DAN lapisan negatif seperti ini:

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

Cobalah metode ini maka Anda dapat melakukan semua hal yang Anda inginkan.
Ini seperti tumpukan jadi hati-hati item mana yang lebih dulu atau terakhir.

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

Sudahkah Anda mencoba overlay satu gradien dengan opacity hampir transparan untuk highlight di atas gambar lain dengan opacity opacity untuk gradien hijau?

Greg D
sumber
Tidak, saya belum, meskipun saya tidak yakin bagaimana cara meletakkan satu gradien di atas yang lain dalam bentuk. Saya menggunakan bentuk sebagai latar belakang LinearLayout menggunakan android: background = "@ drawable / myshape"
Andrew
Apakah mungkin untuk melapisi dua bentuk dengan kekeruhan yang berbeda di atas satu sama lain? (Saya tidak tahu.)
Greg D