0Pricing
Android Academy · บทเรียน

มุมมองที่กำหนดเอง

วาดคอมโพเนนต์ส่วนติดต่อผู้ใช้แบบกำหนดเองตั้งแต่ต้น ขยาย 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. พื้นฐาน Material Design
  2. ธีมและสไตล์
  3. มุมมองที่กำหนดเอง
  4. ภาพเคลื่อนไหวและการเปลี่ยนผ่าน
  5. การนำทางด้านล่างและแท็บ
← กลับไปที่ Android Academy