Custom View sa Android — pagguhit at pagproseso ng mga event

May-akda: IT Sectr Nai-publish: 2026-07-20 Oras ng pagbabasa: 9 min

Custom View — ay isang custom na component ng interface sa Android, na ginagawa sa pamamagitan ng pagmamana mula sa klase ng View o mga subclass nito (Button, TextView, ImageView) at pag-override ng mga pangunahing pamamaraan: onDraw para sa pagguhit, onMeasure para sa pagsukat ng mga dimensyon at onTouchEvent para sa pagproseso ng mga pagpindot. Ayon sa Google Android Developer Guide (2024), ginagamit ang Custom View kapag ang mga karaniwang component ng Android SDK ay hindi nagbibigay ng kinakailangang pag-uugali o hitsura — halimbawa, para sa custom na animation, hindi karaniwang mga hugis, specialized na mga graph at elemento ng laro.

Mga pangunahing punto

  • Custom View — ginagawa sa pamamagitan ng pagmamana ng View o mga subclass nito na may pag-override ng onDraw, onMeasure, onTouchEvent.
  • Ang pagguhit ay isinasagawa sa pamamagitan ng Canvas API — mga geometric na hugis, teksto, mga path, Bitmap at animation.
  • onMeasure ay responsable para sa tamang dimensyon ng View na isinasaalang-alang ang mga parameter ng layout at padding.
  • Ang mga custom na XML attribute ay idineklara sa res/values/attrs.xml at binabasa sa constructor ng View.
  • Ang pagproseso ng pagpindot onTouchEvent ay nagbibigay-daan sa pagdagdag ng suporta para sa mga galaw, drag-and-drop at multitouch.

Ano ang Custom View sa Android?

Custom View — ay isang klase na nagmamana ng android.view.View (o isa sa mga subclass nito) at nag-o-override ng mga system method para ipatupad ang sarili nitong lohika sa pagguhit, pagsukat at pagproseso ng event. Ang Custom View ay ang pangunahing mekanismo ng Android para sa paglikha ng mga natatanging UI component.

Ang Android ay nagbibigay ng dalawang approach sa paggawa ng Custom View: pagmamana mula sa View(ganap na custom na pagguhit sa pamamagitan ng onDraw) at pagmamana mula sa isang umiiral na subclass ng View (halimbawa, pag-override ng Button o TextView para magdagdag ng functionality habang pinapanatili ang base behavior).

Ang base constructor ng Custom View ay dapat tumanggap ng Context at AttributeSet — ito ay nagpapahintulot sa system na gumawa ng View mula sa XML code. Kung ang View ay gagamitin lamang mula sa code, sapat na ang constructor na may Context. Ang ikatlong constructor na may style ay kailangan para sa suporta ng Android theme.

kotlin
class CustomView
    @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.BLUE
        strokeWidth = 4f
        style = Paint.Style.FILL
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawCircle(width / 2f,
            height / 2f,
            50f, paint)
    }
}

Minimal na Custom View ay binubuo ng constructor at onDraw. Gayunpaman, para sa tamang paggana sa Android Layout system, kailangan din i-override ang onMeasure — kung hindi, ang View ay maaaring magpakita ng zero na dimensyon o hindi tama ang reaksyon sa layout parameter na wrap_content.

Ayon sa Google I/O 2023, ang mga custom na View ay ginagamit sa 65% ng mga app mula sa top 100 ng Google Play. Ang pinakakaraniwang dahilan ng paggawa ng Custom View: custom na animation (35%), custom na graph at diagram (25%), specialized na control element (20%) at branded na component (20%).

onMeasure at onLayout: pamamahala ng mga dimensyon

onMeasure — ay ang pamamaraan kung saan ipinapaalam ng View sa system ang ninanais na dimensyon nito batay sa ibinigay na MeasureSpec (mga limitasyon ng magulang). Kung walang tamang implementasyon ng onMeasure, ang View ay maaaring magkaroon ng zero na taas sa wrap_content o sakupin ang buong lugar ng screen sa match_parent.

MeasureSpec ay binubuo ng mode (EXACTLY, AT_MOST, UNSPECIFIED) at value. EXACTLY — ang magulang ay nagtakda ng eksaktong sukat (match_parent o fixed value). AT_MOST — ang magulang ay nagtakda ng maximum (wrap_content). UNSPECIFIED — walang limitasyon (ScrollView, ListView).

kotlin
override fun onMeasure(
    widthMeasureSpec: Int,
    heightMeasureSpec: Int
) {
    val desiredWidth = paddingLeft + paddingRight
        + DEFAULT_WIDTH
    val desiredHeight = paddingTop + paddingBottom
        + DEFAULT_HEIGHT

    val measuredWidth = MeasureSpec.getSize(widthMeasureSpec)
    val modeWidth = MeasureSpec.getMode(widthMeasureSpec)

    val resultWidth = when (modeWidth) {
        MeasureSpec.EXACTLY -> measuredWidth
        MeasureSpec.AT_MOST ->
            desiredWidth.coerceAtMost(measuredWidth)
        else -> desiredWidth
    }

    val resultHeight = resolveSize(desiredHeight,
        heightMeasureSpec)

    setMeasuredDimension(resultWidth, resultHeight)
}

resolveSize — ay isang utility method ng Android na nagpapasimple sa implementasyon ng onMeasure. Tumatanggap ito ng ninanais na sukat at MeasureSpec at nagbabalik ng tamang value: para sa EXACTLY — eksaktong sukat, para sa AT_MOST — minimum ng ninanais at maximum, para sa UNSPECIFIED — ninanais na sukat.

onLayout para sa View (hindi ViewGroup) ay karaniwang hindi nai-override — ito ay tinatawag ng magulang para sa pagpoposisyon ng mga child element. Para sa ViewGroup, ang onLayout ay sapilitan — dito kailangang tawagan ang layout() para sa bawat child View.

onDraw at Canvas API: pagguhit

onDraw — ang puso ng Custom View. Ang pamamaraang ito ay tinatawag ng Android sa bawat pag-redraw ng View. Ito ay tumatanggap ng Canvas object kung saan isinasagawa ang lahat ng pagguhit: mga geometric na hugis, teksto, mga path, Bitmap at animation.

Canvas API ay nagbibigay ng mga pamamaraan para sa lahat ng pangunahing operasyon: drawCircle, drawRect, drawLine, drawPath, drawText, drawBitmap, drawArc, drawOval. Para sa pag-configure ng style, ginagamit ang Paint — isang object na tumutukoy sa kulay, kapal, style ng fill, font, anino at effect.

kotlin
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)

    // Background
    canvas.drawRect(0f, 0f,
        width.toFloat(), height.toFloat(), bgPaint)

    // Teksto
    canvas.drawText("Pasadyang View",
        paddingLeft.toFloat(),
        paddingTop.toFloat() + textPaint.textSize,
        textPaint)

    // Circle
    canvas.drawCircle(
        width / 2f,
        height / 2f,
        radius, circlePaint)

    // Path (Bezier curve)
    val path = Path().apply {
        moveTo(0f, height.toFloat())
        quadTo(width / 2f, 0f,
            width.toFloat(), height.toFloat())
    }
    canvas.drawPath(path, pathPaint)
}

Paint — ay ang configurator ng style ng pagguhit. Ang Anti-aliasing (Paint.ANTI_ALIAS_FLAG) ay sapilitan para sa makinis na mga gilid. Ang Subpixel text (SUBPIXEL_TEXT_FLAG) ay nagpapabuti sa kalidad ng teksto. Ang Style.FILL, STROKE at FILL_AND_STROKE ay tumutukoy kung ang hugis ay pupunan, iguguhit na outline, o pareho.

Mahalaga: huwag gumawa ng mga object ng Paint, Path, Rect sa onDraw — ito ay nagdudulot ng mga alokasyon sa loop ng pagguhit at nagti-trigger ng garbage collection, na nagpapababa ng FPS. Gawin ang lahat ng drawing object sa constructor o sa init method. Para sa animation, gamitin ang ValueAnimator o ObjectAnimator na may invalidate() para simulan ang pag-redraw.

Custom na XML attribute sa attrs.xml

Ang custom na attribute ay nagpapahintulot sa pag-configure ng Custom View mula sa XML code tulad ng standard na android:layout_width o android:background. Ang mga attribute ay idineklara sa file na res/values/attrs.xml na may pagbanggit ng pangalan, uri at opsyonal na default value.

Mga uri ng attribute: string, integer, float, boolean, color, dimension, enum, flag, fraction, reference (referens sa resource). Para sa bawat uri, awtomatikong ini-parse ng Android ang value mula sa XML at ipinapasa ito sa TypedArray.

xml
<!-- res/values/attrs.xml -->
<resources>
    <declare-styleable name="CustomView">
        <attr name="circleColor"
            format="color" />
        <attr name="circleRadius"
            format="dimension" />
        <attr name="labelText"
            format="string" />
        <attr name="showAnimation"
            format="boolean" />
    </declare-styleable>
</resources>

Sa constructor ng Custom View, ang mga attribute ay binabasa sa pamamagitan ng context.obtainStyledAttributes, na nagbabalik ng TypedArray. Ang TypedArray ay nagbibigay ng mga naka-type na access method: getColor, getDimension, getString, getBoolean, getInt. Pagkatapos magbasa, kailangang tawagan ang recycle() sa TypedArray para magbakante ng mga resource.

kotlin
init {
    val typedArray = context.obtainStyledAttributes(
        attrs,
        R.styleable.CustomView
    )

    circleColor = typedArray.getColor(
        R.styleable.CustomView_circleColor,
        Color.BLUE
    )
    circleRadius = typedArray.getDimension(
        R.styleable.CustomView_circleRadius,
        50f
    )
    labelText = typedArray.getString(
        R.styleable.CustomView_labelText
    ) ?: "Default"

    typedArray.recycle()
}

Paggamit sa XML: idagdag ang namespace app (xmlns:app="http://schemas.android.com/apk/res-auto") at gamitin ang custom na attribute bilang app:circleColor="@color/red". Awtomatikong kukumpletuhin at susuriin ng Android Studio ang mga uri ng attribute kung ang deklarasyon sa attrs.xml ay tama.

Pagproseso ng pagpindot sa onTouchEvent

onTouchEvent — ang pamamaraan na tinatawag sa bawat pagpindot ng View. Ito ay tumatanggap ng MotionEvent object na may impormasyon tungkol sa uri ng event (ACTION_DOWN, ACTION_MOVE, ACTION_UP, ACTION_CANCEL), mga coordinate, presyon at bilang ng mga daliri (multitouch).

Para sa pagproseso ng mga kumplikadong galaw (swipe, scale, long press), gamitin ang GestureDetector o ScaleGestureDetector kasama ng onTouchEvent. Pinapasimple ng GestureDetector ang pagkilala ng onSingleTapUp, onFling, onLongPress, onDoubleTap at iba pang karaniwang galaw.

kotlin
private val gestureDetector = GestureDetector(
    context, object : GestureDetector.SimpleOnGestureListener() {
    override fun onSingleTapUp(e: MotionEvent): Boolean {
        handleTap(e.x, e.y)
        return true
    }
})

override fun onTouchEvent(event: MotionEvent): Boolean {
    val handled = gestureDetector.onTouchEvent(event)

    when (event.action) {
        MotionEvent.ACTION_MOVE -> {
            currentX = event.x
            currentY = event.y
            invalidate()
            return true
        }
    }

    return handled || super.onTouchEvent(event)
}

Hit-testing — pagtukoy kung ang pagpindot ay tumama sa isang partikular na lugar ng View. Para sa mga parihabang lugar, gamitin ang Rect.contains(x, y). Para sa mga bilog — suriin ang distansya mula sa gitna: sqrt(dx^2 + dy^2) < radius. Para sa arbitraryong hugis — Path.op() o Region.contains().

Multitouch ay pinoproseso sa pamamagitan ng MotionEvent.getPointerCount() at getPointerId(index). Ang bawat daliri ay nakakakuha ng natatanging ID na pinapanatili mula ACTION_DOWN hanggang ACTION_POINTER_UP. Ang ACTION_MOVE ay maaaring maglaman ng data ng lahat ng aktibong daliri — gamitin ang getHistoricalX/Y para sa interpolation ng galaw.

Pag-optimize ng performance ng Custom View

Custom View ay maaaring maging bottleneck ng performance kung ang onDraw ay madalas tawagin (animation, scroll) o naglalaman ng mabibigat na operasyon. Ang Android ay nagbibigay ng ilang mekanismo para sa pag-optimize ng pagguhit ng mga custom na component.

Layer type para sa kumplikadong pagguhit

View.setLayerType ay nagpapahintulot sa paglipat ng rendering ng View sa software (LAYER_TYPE_SOFTWARE) o hardware (LAYER_TYPE_HARDWARE) layer. Ang Software layer ay kapaki-pakinabang para sa kumplikadong Canvas graphics na hindi sinusuportahan ng hardware acceleration — halimbawa, drawTextOnPath o kumplikadong Path effect.

invalidate lamang ang kinakailangang lugar

invalidate(Rect) — nagre-redraw lamang ng tinukoy na lugar ng View, hindi ang buong component. Ito ay kritikal para sa malalaking Custom View (graphics, mapa, canvas para sa pagguhit), kung saan ang buong pag-redraw ng bawat frame ay nagdudulot ng pagbaba ng FPS. Gamitin ang postInvalidateOnAnimation() para sa synchronisasyon sa vsync.

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

    // Mga bagay na ginawa nang isang beses sa constructor
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val rect = Rect()
    private val position = PointF()

    fun updatePosition(x: Float, y: Float) {
        position.set(x, y)
        // I-redraw lamang ang lugar sa paligid ng punto
        rect.set(
            (x - 10).toInt(),
            (y - 10).toInt(),
            (x + 10).toInt(),
            (y + 10).toInt()
        )
        invalidate(rect)
    }
}

Hardware acceleration ay naka-enable bilang default sa Android 3.0+ (API 11+). Ang mga Canvas operation na drawCircle, drawRect, drawBitmap ay hardware accelerated at isinasagawa sa GPU. Gayunpaman, ang drawTextOnPath, drawVertices, kumplikadong clipping operation ay hindi naa-accelerate — gamitin ang LAYER_TYPE_SOFTWARE para sa kanila.

Ayon sa Android Performance Patterns (Google, 2023), ang mga pangunahing sanhi ng lag sa Custom View ay mga alokasyon sa onDraw (paglikha ng mga object bawat frame), pag-redraw ng buong View kapag nagbago ang isang elemento at kawalan ng LayerType.HARDWARE para sa static na nilalaman. Ang pag-aayos ng tatlong problemang ito ay nagbibigay ng pagtaas ng FPS mula 30 hanggang 60 sa karamihan ng mga senaryo.

PamamaraanLayuninDalas ng pagtawag
onDrawPagguhit ng nilalaman ng ViewSa bawat invalidate()
onMeasurePagtukoy ng dimensyon ng ViewKapag nagbago ang layout
onLayoutPagpoposisyon ng child ViewPagkatapos ng onMeasure
onTouchEventPagproseso ng pagpindotSa bawat pagpindot
onSizeChangedReaksyon sa pagbabago ng lakiSa unang layout at pagbabago

Mga madalas itanong

Kailan gagamitin ang Custom View sa halip na Compose?

Custom View (Canvas API) ay makatwiran kapag kailangan ang maximum na performance ng custom na pagguhit (graphics, video editor, mapa), integration sa umiiral na View-based code o suporta sa mga bersyon ng Android na mas mababa sa API 21. Ang Jetpack Compose ay modernong approach para sa karamihan ng mga bagong proyekto, gamit ang declarative UI at mas angkop para sa mga dynamic na interface.

Kailangan bang i-override ang onMeasure?

Oo, kung ang View ay gumagamit ng wrap_content sa XML code. Kung walang onMeasure, ang wrap_content ay gagana tulad ng match_parent, dahil ang standard na implementasyon ng View.onMeasure ay hindi nagtatakda ng default na laki. Kung ang View ay palaging may fixed na laki o match_parent, ang onMeasure ay maaaring laktawan.

Paano i-animate ang Custom View?

Gamitin ang ValueAnimator o ObjectAnimator para baguhin ang mga property ng View (kulay, radius, posisyon) at tawagan ang invalidate() sa callback ng animation para sa pag-redraw. Ang ValueAnimator ay gumagana sa main thread at naka-synchronize sa vsync. Para sa kumplikadong animation (physics, particle), gamitin ang Choreographer.FrameCallback o Android Animation Framework.

Paano iproseso ang multitouch sa Custom View?

Sa onTouchEvent, gamitin ang MotionEvent.getPointerCount() para sa pagtukoy ng bilang ng mga daliri at getPointerId(i) para sa pagsubaybay sa bawat daliri sa pamamagitan ng natatanging ID. ACTION_POINTER_DOWN / ACTION_POINTER_UP — mga event ng pagdagdag/pag-alis ng daliri. Para sa scaling, gamitin ang ScaleGestureDetector kasama ng onTouchEvent — pinapasimple nito ang detection ng pinch-to-zoom.

Maaari bang gamitin ang Custom View sa Jetpack Compose?

Oo, sa pamamagitan ng AndroidView — isang composable function na naglalagay ng View-based element sa Compose hierarchy. Ang AndroidView ay tumatanggap ng factory para sa paggawa ng View at update callback para sa synchronisasyon ng estado. Ito ay nagpapahintulot sa muling paggamit ng mga umiiral na Custom View sa mga bagong Compose na proyekto nang hindi kinakailangang ganap na muling isulat.

Buod

  • Custom View — custom na Android component, na ginagawa sa pamamagitan ng pagmamana ng View na may pag-override ng onDraw, onMeasure at onTouchEvent para sa custom na pagguhit at pagproseso.
  • onDraw at Canvas API — batayan ng pagguhit: mga geometric na hugis, teksto, Bitmap, mga path — na may configuration sa pamamagitan ng Paint para sa kulay, style at font.
  • onMeasure ay mahalaga para sa tamang paggana ng wrap_content; gumagamit ng MeasureSpec (EXACTLY, AT_MOST, UNSPECIFIED) at resolveSize para sa pagkalkula ng mga dimensyon.
  • Custom na attribute sa pamamagitan ng attrs.xml at TypedArray ay nagpapahintulot sa configuration ng Custom View mula sa XML code na may suporta sa autocomplete sa Android Studio.
  • onTouchEvent na may GestureDetector ay nagbibigay ng pagproseso ng pagpindot, galaw at multitouch, at ang hit-testing ay tumutukoy sa lugar ng interaksyon.
  • Performance: gumawa ng mga object nang isang beses sa constructor, gamitin ang invalidate(Rect) para sa partial na pag-redraw at LayerType para sa kumplikadong effect.
  • Ang Custom View ay nag-i-integrate sa Jetpack Compose sa pamamagitan ng AndroidView, na nagpapahintulot sa muling paggamit ng mga handa nang component sa mga bagong Compose na proyekto.

Gagawa kami ng mobile application na turnkey

Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.

Pag-usapan ang proyekto

Basahin din