View Lifecycle — คืออะไร กระบวนการ onMeasure onLayout onDraw

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-03-05 เวลาอ่าน: 10 นาที

View Lifecycle — ลำดับของเมธอดที่ Android เรียกเพื่อวาดและวาดใหม่ส่วนติดต่อผู้ใช้ (View) บนหน้าจอ ต่างจาก Activity หรือ Fragment, View เป็นองค์ประกอบที่มีน้ำหนักเบาไม่มีวงจรชีวิตขยาย แต่ผ่านกระบวนการสามขั้นตอนที่เข้มงวด: onMeasure (การวัด), onLayout (การวางตำแหน่ง), onDraw (การวาด) การเข้าใจ View Lifecycle เป็นสิ่งจำเป็นสำหรับการสร้าง View แบบกำหนดเอง การเพิ่มประสิทธิภาพ และการแก้ปัญหาการวาด ตามข้อมูลจาก Google, View แบบกำหนดเองเมื่อนำไปใช้อย่างถูกต้อง ทำให้ UI เร็วขึ้น 15–40% เมื่อเทียบกับการใช้ ViewGroups มาตรฐานที่ซ้อนกัน เอกสาร Android เกี่ยวกับ View แบบกำหนดเอง อธิบาย onMeasure, onLayout และ onDraw เป็นสามเสาหลักของ View Lifecycle

ข้อควรจำสำคัญ

  • View Lifecycle ประกอบด้วยสามระยะ: onMeasure (ขนาด), onLayout (ตำแหน่ง), onDraw (การวาด) — และถูกกระตุ้นโดย invalidate() หรือ requestLayout()
  • onMeasure คำนวณความกว้างและความสูงของ View ตาม MeasureSpec (AT_MOST, EXACTLY, UNSPECIFIED)
  • onLayout จัดเรียง View ย่อยภายใน ViewGroup โดยกำหนดพิกัด left, top, right, bottom
  • onDraw แสดงเนื้อหา View บน Canvas: พื้นหลัง, ข้อความ, รูปร่าง, รูปภาพ
  • View Lifecycle ที่ไม่ถูกต้องเป็นสาเหตุหลักของปัญหาประสิทธิภาพ UI (jank, เฟรมหลุด) และปัญหาลำดับชั้น

View Lifecycle — คืออะไรใน Android

View Lifecycle คือกระบวนการที่ View (และ ViewGroup) ของ Android ผ่านเพื่อแสดงตัวเองบนหน้าจอ ต่างจาก Activity หรือ Fragment, View ไม่มี onStart/onStop/onDestroy — วงจรชีวิตของมันประกอบด้วยกระบวนการเป็นวงจรของการวัด การวางตำแหน่ง และการวาด วงจรนี้ถูกกระตุ้นทุกครั้งที่ View ต้องการแสดงหรือวาดใหม่

สามระยะของ View Lifecycle:

  • onMeasure(int widthMeasureSpec, int heightMeasureSpec) — กำหนดขนาดที่ต้องการของ View ระบบส่ง MeasureSpec — คำแนะนำเกี่ยวกับขนาดที่อนุญาต (ค่าที่แน่นอน, สูงสุด, หรือไม่มีข้อจำกัด)
  • onLayout(boolean changed, int left, int top, int right, int bottom) — วางตำแหน่ง View และองค์ประกอบย่อยบนหน้าจอ สำหรับ View, มันกำหนดขอบเขตของตัวเอง; สำหรับ ViewGroup, มันกำหนดตำแหน่งขององค์ประกอบย่อย
  • onDraw(Canvas canvas) — วาดเนื้อหา View บน Canvas ที่ให้มา ระบบให้ Canvas ที่แปลคำสั่งเป็น bitmap หรือ GPU

วงจร View Lifecycle ที่สมบูรณ์ยังรวมถึงเมธอดที่เกี่ยวข้องกับการแนบ View กับหน้าต่าง: onAttachedToWindow (View แนบกับหน้าต่าง, มีการเร่ง HW) และ onDetachedFromWindow (View ถูกถอด, ทรัพยากรถูกปล่อย) เมธอดเหล่านี้ถูกเรียกครั้งเดียวต่ออายุของ View และสำคัญสำหรับการลงทะเบียน/ยกเลิกแอนิเมชันและเซ็นเซอร์

ตาม Android Performance Blog, 65% ของปัญหาประสิทธิภาพ UI (jank, เฟรมหลุด) เกี่ยวข้องกับการใช้งาน onMeasure และ onDraw ที่ไม่ถูกต้อง: การแทนที่มากเกินไป, การเรียก requestLayout() โดยไม่จำเป็น, การสร้างออบเจ็กต์ใน onDraw

onMeasure: การวัดขนาด View

onMeasure — ระยะที่สำคัญที่สุดและซับซ้อนที่สุดของ View Lifecycle ในขั้นตอนนี้ Android กำหนดว่า View จะใช้พื้นที่เท่าใดบนหน้าจอ ระบบส่ง MeasureSpec — คำแนะนำที่บรรจุใน int ประกอบด้วยโหมดและขนาด

สามโหมด MeasureSpec:

โหมดค่าคงที่ความหมายตัวอย่าง
EXACTLYMeasureSpec.EXACTLYขนาดที่แน่นอนกำหนดโดยผู้เป็นเจ้าของ (match_parent หรือความกว้างคงที่)width=400dp → MeasureSpec(400, EXACTLY)
AT_MOSTMeasureSpec.AT_MOSTView สามารถมีขนาดสูงสุดที่ระบุ (wrap_content)width ≤ 400dp → MeasureSpec(400, AT_MOST)
UNSPECIFIEDMeasureSpec.UNSPECIFIEDไม่มีข้อจำกัด — View สามารถมีขนาดใดก็ได้ (ScrollView, RecyclerView)ความกว้างไม่จำกัด → MeasureSpec(0, UNSPECIFIED)

การใช้งาน onMeasure ควร:

  • เรียก setMeasuredDimension(int width, int height) เพื่อบันทึกขนาดที่วัดได้
  • คำนึงถึง padding — ลบ getPaddingLeft() + getPaddingRight() จากความกว้างที่มี
  • สำหรับ ViewGroup — วัดองค์ประกอบย่อยทั้งหมดผ่าน measureChild() หรือ measureChildWithMargins()
  • สำหรับ wrap_content — คำนวณขนาดตามเนื้อหา (ข้อความ, รูปภาพ)
  • ไม่เรียก requestLayout() ภายใน onMeasure — จะทำให้เกิดวงจรไม่สิ้นสุด

ข้อผิดพลาดทั่วไป: ไม่คำนึงถึง MeasureSpec เมื่อใช้ wrap_content ถ้า View ถูกตั้งเป็น wrap_content แต่ onMeasure ไม่จัดการ AT_MOST และส่งคืนขนาดคงที่ View จะถูกตัดหรือใช้พื้นที่มากเกินกว่าที่จำเป็น

onLayout: การวาง View บนหน้าจอ

onLayout — ระยะที่ View หรือ ViewGroup จัดเรียงองค์ประกอบย่อย ภายในขอบเขตของมัน สำหรับ View ทั่วไป (ไม่ใช่ ViewGroup), onLayout ไม่จำเป็น — ระบบเรียก layout() ด้วยพารามิเตอร์ที่ส่งจากผู้เป็นเจ้าของ สำหรับ ViewGroup, onLayout เป็นสิ่งจำเป็น — หากไม่มี View ย่อยจะไม่ถูกวาง

ลายเซ็นของ onLayout:

java
@Override
protected void onLayout(boolean changed,
        int left, int top,
        int right, int bottom) {
    // การจัดเรียง View ย่อย
}

พารามิเตอร์ changed บ่งชี้ว่าตำแหน่งหรือขนาดของ View เปลี่ยนไปเมื่อเทียบกับ layout ก่อนหน้าหรือไม่ ถ้าเป็น false, View สามารถข้ามการคำนวณตำแหน่งขององค์ประกอบย่อยใหม่เพื่อการเพิ่มประสิทธิภาพ

สำหรับ ViewGroup, onLayout ควร:

  • วนซ้ำองค์ประกอบย่อยทั้งหมดผ่าน getChildCount() และ getChildAt(i)
  • สำหรับแต่ละองค์ประกอบย่อย กำหนด left, top, right, bottom — พิกัดภายใน ViewGroup (โดยคำนึงถึง padding)
  • เรียก child.layout(l, t, r, b) สำหรับแต่ละองค์ประกอบย่อย
  • คำนึงถึง gravity, margins, alignment

onLayout ถูกเรียกหลังจาก onMeasure — ขนาดที่วัดได้พร้อมใช้งานผ่าน getMeasuredWidth()/getMeasuredHeight() ถ้า View ย่อยมีขนาดจริงที่แตกต่างหลังจาก layout(), requestLayout() จะถูกเรียกเพื่อวัดใหม่ สิ่งนี้เรียกว่า "layout pass" และสามารถกระตุ้นปฏิกิริยาลูกโซ่ของการคำนวณใหม่

onDraw: การวาดเนื้อหา Canvas

onDraw — ระยะที่ View วาดตัวเองบน Canvas นี่เป็นระยะเดียวที่สามารถถูกเรียกหลายครั้งโดยไม่ต้อง onMeasure และ onLayout — ถ้า View ถูกทำเครื่องหมายเป็น invalidate() Canvas ให้ API การวาด: drawLine, drawRect, drawCircle, drawText, drawBitmap และ drawPath

กฎของ onDraw:

  • อย่าสร้างออบเจ็กต์ใน onDraw — การเรียก onDraw แต่ละครั้งควรใช้ออบเจ็กต์ที่สร้างไว้ล่วงหน้า (Path, Paint, Rect) การสร้างออบเจ็กต์ใน onDraw ทำให้เกิดการหยุด GC และเฟรมหลุด
  • อย่าเรียก requestLayout() หรือ invalidate() ภายใน onDraw — จะเริ่มวงจรการวาดใหม่ไม่สิ้นสุด
  • อย่าทำการคำนวณที่ยาวนาน — onDraw ทำงานบน UI thread การคำนวณที่ซับซ้อนควรย้ายไปยัง background thread หรือคำนวณล่วงหน้า
  • ใช้ Hardware Acceleration — ตั้งแต่ API 14+, Canvas สามารถทำงานผ่าน GPU สำหรับกราฟิกที่ซับซ้อน (การไล่สี, เงา, การหมุน) การเร่ง HW ให้การปรับปรุงประสิทธิภาพสูงถึง 300%
  • วาดเฉพาะพื้นที่ที่มองเห็น — ใช้ canvas.clipRect() เพื่อตัดส่วนที่มองไม่เห็น

ลำดับการวาดใน ViewGroup: พื้นหลัง (setBackgroundDrawable) → onDraw (เนื้อหา) → dispatchDraw (View ย่อย) → onDrawForeground (เบื้องหน้า) dispatchDraw เรียก onDraw ของแต่ละองค์ประกอบย่อย การแทนที่ dispatchDraw ใช้สำหรับการใช้เอฟเฟกต์เหนือองค์ประกอบย่อย

ตามสถิติ Android Vitals, สาเหตุที่พบบ่อยที่สุดของเฟรมหลุด ใน onDraw คือการสร้างออบเจ็กต์ภายในเมธอด (48%), การเรียก decodeResource (22%) และการดำเนินการ Path ที่ซับซ้อนโดยไม่มีแคช (15%)

การทำให้ใช้งานไม่ได้: เมื่อ View ถูกวาดใหม่

การทำให้ใช้งานไม่ได้ — กลไกที่กระตุ้นการวาดใหม่ของ View การเรียก invalidate() ทำเครื่องหมาย View ว่า "สกปรก" และกำหนดเวลาการเรียก onDraw ในวงจรการวาดถัดไป การเรียก requestLayout() เป็นการดำเนินการที่ "หนักกว่า" ซึ่งเริ่มวงจรที่สมบูรณ์: onMeasure → onLayout → onDraw

เมธอดมันทำอะไรเมื่อไรใช้
invalidate()เริ่ม onDraw โดยไม่ต้อง onMeasure/onLayoutเฉพาะรูปลักษณ์เปลี่ยน (สี, ข้อความ, ความคืบหน้า)
invalidate(Rect)วาดใหม่เฉพาะพื้นที่ที่ระบุส่วนหนึ่งของ View เปลี่ยน — แอนิเมชัน, การเลือก
postInvalidate()เรียก invalidate จาก non-UI threadbackground thread อัปเดตข้อมูลสำหรับการวาด
requestLayout()เริ่ม onMeasure → onLayout → onDrawขนาดเนื้อหาเปลี่ยน (ข้อความ, รูปภาพ)
forceLayout()ทำเครื่องหมาย View สำหรับการวัดใหม่แบบบังคับสถานะภายในเปลี่ยน ขนาดอาจเปลี่ยน

แอนิเมชันและ View Lifecycle: ViewPropertyAnimator และ ValueAnimator เรียก invalidate() ในทุกเฟรมแอนิเมชัน ObjectAnimator เรียก setter บน View ซึ่งถ้า setter เปลี่ยนขนาด (กว้าง/สูง) จะเรียก requestLayout() โดยอัตโนมัติ สิ่งนี้อาจมีค่าใช้จ่ายสูงสำหรับ ViewGroups ที่ซับซ้อน: แต่ละ requestLayout กระตุ้นลำดับชั้นทั้งหมดจนถึง root view

กฎการเพิ่มประสิทธิภาพ: invalidate() แทน requestLayout() ทุกที่ที่เฉพาะรูปลักษณ์เปลี่ยน (สี, ความโปร่งใส, การหมุนโดยไม่เปลี่ยนขนาด) ใช้ requestLayout เฉพาะเมื่อเปลี่ยนขนาดหรือเนื้อหาที่มีผลต่อขนาด

การเพิ่มประสิทธิภาพ View แบบกำหนดเอง: แนวปฏิบัติที่ดีที่สุด

View แบบกำหนดเองเป็นเครื่องมือที่ทรงพลังสำหรับการสร้าง UI ที่ไม่ซ้ำใคร แต่ต้องการการปฏิบัติตามกฎประสิทธิภาพอย่างเคร่งครัด นี่คือคำแนะนำหลักของ Google สำหรับการเพิ่มประสิทธิภาพ View Lifecycle

  • คำนวณล่วงหน้าทุกอย่างที่สามารถคำนวณได้ — ขนาด, พิกัด, เส้นทาง, สีไล่ระดับ ใน onDraw, ให้วาดอย่างเดียว
  • แคชผลการวัด — ถ้า View มีขนาดคงที่ บันทึก MeasureSpec และส่งคืน setMeasuredDimension โดยไม่ต้องคำนวณเพิ่ม
  • ใช้ ViewConfiguration — getScaledTouchSlop, getScaledMinimumFlingVelocity — สำหรับการจัดการสัมผัส
  • ลดจำนวน View ในลำดับชั้น — View แบบกำหนดเองที่รวมหลายองค์ประกอบเร็วกว่า ViewGroup ที่มี 3–5 View ซ้อนกัน Google แนะนำไม่เกิน 10 View ซ้อนกันต่อหน้าจอ
  • ใช้ ConstraintLayout สำหรับลำดับชั้นแบบราบ — มันสร้าง ViewGroup เดียวที่มีประสิทธิภาพใกล้เคียง RelativeLayout แต่ไม่มีการซ้อนกัน
  • ปิดเลเยอร์ฮาร์ดแวร์หลังการวาดใหม่ — ใช้ setLayerType(LAYER_TYPE_HARDWARE) สำหรับ View ที่มีแอนิเมชันและ setLayerType(LAYER_TYPE_NONE) หลังจากเสร็จสิ้น
  • ใช้ invalidate() กับ Rect — วาดใหม่เฉพาะพื้นที่ที่เปลี่ยน ไม่ใช่ทั้ง View
  • หลีกเลี่ยง overdraw — ใช้ Profile GPU Rendering ใน Android Studio เพื่อระบุการวาดใหม่ที่ไม่จำเป็น ค่า overdraw เฉลี่ยสำหรับแอป Google คือ 1.5x สูงสุดที่แนะนำคือ 2.5x

ตัวอย่างโค้ด View ใน Kotlin

ตัวอย่าง 1: View แบบกำหนดเอง — ตัวบ่งชี้ความคืบหน้า

ตัวบ่งชี้ความคืบหน้าแบบวงกลมอย่างง่ายที่มีการใช้งาน onMeasure, onDraw และ invalidate ที่ถูกต้อง

kotlin
class CircularProgressView constructor(
    context: Context, attrs: AttributeSet? = null
) : View(context, attrs) {

    private val progressPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.BLUE
        style = Paint.Style.STROKE
        strokeWidth = 8f
        strokeCap = Paint.Cap.ROUND
    }

    private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.LTGRAY
        style = Paint.Style.STROKE
        strokeWidth = 8f
    }

    private var progress = 0f
    private var viewWidth = 0
    private var viewHeight = 0

    fun setProgress(value: Float) {
        progress = value.coerceIn(0f, 100f)
        invalidate()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val desiredSize = 100 * resources.displayMetrics.density.toInt()
        val width = MeasureSpec.getSize(widthMeasureSpec)
        val height = MeasureSpec.getSize(heightMeasureSpec)
        val size = minOf(width, height).coerceAtLeast(desiredSize)
        setMeasuredDimension(size, size)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val padding = progressPaint.strokeWidth / 2
        val radius = (minOf(viewWidth, viewHeight) - padding) / 2
        val cx = viewWidth / 2f
        val cy = viewHeight / 2f
        canvas.drawCircle(cx, cy, radius, backgroundPaint)
        val sweepAngle = (progress / 100f) * 360f
        canvas.drawArc(cx - radius, cy - radius, cx + radius, cy + radius,
            -90f, sweepAngle, false, progressPaint)
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        viewWidth = w
        viewHeight = h
    }
}

แถบความคืบหน้าแบบวงกลม: onMeasure ส่งคืนขนาดสี่เหลี่ยมตาม MeasureSpec, onSizeChanged จดจำขนาด, onDraw วาดพื้นหลังและส่วนโค้งความคืบหน้า Invalidate ถูกเรียกเมื่อความคืบหน้าเปลี่ยน — onMeasure/onLayout ไม่ได้รับผลกระทบ Paint ถูกสร้างครั้งเดียวในคอนสตรัคเตอร์ ไม่ใช่ใน onDraw

ตัวอย่าง 2: ViewGroup — FlowLayout อย่างง่าย

ViewGroup แบบกำหนดเองที่จัดเรียง View ย่อยเป็นแถว (เหมือน Flexbox wrap)

kotlin
class FlowLayout constructor(
    context: Context, attrs: AttributeSet? = null
) : ViewGroup(context, attrs) {

    private val horizontalSpacing = 8.dpToPx(resources)
    private val verticalSpacing = 8.dpToPx(resources)

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val width = MeasureSpec.getSize(widthMeasureSpec)
        var totalHeight = paddingTop + paddingBottom
        var rowWidth = paddingLeft
        var rowHeight = 0
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight)
            if (rowWidth + child.measuredWidth > width - paddingRight) {
                totalHeight += rowHeight + verticalSpacing
                rowWidth = paddingLeft
                rowHeight = 0
            }
            rowWidth += child.measuredWidth + horizontalSpacing
            rowHeight = maxOf(rowHeight, child.measuredHeight)
        }
        totalHeight += rowHeight
        setMeasuredDimension(
            MeasureSpec.getSize(widthMeasureSpec),
            resolveSize(totalHeight, heightMeasureSpec)
        )
    }

    override fun onLayout(changed: Boolean,
        l: Int, t: Int, r: Int, b: Int) {
        var rowTop = paddingTop
        var rowLeft = paddingLeft
        var rowHeight = 0
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            if (rowLeft + child.measuredWidth > r - paddingRight) {
                rowTop += rowHeight + verticalSpacing
                rowLeft = paddingLeft
                rowHeight = 0
            }
            child.layout(rowLeft, rowTop, rowLeft + child.measuredWidth, rowTop + child.measuredHeight)
            rowLeft += child.measuredWidth + horizontalSpacing
            rowHeight = maxOf(rowHeight, child.measuredHeight)
        }
    }

    override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
        return MarginLayoutParams(context, attrs)
    }
}

FlowLayout แทนที่ onMeasure: วัดแต่ละองค์ประกอบย่อย เลื่อนไปแถวใหม่เมื่อเกินความกว้าง คำนวณความสูงทั้งหมด onLayout วางองค์ประกอบย่อยตามพิกัดโดยคำนึงถึงการขึ้นบรรทัดใหม่ generateLayoutParams ส่งคืน MarginLayoutParams เพื่อรองรับ margin บน View ย่อย

ตัวอย่าง 3: onDraw พร้อมแคช Path

View แบบกำหนดเองวาดเส้นโค้ง Bezier ที่เรียบ คำนวณ Path ล่วงหน้าและแคชมัน

kotlin
class WaveView constructor(
    context: Context, attrs: AttributeSet? = null
) : View(context, attrs) {

    private val wavePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.parseColor("#4A90D9")
        style = Paint.Style.FILL
    }

    private val wavePath = Path()
    private var isPathDirty = true
    private var viewWidth = 0
    private var viewHeight = 0

    fun refreshWave() {
        isPathDirty = true
        invalidate()
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        viewWidth = w
        viewHeight = h
        isPathDirty = true
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        if (isPathDirty) {
            wavePath.reset()
            val amplitude = viewHeight * 0.1f
            wavePath.moveTo(0f, viewHeight * 0.5f)
            for (x in 0..viewWidth step 4) {
                val y = viewHeight * 0.5f + amplitude * Math.sin(x * 2 * Math.PI / viewWidth).toFloat()
                wavePath.lineTo(x.toFloat(), y)
            }
            wavePath.lineTo(viewWidth.toFloat(), viewHeight.toFloat())
            wavePath.lineTo(0f, viewHeight.toFloat())
            wavePath.close()
            isPathDirty = false
        }
        canvas.drawPath(wavePath, wavePaint)
    }
}

แคช Path: isPathDirty = true เฉพาะเมื่อขนาด View เปลี่ยนหรือเรียก refreshWave() ใน onDraw, Path จะถูกคำนวณใหม่เฉพาะเมื่อมัน "สกปรก" สิ่งนี้ป้องกันการคำนวณเส้นโค้ง Bezier ใหม่ในทุกเฟรมแอนิเมชัน ประหยัด CPU

คำถามที่พบบ่อย

View Lifecycle แตกต่างจาก Activity Lifecycle อย่างไร?

View Lifecycle เป็นกระบวนการวาดแบบวงจร (onMeasure → onLayout → onDraw) ที่เป็นอิสระจากการสร้าง/ทำลาย Activity View ไม่มี onStart/onStop — มัน either มองเห็นได้ (แนบกับหน้าต่าง) หรือไม่ Activity Lifecycle จัดการสถานะขององค์ประกอบแอปพลิเคชัน View Lifecycle จัดการการวาด UI

requestLayout() มีผลกระทบต่อประสิทธิภาพอย่างไร?

requestLayout() เริ่มวงจรที่สมบูรณ์ onMeasure → onLayout → onDraw สำหรับต้นไม้ View ทั้งหมดจากราก ถ้า requestLayout() ถูกเรียกบ่อยครั้ง (เช่น ทุกเฟรมแอนิเมชัน) มันทำให้เกิด jank และเฟรมหลุด ตาม Google หนึ่ง requestLayout ใช้เวลาเฉลี่ย 2–5 ms บน ViewGroup ที่มี 10 องค์ประกอบ สำหรับแอนิเมชัน ใช้ invalidate()

เมื่อใดที่ onAttachedToWindow ถูกเรียก?

onAttachedToWindow ถูกเรียกเมื่อ View แนบกับ Window — กลายเป็นส่วนหนึ่งของลำดับชั้นที่มองเห็น ในขณะนั้น View ได้รับการเร่ง HW และการเข้าถึงทรัพยากร Window (WindowManager, Display) onAttachedToWindow เป็นสถานที่ที่เหมาะสมสำหรับการลงทะเบียนตัวฟังแอนิเมชันและ BroadcastReceiver ที่มีชีวิตตราบเท่าที่ View มองเห็น

overdraw คืออะไรและจะลดมันได้อย่างไร?

Overdraw คือสถานการณ์ที่พิกเซลถูกวาดหลายครั้งในเฟรมเดียว แต่ละรอบพิเศษสูญเสียเวลา GPU วิธีการลด: ตั้ง windowBackground ใน theme (อย่าวาดพื้นหลังใน layout), ใช้ canvas.clipRect(), หลีกเลี่ยงการรวมพื้นหลังที่ซ้อนกัน, ใช้ ConstraintLayout แทน LinearLayout ที่ซ้อนกัน Android Studio → Profile GPU Rendering → Overdraw แสดงแผนที่สีของ overdraw (น้ำเงิน = 1x, แดง = 3x+)

จำเป็นต้องใช้ super.onDraw() ใน View แบบกำหนดเองหรือไม่?

ใช่ ถ้า View มีพื้นหลัง super.onDraw() วาดพื้นหลังของ View ถ้า View แบบกำหนดเองของคุณไม่มีพื้นหลังหรือคุณวาดพื้นหลังของคุณเอง สามารถละ super.onDraw() ได้ — ประหยัดการวาดหนึ่งรอบ สำหรับ ViewGroup, super.dispatchDraw() เป็นสิ่งจำเป็น — มันวาด View ย่อย

สรุป

  • View Lifecycle — สามระยะการวาด: onMeasure (ขนาด), onLayout (ตำแหน่ง), onDraw (การแสดงผล)
  • onMeasure ประมวลผล MeasureSpec (EXACTLY, AT_MOST, UNSPECIFIED) และเรียก setMeasuredDimension
  • onLayout ใน ViewGroup วาง View ย่อยด้วยพิกัด left/top/right/bottom
  • onDraw แสดงเนื้อหาบน Canvas — อย่าสร้างออบเจ็กต์ภายในเมธอดนี้
  • invalidate() เริ่มเฉพาะ onDraw, requestLayout() เริ่มวงจรที่สมบูรณ์ onMeasure → onLayout → onDraw
  • View แบบกำหนดเองทำให้ UI เร็วขึ้น 15–40% แต่ต้องการการใช้งาน onMeasure ที่ถูกต้องและการแคชออบเจ็กต์ใน onDraw
  • สำหรับกราฟิกที่ซับซ้อน ใช้ Hardware Acceleration และแคช Path/Bitmap

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม