Custom View у Android-у — цртање и обрада догађаја

Аутор: IT Sectr Објављено: 2026-07-20 Време читања: 9 мин

Custom View — то је прилагођена компонента интерфејса у Android-у, која се креира наслеђивањем класе View или њених подкласа (Button, TextView, ImageView) и преоптерећењем кључних метода: onDraw за цртање, onMeasure за мерење димензија и onTouchEvent за обраду додира. Према Google Android Developer Guide (2024), Custom View се користи када стандардне Android SDK компоненте не обезбеђују потребно понашање или изглед — на пример, за прилагођене анимације, нестандардне облике, специјализоване графиконе и елементе игара.

Главно

  • Custom View — креира се наслеђивањем View или његових подкласа са преоптерећењем onDraw, onMeasure, onTouchEvent.
  • Цртање се изводи кроз Canvas API — геометријске фигуре, текст, путање, Bitmap и анимације.
  • onMeasure одговара за коректне димензије View-а узимајући у обзир layout параметре и padding.
  • Прилагођени XML атрибути се декларишу у res/values/attrs.xml и читају у конструктору View-а.
  • Обрада додира onTouchEvent омогућава додавање подршке за гестове, drag-and-drop и мултитач.

Шта је Custom View у Android-у?

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 тема.

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)
    }
}

Минимални 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 и onLayout: управљање димензијама

onMeasure — то је метод у ком View обавештава систем о својим жељеним димензијама на основу прослеђеног MeasureSpec-а (родитељска ограничења). Без правилне имплементације onMeasure-а, View може имати нулту висину при wrap_content или заузимати целу површину екрана при match_parent.

MeasureSpec се састоји од режима (EXACTLY, AT_MOST, UNSPECIFIED) и вредности. EXACTLY — родитељ је задао тачну димензију (match_parent или фиксна вредност). AT_MOST — родитељ је задао максимум (wrap_content). UNSPECIFIED — без ограничења (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 — то је корисна Android метода која поједностављује имплементацију onMeasure-а. Прима жељену димензију и MeasureSpec и враћа коректну вредност: за EXACTLY — тачну димензију, за AT_MOST — минимум од жељене и максималне, за UNSPECIFIED — жељену димензију.

onLayout за View (не ViewGroup) се обично не преоптерећује — позива га родитељ за позиционирање деце. За ViewGroup onLayout је обавезан — у њему треба позвати layout() за свако дете View.

onDraw и Canvas API: цртање

onDraw — срце Custom View-а. Овај метод позива Android при сваком прецртавању View-а. У њега се прослеђује Canvas објекат кроз који се изводи све цртање: геометријске фигуре, текст, путање, Bitmap и анимације.

Canvas API пружа методе за све основне операције: drawCircle, drawRect, drawLine, drawPath, drawText, drawBitmap, drawArc, drawOval. За подешавање стила користи се Paint — објекат који дефинише боју, дебљину, стил попуне, фонт, сенке и ефекте.

kotlin
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() за покретање прецртавања.

Прилагођени XML атрибути у attrs.xml

Прилагођени атрибути омогућавају подешавање 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.

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>

У конструктору Custom View-а атрибути се читају кроз context.obtainStyledAttributes, који враћа TypedArray. TypedArray пружа типизиране методе приступа: getColor, getDimension, getString, getBoolean, getInt. Након читања потребно је позвати recycle() на TypedArray-у за ослобађање ресурса.

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()
}

Коришћење у XML-у: додајте именски простор app (xmlns:app="http://schemas.android.com/apk/res-auto") и користите прилагођене атрибуте као app:circleColor="@color/red". Android Studio ће аутоматски допуњавати и проверавати типове атрибута ако је декларација у attrs.xml коректна.

Обрада додира са onTouchEvent

onTouchEvent — метод који се позива при сваком додиру View-а. Прима MotionEvent објекат са информацијом о типу догађаја (ACTION_DOWN, ACTION_MOVE, ACTION_UP, ACTION_CANCEL), координатама, притиску и броју прстију (мултитач).

За обраду сложених гестова (свајп, скалирање, дуг притисак) користите GestureDetector или ScaleGestureDetector у комбинацији са onTouchEvent-ом. GestureDetector поједностављује препознавање onSingleTapUp, onFling, onLongPress, onDoubleTap и других стандардних гестова.

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 — одређивање да ли је додир погодио одређену област 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-а

Custom View може постати уско грло перформанси ако се onDraw позива често (анимација, скрол) или садржи тешке операције. Android пружа неколико механизама за оптимизацију цртања прилагођених компоненти.

Layer type за сложено цртање

View.setLayerType омогућава пребацивање рендеровања View-а на софтверски (LAYER_TYPE_SOFTWARE) или хардверски (LAYER_TYPE_HARDWARE) слој. Software layer је користан за сложену Canvas графику која није подржана хардверским убрзањем — на пример, drawTextOnPath или сложени Path ефекти.

invalidate само потребне области

invalidate(Rect) — прецртава само наведену област View-а, а не цео компонент. Ово је критично важно за велике Custom View-ове (графике, мапе, платна за цртање), где потпуно прецртавање сваког кадра изазива пад FPS-а. Користите postInvalidateOnAnimation() за синхронизацију са vsync-ом.

kotlin
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 уместо Compose-а?

Custom View (Canvas API) је оправдан када је потребна максимална перформанса прилагођеног цртања (графике, видео едитори, мапе), интеграција са постојећим View-based кодом или подршка Android верзијама испод API 21. Jetpack Compose је модеран приступ за већину нових пројеката, који користи декларативни UI и боље одговара динамичким интерфејсима.

Да ли је обавезно преоптеретити onMeasure?

Да, ако View користи wrap_content у XML коду. Без onMeasure-а, wrap_content ће радити као match_parent, пошто стандардна имплементација View.onMeasure не поставља подразумевану величину. Ако View увек има фиксну величину или match_parent, onMeasure се може прескочити.

Како анимирати Custom View?

Користите ValueAnimator или ObjectAnimator за промену својстава View-а (боја, радијус, позиција) и позивајте invalidate() у callback-у анимације за прецртавање. ValueAnimator ради на главној нити и синхронизован је са vsync-ом. За сложене анимације (физика, честице) користите Choreographer.FrameCallback или Android Animation Framework.

Како обрадити мултитач у Custom View-у?

У onTouchEvent-у користите MotionEvent.getPointerCount() за одређивање броја прстију и getPointerId(i) за праћење сваког прста по јединственом ID-у. ACTION_POINTER_DOWN / ACTION_POINTER_UP — догађаји додавања/уклањања прста. За скалирање користите ScaleGestureDetector у пару са onTouchEvent-ом — поједностављује детекцију pinch-to-zoom-а.

Може ли се Custom View користити у Jetpack Compose-у?

Да, кроз AndroidView — composable функцију која убацује View-based елемент у Compose хијерархију. AndroidView прима фабрику за креирање View-а и callback update за синхронизацију стања. Ово омогућава поновно коришћење постојећих Custom View-ова у новим Compose пројектима без потпуног преписивања.

Закључак

  • Custom View — прилагођена Android компонента, креирана наслеђивањем View-а са преоптерећењем onDraw, onMeasure и onTouchEvent за прилагођено цртање и обраду.
  • onDraw и Canvas API — основа цртања: геометријске фигуре, текст, Bitmap, путање — са конфигурацијом кроз Paint за боју, стил и фонт.
  • onMeasure је важан за коректан рад wrap_content-а; користи MeasureSpec (EXACTLY, AT_MOST, UNSPECIFIED) и resolveSize за израчунавање димензија.
  • Прилагођени атрибути кроз attrs.xml и TypedArray омогућавају подешавање Custom View-а из XML кода са подршком за аутоматско допуњавање у Android Studio.
  • onTouchEvent са GestureDetector-ом обезбеђује обраду додира, гестова и мултитач-а, а hit-testing одређује област интеракције.
  • Перформансе: креирајте објекте једном у конструктору, користите invalidate(Rect) за делимично прецртавање и LayerType за сложене ефекте.
  • Custom View се интегрише са Jetpack Compose-ом кроз AndroidView, што омогућава поновно коришћење готових компоненти у новим Compose пројектима.

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође