Custom View — то је прилагођена компонента интерфејса у Android-у, која се креира наслеђивањем класе View или њених подкласа (Button, TextView, ImageView) и преоптерећењем кључних метода: onDraw за цртање, onMeasure за мерење димензија и onTouchEvent за обраду додира. Према Google Android Developer Guide (2024), Custom View се користи када стандардне Android SDK компоненте не обезбеђују потребно понашање или изглед — на пример, за прилагођене анимације, нестандардне облике, специјализоване графиконе и елементе игара.
Главно
Custom View — то је класа која наслеђује android.view.View (или једну од његових подкласа) и преоптерећује системске методе за имплементацију сопствене логике цртања, мерења и обраде догађаја. Custom View је фундаментални механизам Android-а за креирање јединствених UI компоненти.
Android пружа два приступа креирању Custom View-а: наслеђивање од View (потпуно прилагођено цртање кроз onDraw) и наслеђивање од постојеће View подкласе (на пример, преоптерећење Button-а или TextView-а за додавање функционалности уз задржавање основног понашања).
Основни конструктор Custom View-а мора прихватити Context и AttributeSet — то омогућава систему да креира View из XML кода. Ако ће View бити коришћен само из кода, довољан је конструктор са Context-ом. Трећи конструктор са стилом је потребан за подршку Android тема.
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)
}
}
Минимални Custom View се састоји од конструктора и onDraw-а. Међутим, за коректан рад у Android Layout систему потребно је преоптеретити и onMeasure — у супротном View може да се прикаже са нултим димензијама или неправилно реагује на layout параметре wrap_content.
Према Google I/O 2023, прилагођени View-ови се користе у 65% апликација из top 100 Google Play-а. Најчешћи разлози креирања Custom View-а: прилагођене анимације (35%), прилагођени графикони и дијаграми (25%), специјализовани контролни елементи (20%) и брендиране компоненте (20%).
onMeasure — то је метод у ком View обавештава систем о својим жељеним димензијама на основу прослеђеног MeasureSpec-а (родитељска ограничења). Без правилне имплементације onMeasure-а, View може имати нулту висину при wrap_content или заузимати целу површину екрана при match_parent.
MeasureSpec се састоји од режима (EXACTLY, AT_MOST, UNSPECIFIED) и вредности. EXACTLY — родитељ је задао тачну димензију (match_parent или фиксна вредност). AT_MOST — родитељ је задао максимум (wrap_content). UNSPECIFIED — без ограничења (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 — то је корисна Android метода која поједностављује имплементацију onMeasure-а. Прима жељену димензију и MeasureSpec и враћа коректну вредност: за EXACTLY — тачну димензију, за AT_MOST — минимум од жељене и максималне, за UNSPECIFIED — жељену димензију.
onLayout за View (не ViewGroup) се обично не преоптерећује — позива га родитељ за позиционирање деце. За ViewGroup onLayout је обавезан — у њему треба позвати layout() за свако дете View.
onDraw — срце Custom View-а. Овај метод позива Android при сваком прецртавању View-а. У њега се прослеђује Canvas објекат кроз који се изводи све цртање: геометријске фигуре, текст, путање, Bitmap и анимације.
Canvas API пружа методе за све основне операције: drawCircle, drawRect, drawLine, drawPath, drawText, drawBitmap, drawArc, drawOval. За подешавање стила користи се Paint — објекат који дефинише боју, дебљину, стил попуне, фонт, сенке и ефекте.
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Позадина
canvas.drawRect(0f, 0f,
width.toFloat(), height.toFloat(), bgPaint)
// Текст
canvas.drawText("Прилагођени приказ",
paddingLeft.toFloat(),
paddingTop.toFloat() + textPaint.textSize,
textPaint)
// Круг
canvas.drawCircle(
width / 2f,
height / 2f,
radius, circlePaint)
// Путања (Bezier-ова крива)
val path = Path().apply {
moveTo(0f, height.toFloat())
quadTo(width / 2f, 0f,
width.toFloat(), height.toFloat())
}
canvas.drawPath(path, pathPaint)
}
Paint — то је конфигуратор стила цртања. Anti-aliasing (Paint.ANTI_ALIAS_FLAG) је обавезан за глатке ивице. Subpixel text (SUBPIXEL_TEXT_FLAG) побољшава квалитет текста. Style.FILL, STROKE и FILL_AND_STROKE одређују да ли ће фигура бити попуњена, само контурна или обоје.
Важно: не креирајте објекте Paint, Path, Rect у onDraw-у — то доводи до алокација у петљи цртања и покреће garbage collection, што смањује FPS. Креирајте све објекте цртања у конструктору или init методу. За анимацију користите ValueAnimator или ObjectAnimator са invalidate() за покретање прецртавања.
Прилагођени атрибути омогућавају подешавање Custom View-а из XML кода исто као стандардни android:layout_width или android:background. Атрибути се декларишу у фајлу res/values/attrs.xml са навођењем имена, типа и опционе подразумеване вредности.
Типови атрибута: string, integer, float, boolean, color, dimension, enum, flag, fraction, reference (референца на ресурс). За сваки тип Android аутоматски парсира вредност из XML-а и прослеђује је у 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>
У конструктору Custom View-а атрибути се читају кроз context.obtainStyledAttributes, који враћа TypedArray. TypedArray пружа типизиране методе приступа: getColor, getDimension, getString, getBoolean, getInt. Након читања потребно је позвати recycle() на TypedArray-у за ослобађање ресурса.
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()
}
Коришћење у XML-у: додајте именски простор app (xmlns:app="http://schemas.android.com/apk/res-auto") и користите прилагођене атрибуте као app:circleColor="@color/red". Android Studio ће аутоматски допуњавати и проверавати типове атрибута ако је декларација у attrs.xml коректна.
onTouchEvent — метод који се позива при сваком додиру View-а. Прима MotionEvent објекат са информацијом о типу догађаја (ACTION_DOWN, ACTION_MOVE, ACTION_UP, ACTION_CANCEL), координатама, притиску и броју прстију (мултитач).
За обраду сложених гестова (свајп, скалирање, дуг притисак) користите GestureDetector или ScaleGestureDetector у комбинацији са onTouchEvent-ом. GestureDetector поједностављује препознавање onSingleTapUp, onFling, onLongPress, onDoubleTap и других стандардних гестова.
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 — одређивање да ли је додир погодио одређену област View-а. За правоугаоне области користите Rect.contains(x, y). За кругове — провера удаљености од центра: sqrt(dx^2 + dy^2) < radius. За произвољне облике — Path.op() или Region.contains().
Мултитач се обрађује кроз MotionEvent.getPointerCount() и getPointerId(index). Сваки прст добија јединствени ID који се чува од ACTION_DOWN до ACTION_POINTER_UP. ACTION_MOVE може садржати податке свих активних прстију — користите getHistoricalX/Y за интерполацију покрета.
Custom View може постати уско грло перформанси ако се onDraw позива често (анимација, скрол) или садржи тешке операције. Android пружа неколико механизама за оптимизацију цртања прилагођених компоненти.
View.setLayerType омогућава пребацивање рендеровања View-а на софтверски (LAYER_TYPE_SOFTWARE) или хардверски (LAYER_TYPE_HARDWARE) слој. Software layer је користан за сложену Canvas графику која није подржана хардверским убрзањем — на пример, drawTextOnPath или сложени Path ефекти.
invalidate(Rect) — прецртава само наведену област View-а, а не цео компонент. Ово је критично важно за велике Custom View-ове (графике, мапе, платна за цртање), где потпуно прецртавање сваког кадра изазива пад FPS-а. Користите postInvalidateOnAnimation() за синхронизацију са vsync-ом.
class EfficientCustomView
@JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : View(context, attrs) {
// Објекти креирани једном у конструктору
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)
// Прецртај само област око тачке
rect.set(
(x - 10).toInt(),
(y - 10).toInt(),
(x + 10).toInt(),
(y + 10).toInt()
)
invalidate(rect)
}
}
Хардверско убрзање је укључено подразумевано на Android 3.0+ (API 11+). Canvas операције drawCircle, drawRect, drawBitmap се хардверски убрзавају и извршавају на GPU-у. Међутим, drawTextOnPath, drawVertices, сложене clipping операције се не убрзавају — за њих користите LAYER_TYPE_SOFTWARE.
Према Android Performance Patterns (Google, 2023), главни узроци лагова Custom View-а су алокације у onDraw-у (креирање објеката у сваком кадру), прецртавање целог View-а при промени једног елемента и одсуство LayerType.HARDWARE за статички садржај. Исправљање ова три проблема даје пораст FPS-а са 30 на 60 у већини сценарија.
| Метод | Намена | Учесталост позива |
|---|---|---|
| onDraw | Цртање садржаја View-а | При сваком invalidate() |
| onMeasure | Одређивање димензија View-а | При промени layout-а |
| onLayout | Позиционирање деце View-а | Након onMeasure-а |
| onTouchEvent | Обрада додира | При сваком додиру |
| onSizeChanged | Реакција на промену величине | При првом layout-у и промени |
Често постављана питања
Custom View (Canvas API) је оправдан када је потребна максимална перформанса прилагођеног цртања (графике, видео едитори, мапе), интеграција са постојећим View-based кодом или подршка Android верзијама испод API 21. Jetpack Compose је модеран приступ за већину нових пројеката, који користи декларативни UI и боље одговара динамичким интерфејсима.
Да, ако View користи wrap_content у XML коду. Без onMeasure-а, wrap_content ће радити као match_parent, пошто стандардна имплементација View.onMeasure не поставља подразумевану величину. Ако View увек има фиксну величину или match_parent, onMeasure се може прескочити.
Користите ValueAnimator или ObjectAnimator за промену својстава View-а (боја, радијус, позиција) и позивајте invalidate() у callback-у анимације за прецртавање. ValueAnimator ради на главној нити и синхронизован је са vsync-ом. За сложене анимације (физика, честице) користите Choreographer.FrameCallback или Android Animation Framework.
У onTouchEvent-у користите MotionEvent.getPointerCount() за одређивање броја прстију и getPointerId(i) за праћење сваког прста по јединственом ID-у. ACTION_POINTER_DOWN / ACTION_POINTER_UP — догађаји додавања/уклањања прста. За скалирање користите ScaleGestureDetector у пару са onTouchEvent-ом — поједностављује детекцију pinch-to-zoom-а.
Да, кроз AndroidView — composable функцију која убацује View-based елемент у Compose хијерархију. AndroidView прима фабрику за креирање View-а и callback update за синхронизацију стања. Ово омогућава поновно коришћење постојећих Custom View-ова у новим Compose пројектима без потпуног преписивања.
Закључак
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође