มุมมองที่กำหนดเอง
วาดคอมโพเนนต์ส่วนติดต่อผู้ใช้แบบกำหนดเองตั้งแต่ต้น ขยาย View ใช้ onMeasure และ onDraw ร่วมกับ Canvas และ Paint กำหนดแอตทริบิวต์ XML แบบกำหนดเอง และสร้างมุมมองแบบผสม
มุมมองที่กำหนดเอง เป็นบทเรียน Android Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 5 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Android Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Android Academy มีบทเรียนทั้งหมด 5 บทเรียน
เหตุใดจึงต้องใช้มุมมองที่กำหนดเอง
บางครั้งมุมมอง Android ที่มีอยู่ก็ไม่สามารถทำสิ่งที่คุณต้องการได้อย่างพอดี มุมมองที่กำหนดเองช่วยให้คุณสามารถ:
- วาดรูปร่าง กราฟ วงแหวนแสดงความคืบหน้า หรือแผนภูมิที่ไม่มีอยู่ใน SDK
- สร้างคอมโพเนนต์ UI ของแบรนด์ที่ใช้สอดคล้องกันทั่วทั้งแอป
- รวมหลายมุมมองให้เป็นคอมโพเนนต์ที่ใช้ซ้ำได้หนึ่งรายการ (มุมมองแบบผสม)
- เพิ่มประสิทธิภาพด้วยการลดโครงสร้างลำดับชั้นของเลย์เอาต์ที่ซ้อนกัน
การสืบทอดจาก View
สร้างมุมมองที่กำหนดเองโดยสืบทอดจาก View (หรือคลาสย่อยอย่าง ImageView) แล้วเขียนทับ constructor ที่จำเป็น:
class CircleProgressView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
var progress: Float = 0f
set(value) {
field = value.coerceIn(0f, 100f)
invalidate() // trigger redraw
}
// onMeasure and onDraw go here
}onMeasure()
onMeasure() แจ้ง parent ว่ามุมมองนี้ต้องการขนาดเท่าใด ต้องเรียก setMeasuredDimension() ที่ท้ายเมธอดเสมอ:
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val desiredSize = 200 // dp → pixels
val px = (desiredSize * resources.displayMetrics.density).toInt()
val width = resolveSize(px, widthMeasureSpec)
val height = resolveSize(px, heightMeasureSpec)
// Square: same width and height
val size = minOf(width, height)
setMeasuredDimension(size, size)
}Paint — เครื่องมือวาด
ออบเจ็กต์ Paint เก็บข้อมูลสไตล์สำหรับการวาด เช่น สี ความกว้างเส้น และการลบรอยหยัก ให้สร้างเพียงครั้งเดียว — ห้ามสร้างภายใน onDraw():
class CircleProgressView ... : View(...) {
// Create Paint in init, not in onDraw!
private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.LTGRAY
style = Paint.Style.STROKE
strokeWidth = 20f
strokeCap = Paint.Cap.ROUND
}
private val progressPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.parseColor("#6750A4")
style = Paint.Style.STROKE
strokeWidth = 20f
strokeCap = Paint.Cap.ROUND
}
}onDraw() — การวาดมุมมอง
onDraw(canvas) จะถูกเรียกทุกครั้งที่ต้องวาดมุมมองใหม่ ใช้ Canvas เพื่อวาดรูปร่าง ข้อความ และบิตแมป:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val cx = width / 2f
val cy = height / 2f
val radius = (minOf(width, height) / 2f) - 30f
// Draw background circle
canvas.drawCircle(cx, cy, radius, backgroundPaint)
// Draw progress arc
val oval = RectF(cx - radius, cy - radius, cx + radius, cy + radius)
val sweepAngle = 360f * (progress / 100f)
canvas.drawArc(oval, -90f, sweepAngle, false, progressPaint)
}invalidate() กับ requestLayout()
มีสองเมธอดสำหรับเรียกให้เกิดการอัปเดต:
invalidate()— วาดมุมมองใหม่ (เรียกonDraw) ใช้เมื่อเนื้อหาเปลี่ยนแต่ขนาดยังเท่าเดิมrequestLayout()— วัดขนาดและวาดใหม่ (เรียกonMeasureแล้วจึงเรียกonDraw) ใช้เมื่อขนาดของมุมมองเปลี่ยน
แอตทริบิวต์ที่กำหนดเอง (attrs.xml)
ประกาศแอตทริบิวต์ XML ที่กำหนดเอง เพื่อให้กำหนดค่ามุมมองจากไฟล์เลย์เอาต์ได้:
<!-- res/values/attrs.xml -->
<resources>
<declare-styleable name="CircleProgressView">
<attr name="progressColor" format="color" />
<attr name="trackColor" format="color" />
<attr name="strokeWidth" format="dimension" />
<attr name="progress" format="float" />
</declare-styleable>
</resources>การอ่านแอตทริบิวต์ที่กำหนดเอง
อ่าน attrs ใน constructor โดยใช้ obtainStyledAttributes:
init {
context.obtainStyledAttributes(attrs, R.styleable.CircleProgressView).use { ta ->
progressPaint.color = ta.getColor(
R.styleable.CircleProgressView_progressColor,
Color.parseColor("#6750A4")
)
backgroundPaint.color = ta.getColor(
R.styleable.CircleProgressView_trackColor,
Color.LTGRAY
)
val sw = ta.getDimension(R.styleable.CircleProgressView_strokeWidth, 20f)
progressPaint.strokeWidth = sw
backgroundPaint.strokeWidth = sw
progress = ta.getFloat(R.styleable.CircleProgressView_progress, 0f)
}
}การใช้มุมมองที่กำหนดเองใน XML
ใช้มุมมองที่กำหนดเองใน XML ของเลย์เอาต์ โดยระบุเส้นทางคลาสแบบเต็มและแอตทริบิวต์ที่กำหนดเอง:
<!-- In any layout XML -->
<com.example.myapp.CircleProgressView
android:id="@+id/progressView"
android:layout_width="120dp"
android:layout_height="120dp"
app:progress="65"
app:progressColor="@color/primary"
app:trackColor="@color/surface_variant"
app:strokeWidth="12dp" />
<!-- Control in code: -->
binding.progressView.progress = 75fมุมมองแบบผสม
มุมมองแบบผสม รวมมุมมองที่มีอยู่แล้วให้เป็นคอมโพเนนต์ที่ใช้ซ้ำได้หนึ่งรายการ ให้สืบทอดจากคลาสเลย์เอาต์อย่าง LinearLayout:
class SearchBarView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : LinearLayout(context, attrs) {
private val binding = ViewSearchBarBinding.inflate(LayoutInflater.from(context), this)
init {
orientation = HORIZONTAL
binding.ivSearch.setOnClickListener {
onSearchClick?.invoke(binding.etSearch.text.toString())
}
}
var onSearchClick: ((String) -> Unit)? = null
fun setHint(hint: String) { binding.etSearch.hint = hint }
}การวาดข้อความ
วาดข้อความบน Canvas ด้วย drawText() จัดให้อยู่กึ่งกลางอย่างแม่นยำโดยใช้ Paint.getFontMetrics():
private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.BLACK
textSize = 48f
textAlign = Paint.Align.CENTER
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val cx = width / 2f
val cy = height / 2f
// Vertically center the text:
val fm = textPaint.fontMetrics
val textY = cy - (fm.ascent + fm.descent) / 2f
canvas.drawText("${progress.toInt()}%", cx, textY, textPaint)
}ตรวจสอบความเข้าใจอย่างรวดเร็ว
ควรเรียกเมธอดใดเพื่อเรียกให้มุมมองที่กำหนดเองวาดใหม่ เมื่อข้อมูลเปลี่ยนแต่ขนาดยังเท่าเดิม
ทบทวน: มุมมองที่กำหนดเอง
ตอนนี้คุณสามารถวาดสิ่งต่าง ๆ บนหน้าจอได้แล้ว:
- สืบทอด
Viewด้วย@JvmOverloads constructor onMeasure()— รายงานขนาดที่ต้องการด้วยsetMeasuredDimension()Paint— กำหนดสไตล์ สร้างครั้งเดียว ไม่ใช่ภายในonDraw()onDraw(canvas)— วาดวงกลม ส่วนโค้ง ข้อความ และบิตแมป- ใช้
invalidate()เพื่อวาดใหม่ และrequestLayout()เพื่อวัดขนาดใหม่ - แอตทริบิวต์ที่กำหนดเอง ผ่าน
attrs.xmlสำหรับการกำหนดค่าด้วย XML - มุมมองแบบผสม — สืบทอดเลย์เอาต์เพื่อรวมมุมมองที่มีอยู่แล้ว
ถัดไป: ทำให้ UI ของคุณมีชีวิตชีวาด้วยภาพเคลื่อนไหวและการเปลี่ยนฉาก
คำถามที่พบบ่อย
บทเรียน “มุมมองที่กำหนดเอง” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “มุมมองที่กำหนดเอง” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Android Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Android Academy มีบทเรียนทั้งหมด 5 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “มุมมองที่กำหนดเอง”
วาดคอมโพเนนต์ส่วนติดต่อผู้ใช้แบบกำหนดเองตั้งแต่ต้น ขยาย View ใช้ onMeasure และ onDraw ร่วมกับ Canvas และ Paint กำหนดแอตทริบิวต์ XML แบบกำหนดเอง และสร้างมุมมองแบบผสม คุณปฏิบัติ Android Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Android Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Android Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 5 บทเรียน
บทเรียน “มุมมองที่กำหนดเอง” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Android Academy นี้ได้ไหม
ได้ บทเรียน Android Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- พื้นฐาน Material Design
- ธีมและสไตล์
- มุมมองที่กำหนดเอง
- ภาพเคลื่อนไหวและการเปลี่ยนผ่าน
- การนำทางด้านล่างและแท็บ