Bagian terpenting dari tampilan kustom adalah penampilannya. Gambar kustom bisa mudah atau kompleks sesuai dengan kebutuhan aplikasi Anda. Dokumen ini mencakup beberapa operasi yang paling umum.
Untuk informasi selengkapnya, lihat Ringkasan drawable.
Mengganti onDraw()
Langkah terpenting dalam menggambar tampilan kustom adalah mengganti
onDraw()
. Parameter untuk onDraw()
adalah
Canvas
yang bisa digunakan tampilan untuk menggambar dirinya sendiri. Class Canvas
mendefinisikan metode untuk menggambar teks, garis, bitmap, dan banyak grafik lainnya
primitif. Anda dapat menggunakan metode ini di onDraw()
untuk membuat
antarmuka pengguna (UI) kustom.
Mulai dengan membuat
Objek Paint
.
Bagian selanjutnya dalam artikel ini membahas Paint
lebih mendalam.
Membuat objek gambar
Tujuan
android.graphics
membagi gambar menjadi dua area:
- Apa yang akan digambar, ditangani oleh
Canvas
. - Bagaimana menggambarnya, ditangani oleh
Paint
.
Misalnya, Canvas
menyediakan metode untuk menggambar garis, dan
Paint
menyediakan metode untuk menentukan warna garis tersebut.
Canvas
memiliki metode untuk menggambar persegi panjang, dan Paint
mendefinisikan apakah akan mengisi persegi panjang itu dengan warna atau membiarkannya kosong.
Canvas
menentukan bentuk yang dapat Anda gambar di layar, dan
Paint
menentukan warna, gaya, font, dan sebagainya dari setiap bentuk
Anda menggambar.
Sebelum menggambar apa pun, buat satu atau beberapa objek Paint
. Tujuan
contoh berikut melakukannya dalam metode yang disebut init
. Metode ini adalah
dipanggil dari konstruktor dari Java, tetapi dapat diinisialisasi secara inline di
Kotlin.
Kotlin
@ColorInt private var textColor // Obtained from style attributes. @Dimension private var textHeight // Obtained from style attributes. private val textPaint = Paint(ANTI_ALIAS_FLAG).apply { color = textColor if (textHeight == 0f) { textHeight = textSize } else { textSize = textHeight } } private val piePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL textSize = textHeight } private val shadowPaint = Paint(0).apply { color = 0x101010 maskFilter = BlurMaskFilter(8f, BlurMaskFilter.Blur.NORMAL) }
Java
private Paint textPaint; private Paint piePaint; private Paint shadowPaint; @ColorInt private int textColor; // Obtained from style attributes. @Dimension private float textHeight; // Obtained from style attributes. private void init() { textPaint = new Paint(Paint.ANTI_ALIAS_FLAG); textPaint.setColor(textColor); if (textHeight == 0) { textHeight = textPaint.getTextSize(); } else { textPaint.setTextSize(textHeight); } piePaint = new Paint(Paint.ANTI_ALIAS_FLAG); piePaint.setStyle(Paint.Style.FILL); piePaint.setTextSize(textHeight); shadowPaint = new Paint(0); shadowPaint.setColor(0xff101010); shadowPaint.setMaskFilter(new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL)); ... }
Membuat objek di awal merupakan langkah pengoptimalan yang penting. Penayangan adalah
sering digambar ulang, dan banyak objek gambar memerlukan inisialisasi yang mahal.
Membuat objek gambar dalam metode onDraw()
Anda secara signifikan
akan menurunkan performa dan dapat
membuat UI menjadi lambat.
Menangani peristiwa tata letak
Untuk menggambar tampilan kustom dengan benar, cari tahu ukurannya. Kustom kompleks sering kali perlu melakukan beberapa penghitungan tata letak tergantung pada ukurannya dan bentuk area mereka di layar. Jangan pernah membuat asumsi tentang ukuran pada layar. Meskipun hanya ada satu aplikasi yang menggunakan tampilan Anda, aplikasi tersebut harus menangani berbagai ukuran layar, beberapa kepadatan layar, dan berbagai aspek rasio dalam mode potret dan lanskap.
Meskipun View
memiliki banyak metode untuk menangani pengukuran, sebagian besar dari mereka tidak perlu
diganti. Jika tampilan Anda tidak memerlukan kontrol khusus atas ukurannya, hanya
mengganti satu metode:
onSizeChanged()
.
onSizeChanged()
dipanggil saat tampilan Anda pertama kali diberi
dan sekali lagi jika ukuran tampilan berubah karena alasan apa pun. Hitung
posisi, dimensi, dan nilai lain yang terkait dengan ukuran tampilan Anda di
onSizeChanged()
, bukan menghitungnya ulang setiap kali Anda menggambar.
Pada contoh berikut, onSizeChanged()
adalah tempat tampilan
menghitung persegi panjang pembatas diagram dan posisi relatif
label teks dan elemen visual lainnya.
Saat tampilan Anda diberi ukuran, pengelola tata letak akan mengasumsikan bahwa ukuran tersebut
menyertakan padding tampilan. Menangani nilai padding ketika Anda menghitung
ukuran tampilan. Berikut ini cuplikan dari onSizeChanged()
yang menunjukkan bagaimana
untuk melakukannya:
Kotlin
private val showText // Obtained from styled attributes. private val textWidth // Obtained from styled attributes. override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) // Account for padding. var xpad = (paddingLeft + paddingRight).toFloat() val ypad = (paddingTop + paddingBottom).toFloat() // Account for the label. if (showText) xpad += textWidth.toFloat() val ww = w.toFloat() - xpad val hh = h.toFloat() - ypad // Figure out how big you can make the pie. val diameter = Math.min(ww, hh) }
Java
private Boolean showText; // Obtained from styled attributes. private int textWidth; // Obtained from styled attributes. @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // Account for padding. float xpad = (float)(getPaddingLeft() + getPaddingRight()); float ypad = (float)(getPaddingTop() + getPaddingBottom()); // Account for the label. if (showText) xpad += textWidth; float ww = (float)w - xpad; float hh = (float)h - ypad; // Figure out how big you can make the pie. float diameter = Math.min(ww, hh); }
Jika Anda memerlukan kontrol yang lebih baik atas parameter tata letak tampilan, terapkan
onMeasure()
.
Parameter metode ini
View.MeasureSpec
nilai yang memberi tahu Anda seberapa besar tampilan yang
diinginkan orang tua untuk Anda dan
apakah ukuran itu merupakan batas maksimum
atau sekadar saran. Sebagai pengoptimalan,
nilai-nilai ini disimpan sebagai bilangan bulat yang dikemas, dan Anda menggunakan metode statis
View.MeasureSpec
untuk mengekstrak informasi yang disimpan di setiap bilangan bulat.
Berikut ini contoh implementasi onMeasure()
. Di sini
implementasi, model ini mencoba membuat areanya cukup besar untuk membuat diagram sebesar
sebagai labelnya:
Kotlin
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { // Try for a width based on your minimum. val minw: Int = paddingLeft + paddingRight + suggestedMinimumWidth val w: Int = View.resolveSizeAndState(minw, widthMeasureSpec, 1) // Whatever the width is, ask for a height that lets the pie get as big as // it can. val minh: Int = View.MeasureSpec.getSize(w) - textWidth.toInt() + paddingBottom + paddingTop val h: Int = View.resolveSizeAndState(minh, heightMeasureSpec, 0) setMeasuredDimension(w, h) }
Java
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Try for a width based on your minimum. int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth(); int w = resolveSizeAndState(minw, widthMeasureSpec, 1); // Whatever the width is, ask for a height that lets the pie get as big as it // can. int minh = MeasureSpec.getSize(w) - (int)textWidth + getPaddingBottom() + getPaddingTop(); int h = resolveSizeAndState(minh, heightMeasureSpec, 0); setMeasuredDimension(w, h); }
Ada tiga hal penting yang perlu diperhatikan dalam kode ini:
- Kalkulasi memperhitungkan padding tampilan. Seperti yang disebutkan sebelumnya, ini adalah tanggung jawab tampilan.
- Metode bantuan
resolveSizeAndState()
digunakan untuk membuat nilai lebar dan tinggi akhir. Helper ini mengembalikan nilaiView.MeasureSpec
yang sesuai dengan membandingkan ukuran tampilan yang diperlukan ke nilai yang diteruskan keonMeasure()
. onMeasure()
tidak memiliki nilai kembalian. Sebagai gantinya, metode mengkomunikasikan hasilnya dengan memanggilsetMeasuredDimension()
. Pemanggilan metode ini bersifat wajib. Jika Anda menghilangkan panggilan ini, ClassView
menampilkan pengecualian runtime.
Gambar
Setelah menentukan pembuatan dan pengukuran objek, Anda dapat mengimplementasikan
onDraw()
. Setiap tampilan mengimplementasikan onDraw()
dengan cara yang berbeda,
tetapi ada beberapa operasi umum yang sama-sama dimiliki oleh sebagian besar penayangan:
- Gambar teks menggunakan
drawText()
. Tentukan jenis huruf dengan memanggilsetTypeface()
dan warna teks dengan memanggilsetColor()
. - Gambar bentuk dasar menggunakan
drawRect()
,drawOval()
, dandrawArc()
. Ubah apakah bentuk akan diisi, diberi garis batas, atau keduanya dengan memanggilsetStyle()
. - Menggambar bentuk yang lebih kompleks menggunakan
Path
. Tentukan bentuk dengan menambahkan garis dan kurva kePath
lalu menggambar bentuknya menggunakandrawPath()
. Seperti bentuk dasar, jalur dapat digarisbatasi, diisi, atau keduanya, bergantung padasetStyle()
. -
Tentukan isi gradien dengan membuat
LinearGradient
objek terstruktur dalam jumlah besar. TeleponsetShader()
untuk menggunakanLinearGradient
pada bentuk yang diisi. - Gambar bitmap menggunakan
drawBitmap()
.
Kode berikut menggambar campuran teks, garis, dan bentuk:
Kotlin
private val data = mutableListOf<Item>() // A list of items that are displayed. private var shadowBounds = RectF() // Calculated in onSizeChanged. private var pointerRadius: Float = 2f // Obtained from styled attributes. private var pointerX: Float = 0f // Calculated in onSizeChanged. private var pointerY: Float = 0f // Calculated in onSizeChanged. private var textX: Float = 0f // Calculated in onSizeChanged. private var textY: Float = 0f // Calculated in onSizeChanged. private var bounds = RectF() // Calculated in onSizeChanged. private var currentItem: Int = 0 // The index of the currently selected item. override fun onDraw(canvas: Canvas) { super.onDraw(canvas) canvas.apply { // Draw the shadow. drawOval(shadowBounds, shadowPaint) // Draw the label text. drawText(data[currentItem].label, textX, textY, textPaint) // Draw the pie slices. data.forEach {item -> piePaint.shader = item.shader drawArc( bounds, 360 - item.endAngle, item.endAngle - item.startAngle, true, piePaint ) } // Draw the pointer. drawLine(textX, pointerY, pointerX, pointerY, textPaint) drawCircle(pointerX, pointerY, pointerRadius, textPaint) } } // Maintains the state for a data item. private data class Item( var label: String, var value: Float = 0f, @ColorInt var color: Int = 0, // Computed values. var startAngle: Float = 0f, var endAngle: Float = 0f, var shader: Shader )
Java
private List<Item> data = new ArrayList<Item>(); // A list of items that are displayed. private RectF shadowBounds; // Calculated in onSizeChanged. private float pointerRadius; // Obtained from styled attributes. private float pointerX; // Calculated in onSizeChanged. private float pointerY; // Calculated in onSizeChanged. private float textX; // Calculated in onSizeChanged. private float textY; // Calculated in onSizeChanged. private RectF bounds; // Calculated in onSizeChanged. private int currentItem = 0; // The index of the currently selected item. protected void onDraw(Canvas canvas) { super.onDraw(canvas); // Draw the shadow. canvas.drawOval( shadowBounds, shadowPaint ); // Draw the label text. canvas.drawText(data.get(currentItem).label, textX, textY, textPaint); // Draw the pie slices. for (int i = 0; i < data.size(); ++i) { Item it = data.get(i); piePaint.setShader(it.shader); canvas.drawArc( bounds, 360 - it.endAngle, it.endAngle - it.startAngle, true, piePaint ); } // Draw the pointer. canvas.drawLine(textX, pointerY, pointerX, pointerY, textPaint); canvas.drawCircle(pointerX, pointerY, pointerRadius, textPaint); } // Maintains the state for a data item. private class Item { public String label; public float value; @ColorInt public int color; // Computed values. public int startAngle; public int endAngle; public Shader shader; }
Menerapkan efek grafis
Android 12 (API level 31) menambahkan
RenderEffect
yang menerapkan efek grafis umum seperti buram, filter warna,
Efek shader Android, dan lainnya untuk
View
objek dan
hierarki rendering. Anda dapat menggabungkan efek sebagai efek berantai, yang terdiri
efek dalam dan luar, atau efek campuran. Dukungan untuk fitur ini
bervariasi tergantung
pada daya pemrosesan perangkat.
Anda juga dapat menerapkan efek pada
RenderNode
untuk
View
dengan memanggil
View.setRenderEffect(RenderEffect)
.
Untuk mengimplementasikan objek RenderEffect
, lakukan hal berikut:
view.setRenderEffect(RenderEffect.createBlurEffect(radiusX, radiusY, SHADER_TILE_MODE))
Anda bisa membuat tampilan secara terprogram atau meng-inflate-nya dari tata letak XML dan
mengambilnya menggunakan View binding atau
findViewById()
.