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 — 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.
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 — 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).
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 — 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.
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.
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.
<!-- 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.
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.
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.
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.
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.
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(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.
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.
| Pamamaraan | Layunin | Dalas ng pagtawag |
|---|---|---|
| onDraw | Pagguhit ng nilalaman ng View | Sa bawat invalidate() |
| onMeasure | Pagtukoy ng dimensyon ng View | Kapag nagbago ang layout |
| onLayout | Pagpoposisyon ng child View | Pagkatapos ng onMeasure |
| onTouchEvent | Pagproseso ng pagpindot | Sa bawat pagpindot |
| onSizeChanged | Reaksyon sa pagbabago ng laki | Sa unang layout at pagbabago |
Mga madalas itanong
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.
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.
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.
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.
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
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.
Basahin din