Bagian terpenting dari tampilan kustom adalah penampilannya. Gambar kustom dapat mudah atau rumit sesuai 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 tampilan untuk menggambar sendiri. Class Canvas
menentukan metode untuk menggambar teks, garis, bitmap, dan banyak elemen grafis
primitif lainnya. Anda dapat menggunakan metode ini di onDraw()
untuk membuat
antarmuka pengguna (UI) kustom.
Mulailah 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 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
menentukan apakah akan mengisi persegi panjang tersebut 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
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()
akan
mengurangi performa secara signifikan dan dapat membuat UI Anda menjadi lambat.
Menangani peristiwa tata letak
Untuk menggambar tampilan kustom dengan benar, cari tahu ukurannya. Tampilan kustom yang kompleks sering kali perlu melakukan beberapa penghitungan tata letak bergantung pada ukuran dan bentuk areanya di layar. Jangan pernah berasumsi tentang ukuran tampilan Anda di layar. Meskipun hanya satu aplikasi yang menggunakan tampilan Anda, aplikasi tersebut perlu menangani berbagai ukuran layar, beberapa 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, hanya
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()
, bukan menghitungnya ulang setiap kali Anda menggambar.
Dalam contoh berikut, onSizeChanged()
adalah tempat tampilan
menghitung kotak pembatas diagram dan posisi relatif
label teks dan elemen visual lainnya.
Saat tampilan Anda diberi ukuran, pengelola tata letak mengasumsikan bahwa ukuran
menyertakan padding tampilan. Tangani nilai padding saat Anda menghitung
ukuran tampilan. Berikut adalah 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 baik atas parameter tata letak tampilan, terapkan
onMeasure()
.
Parameter metode ini adalah
nilai View.MeasureSpec
yang memberi tahu seberapa besar ukuran tampilan yang dikehendaki oleh induk tampilan Anda dan
apakah ukuran tersebut merupakan batas maksimal atau sekadar saran. Sebagai pengoptimalan,
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, kode ini mencoba membuat 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, ini adalah tanggung jawab tampilan.
- Metode helper
resolveSizeAndState()
digunakan untuk membuat nilai lebar dan tinggi akhir. Helper ini menampilkan nilaiView.MeasureSpec
yang sesuai dengan membandingkan ukuran tampilan yang diperlukan dengan nilai yang diteruskan keonMeasure()
. onMeasure()
tidak memiliki nilai kembalian. Sebagai gantinya, metode ini mengomunikasikan hasilnya dengan memanggilsetMeasuredDimension()
. Memanggil metode ini bersifat wajib. Jika Anda menghilangkan panggilan ini, classView
akan menampilkan pengecualian runtime.
Gambar
Setelah menentukan pembuatan objek dan kode pengukuran, Anda dapat mengimplementasikan
onDraw()
. Setiap tampilan mengimplementasikan onDraw()
dengan cara berbeda,
tetapi ada beberapa operasi umum yang dibagikan 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 diisi, diberi garis batas, atau keduanya dengan memanggilsetStyle()
. - Gambar bentuk yang lebih kompleks menggunakan
class
Path
. Tentukan bentuk dengan menambahkan garis dan kurva pada 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 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
class
RenderEffect
, yang menerapkan efek grafis umum seperti pemburaman, 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 tergantung daya pemrosesan perangkat.
Anda juga dapat menerapkan efek ke
RenderNode
yang mendasarinya untuk
View
dengan memanggil
View.setRenderEffect(RenderEffect)
.
Untuk mengimplementasikan 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, lalu
mengambilnya menggunakan View binding atau
findViewById()
.