Bagian terpenting dari tampilan kustom adalah penampilannya. Gambar kustom dapat mudah atau kompleks tergantung kebutuhan aplikasi Anda. Pelajaran ini mencakup beberapa operasi yang paling umum.
Selain dalam pelajaran ini, Anda dapat menemukan informasi terkait lainnya di bagian Canvas dan 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.
Namun, sebelum dapat memanggil metode menggambar apa pun, Anda perlu membuat objek Paint
. Bagian selanjutnya dalam artikel ini membahas Paint
lebih mendalam.
Membuat Objek Gambar
Framework android.graphics
membagi gambar menjadi dua area:
Misalnya, Canvas
menyediakan metode untuk menggambar garis, sedangkan Paint
menyediakan metode untuk menentukan warna garis tersebut. Canvas
memiliki metode untuk menggambar persegi panjang, sedangkan Paint
menentukan apakah persegi panjang itu akan diisi warna atau dibiarkan kosong. Sederhananya, Canvas
menentukan bentuk yang dapat Anda gambar di layar, sedangkan Paint
menentukan warna, gaya, font, dan sebagainya dari setiap bentuk yang Anda gambar.
Jadi, sebelum menggambar apa pun, Anda perlu membuat satu atau beberapa objek Paint
. Contoh PieChart
melakukan hal ini dalam metode yang disebut init
, yang dipanggil dari konstruktor dari Java, tetapi kita dapat menginisialisasi secara inline di Kotlin:
Kotlin
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 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 sangat 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
Agar dapat menggambar tampilan kustom dengan baik, Anda perlu mengetahui ukurannya. Tampilan kustom yang kompleks sering kali perlu melakukan beberapa kalkulasi tata letak yang bergantung pada ukuran dan bentuk areanya di layar. Anda tidak boleh 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 tinggi lebar 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, Anda hanya perlu mengganti 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 PieChart
, onSizeChanged()
adalah tempat tampilan PieChart
menghitung persegi panjang pembatas diagram lingkaran serta posisi relatif label teks dan elemen visual lainnya.
Saat tampilan Anda diberi ukuran, pengelola tata letak akan mengasumsikan bahwa ukuran tersebut mencakup semua padding tampilan. Anda harus menangani nilai padding ini saat menghitung ukuran tampilan. Berikut ini cuplikan dari PieChart.onSizeChanged()
yang menunjukkan cara melakukannya:
Kotlin
// Account for padding var xpad = (paddingLeft + paddingRight).toFloat() val ypad = (paddingTop + paddingBottom).toFloat() // Account for the label if (showText) xpad += textWidth val ww = w.toFloat() - xpad val hh = h.toFloat() - ypad // Figure out how big we can make the pie. val diameter = Math.min(ww, hh)
Java
// 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 we 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 nilai 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 upaya pengoptimalan, nilai-nilai ini disimpan sebagai integer yang dikemas, dan gunakan metode statis View.MeasureSpec
untuk mengekstrak informasi yang disimpan di setiap integer.
Berikut ini contoh implementasi onMeasure()
.
Dalam implementasi ini, PieChart
berupaya menjadikan areanya cukup besar untuk membuat lingkaran itu sebesar labelnya:
Kotlin
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { // Try for a width based on our minimum val minw: Int = paddingLeft + paddingRight + suggestedMinimumWidth val w: Int = View.resolveSizeAndState(minw, widthMeasureSpec, 1) // Whatever the width ends up being, ask for a height that would let the pie // get as big as it can val minh: Int = View.MeasureSpec.getSize(w) - textWidth.toInt() + paddingBottom + paddingTop val h: Int = View.resolveSizeAndState( View.MeasureSpec.getSize(w) - textWidth.toInt(), heightMeasureSpec, 0 ) setMeasuredDimension(w, h) }
Java
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Try for a width based on our minimum int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth(); int w = resolveSizeAndState(minw, widthMeasureSpec, 1); // Whatever the width ends up being, ask for a height that would let the pie // get as big as it can int minh = MeasureSpec.getSize(w) - (int)mTextWidth + getPaddingBottom() + getPaddingTop(); int h = resolveSizeAndState(MeasureSpec.getSize(w) - (int)mTextWidth, heightMeasureSpec, 0); setMeasuredDimension(w, h); }
Ada tiga hal penting yang perlu diperhatikan dalam kode ini:
- Kalkulasi memperhitungkan padding tampilan. Seperti disebutkan di atas, hal ini ditangani oleh tampilan.
- Metode helper
resolveSizeAndState()
digunakan untuk membuat nilai lebar dan tinggi akhir. Helper ini menampilkan nilaiView.MeasureSpec
yang sesuai dengan membandingkan ukuran tampilan yang diinginkan dengan spesifikasi yang diteruskan keonMeasure()
. onMeasure()
tidak memiliki nilai kembalian. Sebagai gantinya, metode tersebut menyampaikan hasilnya dengan memanggilsetMeasuredDimension()
. Pemanggilan metode ini bersifat wajib. Jika Anda menghilangkan panggilan ini, classView
akan memunculkan pengecualian waktu proses.
Mulai Menggambar
Setelah menentukan pembuatan objek dan kode pengukuran, Anda dapat mengimplementasikan onDraw()
. Setiap tampilan mengimplementasikan onDraw()
dengan cara berbeda, tetapi beberapa operasi umum berikut selalu 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 class
Path
. Tentukan bentuk dengan menambahkan garis dan kurva ke objekPath
, lalu gambar bentuk menggunakandrawPath()
. Sama seperti bentuk-bentuk dasar, path dapat diberi garis batas, diisi, atau keduanya, bergantung padasetStyle()
. -
Tentukan isi gradien dengan membuat objek
LinearGradient
. PanggilsetShader()
untuk menggunakanLinearGradient
Anda pada bentuk yang terisi. - Gambar bitmap menggunakan
drawBitmap()
.
Misalnya, berikut adalah kode yang menggambar PieChart
. Kode ini menggunakan campuran teks, garis, dan bentuk.
Kotlin
override fun onDraw(canvas: Canvas) { super.onDraw(canvas) canvas.apply { // Draw the shadow drawOval(shadowBounds, shadowPaint) // Draw the label text drawText(data[mCurrentItem].mLabel, textX, textY, textPaint) // Draw the pie slices data.forEach { piePaint.shader = it.mShader drawArc(bounds, 360 - it.endAngle, it.endAngle - it.startAngle, true, piePaint) } // Draw the pointer drawLine(textX, pointerY, pointerX, pointerY, textPaint) drawCircle(pointerX, pointerY, pointerSize, mTextPaint) } }
Java
protected void onDraw(Canvas canvas) { super.onDraw(canvas); // Draw the shadow canvas.drawOval( shadowBounds, shadowPaint ); // Draw the label text canvas.drawText(data.get(currentItem).mLabel, 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, pointerSize, mTextPaint); }