Bagian terpenting dari tampilan kustom adalah penampilannya. Gambar kustom dapat mudah atau kompleks tergantung kebutuhan aplikasi Anda. Dokumen ini mencakup beberapa operasi yang paling umum.
Untuk mengetahui informasi selengkapnya, lihat Ringkasan drawable.
Mengganti onDraw()
Langkah terpenting dalam menggambar tampilan kustom adalah mengganti metode
onDraw(). Parameter untuk onDraw() adalah objek
Canvas
yang dapat digunakan oleh tampilan untuk menggambar. Class Canvas menentukan metode untuk menggambar teks, garis, bitmap, dan banyak elemen grafis dasar lainnya. 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
Framework
android.graphics
membagi gambar menjadi dua area:
- Apa yang 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
menentukan apakah persegi panjang itu akan diisi warna atau dibiarkan kosong.
Canvas menentukan bentuk yang dapat Anda gambar di layar, dan
Paint menentukan warna, gaya, font, dan sebagainya dari setiap bentuk
yang Anda gambar.
Sebelum menggambar apa pun, buat satu atau beberapa objek Paint. Contoh berikut melakukannya dalam metode yang disebut init. Metode ini 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. Tampilan sering digambar ulang, dan banyak objek gambar memerlukan inisialisasi yang mahal.
Membuat objek gambar dalam metode onDraw() mengurangi performa secara signifikan dan dapat membuat UI Anda terasa lamban.
Menangani peristiwa tata letak
Untuk menggambar tampilan kustom dengan benar, cari tahu ukurannya. Tampilan kustom yang kompleks sering kali perlu melakukan beberapa kalkulasi tata letak yang bergantung pada ukuran dan bentuk areanya di layar. Jangan pernah mengasumsikan ukuran tampilan di layar. Meskipun hanya ada satu aplikasi yang menggunakan tampilan Anda, aplikasi tersebut perlu menangani berbagai ukuran layar, berbagai kepadatan layar, dan berbagai rasio aspek dalam mode potret dan lanskap.
Meskipun View
memiliki banyak metode untuk menangani pengukuran, sebagian besarnya tidak perlu diganti. Jika tampilan Anda tidak memerlukan kontrol khusus atas ukurannya, cukup ganti satu metode:
onSizeChanged().
onSizeChanged() dipanggil saat tampilan Anda pertama kali diberi ukuran, dan dipanggil sekali lagi jika ukuran tampilan berubah karena alasan apa pun. Hitung posisi, dimensi, dan nilai lain apa pun yang terkait dengan ukuran tampilan Anda di onSizeChanged(), alih-alih menghitungnya ulang setiap kali Anda menggambar.
Dalam contoh berikut, onSizeChanged() adalah tempat tampilan menghitung persegi panjang pembatas diagram dan posisi relatif label teks serta elemen visual lainnya.
Saat tampilan Anda diberi ukuran, pengelola tata letak akan mengasumsikan bahwa ukuran tersebut mencakup padding tampilan. Tangani nilai padding saat Anda menghitung ukuran tampilan. Berikut ini cuplikan dari onSizeChanged() yang menunjukkan cara 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 memerlukan kontrol yang lebih terperinci atas parameter tata letak tampilan, terapkan
onMeasure().
Parameter metode ini adalah
View.MeasureSpec
yang memberitahukan seberapa besar ukuran tampilan yang dikehendaki oleh induk tampilan Anda, dan
apakah ukuran itu merupakan batas maksimal 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(). Dalam implementasi ini, berupaya menjadikan areanya cukup besar untuk membuat diagram sebesar 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, hal ini ditangani oleh tampilan.
- Metode helper
resolveSizeAndState()digunakan untuk membuat nilai lebar dan tinggi akhir. Helper ini menampilkan nilaiView.MeasureSpecyang sesuai dengan membandingkan ukuran tampilan yang diperlukan dengan nilai yang diteruskan keonMeasure(). onMeasure()tidak memiliki nilai kembalian. Sebagai gantinya, metode menyampaikan hasilnya dengan memanggilsetMeasuredDimension(). Pemanggilan metode ini bersifat wajib. Jika Anda menghilangkan panggilan ini, classViewakan memunculkan pengecualian saat runtime.
Gambar
Setelah menentukan pembuatan objek dan kode pengukuran, Anda dapat mengimplementasikan
onDraw(). Setiap tampilan mengimplementasikan onDraw() secara berbeda, tetapi ada beberapa operasi umum yang dilakukan oleh sebagian besar tampilan:
- Gambar teks menggunakan
drawText(). Tentukan jenis huruf dengan memanggilsetTypeface()dan warna teks dengan memanggilsetColor(). - Gambar bentuk-bentuk dasar menggunakan
drawRect(),drawOval(), dandrawArc(). Ubah apakah bentuk akan diisi, diberi garis batas, atau keduanya dengan memanggilsetStyle(). - Gambar bentuk yang lebih kompleks menggunakan
Pathclass. Tentukan bentuk dengan menambahkan garis dan kurva ke objekPath, lalu gambar bentuk menggunakandrawPath(). Seperti bentuk-bentuk dasar, jalur dapat diberi garis batas, diisi, atau keduanya, bergantung padasetStyle(). -
Tentukan isi gradien dengan membuat objek
LinearGradient. PanggilsetShader()untuk menggunakanLinearGradientAnda pada bentuk yang terisi. - Menggambar 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 (level API 31) menambahkan
RenderEffect
class, yang menerapkan efek grafis umum seperti blur, filter warna,
efek shader Android, dan lainnya ke
objek View dan
hierarki rendering. Anda dapat menggabungkan efek sebagai efek berantai, yang terdiri dari efek dalam dan luar, atau efek campuran. Dukungan untuk fitur ini bervariasi, bergantung pada kemampuan pemrosesan perangkat.
Anda juga dapat menerapkan efek ke
RenderNode yang mendasarinya untuk
View dengan memanggil
View.setRenderEffect(RenderEffect).
Untuk menerapkan objek RenderEffect, lakukan hal berikut:
view.setRenderEffect(RenderEffect.createBlurEffect(radiusX, radiusY, SHADER_TILE_MODE))
Anda dapat membuat tampilan secara terprogram atau meng-inflate-nya dari tata letak XML dan mengambilnya menggunakan View binding atau
findViewById().