Android-də Custom View — rəsm çəkmə və hadisələrin işlənməsi

Müəllif: IT Sectr Dərc olunub: 2026-07-20 Oxuma vaxtı: 9 dəq

Custom View — bu, View sinfindən və ya onun alt siniflərindən (Button, TextView, ImageView) miras alınmaqla və əsas metodların override edilməsi ilə yaradılan fərdi interfeys komponentidir: rəsm çəkmək üçün onDraw, ölçüləri təyin etmək üçün onMeasure və toxunuşları idarə etmək üçün onTouchEvent. Google Android Developer Guide (2024) məlumatına görə, Custom View standart Android SDK komponentləri tələb olunan davranışı və ya görünüşü təmin etmədikdə istifadə olunur — məsələn, fərdi animasiyalar, qeyri-standart formalar, ixtisaslaşdırılmış qrafiklər və oyun elementləri üçün.

Əsas məqamlar

  • Custom View — onDraw, onMeasure, onTouchEvent-i override etməklə View və ya onun alt siniflərindən miras alınmaqla yaradılır.
  • Rəsm çəkmə Canvas API vasitəsilə həyata keçirilir — həndəsi fiqurlar, mətn, yollar, Bitmap və animasiyalar.
  • onMeasure layout parametrləri və padding nəzərə alınmaqla View-in düzgün ölçülərinə cavabdehdir.
  • Fərdi XML atributları res/values/attrs.xml faylında elan edilir və View konstruktorunda oxunur.
  • onTouchEvent ilə toxunuşların idarə edilməsi jestlərin, sürükləyib buraxmanın və multitouch-un dəstəklənməsinə imkan verir.

Android-də Custom View nədir?

Custom View — bu, android.view.View (və ya onun alt siniflərindən biri) sinfindən miras alınan və öz rəsm çəkmə, ölçmə və hadisələrin işlənməsi məntiqini həyata keçirmək üçün sistem metodlarını override edən sinifdir. Custom View unikal UI komponentləri yaratmaq üçün Android-in əsas mexanizmidir.

Android Custom View yaratmaq üçün iki yanaşma təqdim edir: View sinfindən miras alma (onDraw vasitəsilə tam fərdi rəsm çəkmə) və mövcud View alt sinfindən miras alma (məsələn, əsas davranışı qoruyaraq funksionallıq əlavə etmək üçün Button və ya TextView-i override etmə).

Əsas konstruktor Custom View Context və AttributeSet qəbul etməlidir — bu, sistemə XML kodundan View yaratmağa imkan verir. Əgər View yalnız kodda istifadə olunacaqsa, Context ilə konstruktor kifayətdir. Stil ilə üçüncü konstruktor Android temalarını dəstəkləmək üçün lazımdır.

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 Custom View konstruktor və onDraw-dan ibarətdir. Lakin Android Layout sistemində düzgün işləmək üçün onMeasure-i də override etmək lazımdır — əks halda View sıfır ölçülərlə göstərilə və ya wrap_content layout parametrlərinə düzgün reaksiya verməyə bilər.

Google I/O 2023 məlumatına görə, fərdi View-lər Google Play top 100 tətbiqinin 65%-də istifadə olunur. Custom View yaratmağın ən çox yayılmış səbəbləri: fərdi animasiyalar (35%), fərdi qrafiklər və diaqramlar (25%), ixtisaslaşdırılmış idarəetmə elementləri (20%) və brendləşdirilmiş komponentlər (20%).

onMeasure və onLayout: ölçülərin idarə edilməsi

onMeasure — View-in ötürülən MeasureSpec (valideyn məhdudiyyətləri) əsasında sistemə istədiyi ölçüləri bildirdiyi metoddur. onMeasure-in düzgün tətbiqi olmadan View wrap_content ilə sıfır hündürlüyə malik ola və ya match_parent ilə bütün ekran sahəsini tuta bilər.

MeasureSpec rejimdən (EXACTLY, AT_MOST, UNSPECIFIED) və dəyərdən ibarətdir. EXACTLY — valideyn dəqiq ölçü təyin edib (match_parent və ya sabit dəyər). AT_MOST — valideyn maksimumu təyin edib (wrap_content). UNSPECIFIED — məhdudiyyətsiz (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 — onMeasure-in tətbiqini sadələşdirən Android-in faydalı metodudur. İstədiyiniz ölçü və MeasureSpec qəbul edir və düzgün dəyəri qaytarır: EXACTLY üçün — dəqiq ölçü, AT_MOST üçün — istədiyiniz və maksimumdan minimumu, UNSPECIFIED üçün — istədiyiniz ölçü.

onLayout View (ViewGroup deyil) üçün adətən override edilmir — valideyn tərəfindən uşaq elementlərinin yerləşdirilməsi üçün çağırılır. ViewGroup üçün onLayout məcburidir — hər bir uşaq View üçün layout() çağırılmalıdır.

onDraw və Canvas API: rəsm çəkmə

onDraw — Custom View-in ürəyidir. Bu metod View hər dəfə yenidən çəkildikdə Android tərəfindən çağırılır. Ona Canvas obyekti ötürülür, onun vasitəsilə bütün rəsm çəkmə həyata keçirilir: həndəsi fiqurlar, mətn, yollar, Bitmap və animasiyalar.

Canvas API bütün əsas əməliyyatlar üçün metodlar təqdim edir: drawCircle, drawRect, drawLine, drawPath, drawText, drawBitmap, drawArc, drawOval. Stili konfiqurasiya etmək üçün Paint istifadə olunur — rəngi, qalınlığı, doldurma stilini, şrifti, kölgələri və effektləri təyin edən obyekt.

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

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

    // Mətn
    canvas.drawText("Xüsusi View",
        paddingLeft.toFloat(),
        paddingTop.toFloat() + textPaint.textSize,
        textPaint)

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

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

Paint — bu rəsm çəkmə stilinin konfiquratorudur. Anti-aliasing (Paint.ANTI_ALIAS_FLAG) hamar kənarlar üçün məcburidir. Subpixel text (SUBPIXEL_TEXT_FLAG) mətn keyfiyyətini yaxşılaşdırır. Style.FILL, STROKE və FILL_AND_STROKE fiqurun doldurulacağını, konturla çəkiləcəyini və ya hər ikisini təyin edir.

Vacib: onDraw-da Paint, Path, Rect obyektləri yaratmayın — bu, rəsm çəkmə dövrəsində alokasiyalara səbəb olur və garbage collection-u çağırır, bu da FPS-i aşağı salır. Bütün rəsm çəkmə obyektlərini konstruktorda və ya init metodunda yaradın. Animasiya üçün ValueAnimator və ya ObjectAnimator-dan invalidate() ilə birlikdə istifadə edin.

attrs.xml-də fərdi XML atributları

Fərdi atributlar Custom View-i standart android:layout_width və ya android:background kimi XML kodundan konfiqurasiya etməyə imkan verir. Atributlar res/values/attrs.xml faylında ad, tip və isteğe bağlı standart dəyər göstərilməklə elan edilir.

Atribut növləri: string, integer, float, boolean, color, dimension, enum, flag, fraction, reference (resursa istinad). Hər bir növ üçün Android avtomatik olaraq XML-dən dəyəri pars edir və onu TypedArray-ə ötürür.

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 konstruktorunda atributlar context.obtainStyledAttributes vasitəsilə oxunur, bu da TypedArray qaytarır. TypedArray tipli giriş metodları təqdim edir: getColor, getDimension, getString, getBoolean, getInt. Oxuduqdan sonra resursları boşaltmaq üçün TypedArray-də recycle() çağırılmalıdır.

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-də istifadə: app ad məkanını əlavə edin (xmlns:app="http://schemas.android.com/apk/res-auto") və fərdi atributlardan app:circleColor="@color/red" kimi istifadə edin. attrs.xml-də elan düzgündürsə, Android Studio atribut növlərini avtomatik tamamlayacaq və yoxlayacaq.

onTouchEvent ilə toxunuşların idarə edilməsi

onTouchEvent — View hər toxunuşda çağırılan metoddur. O, hadisə növü (ACTION_DOWN, ACTION_MOVE, ACTION_UP, ACTION_CANCEL), koordinatlar, təzyiq və barmaqların sayı (multitouch) haqqında məlumatı olan MotionEvent obyekti alır.

Mürəkkəb jestləri (sürüşdürmə, miqyaslama, uzun basma) idarə etmək üçün onTouchEvent ilə birlikdə GestureDetector və ya ScaleGestureDetector istifadə edin. GestureDetector onSingleTapUp, onFling, onLongPress, onDoubleTap və digər standart jestlərin tanınmasını asanlaşdırır.

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 — toxunuşun View-in müəyyən sahəsinə düşüb-düşmədiyini təyin etmək. Düzbucaqlı sahələr üçün Rect.contains(x, y) istifadə edin. Dairələr üçün — mərkəzdən məsafənin yoxlanılması: sqrt(dx^2 + dy^2) < radius. İxtiyari formalar üçün — Path.op() və ya Region.contains().

Multitouch MotionEvent.getPointerCount() və getPointerId(index) vasitəsilə idarə olunur. Hər barmaq ACTION_DOWN-dan ACTION_POINTER_UP-a qədər saxlanılan unikal ID alır. ACTION_MOVE bütün aktiv barmaqlar üzrə məlumatları ehtiva edə bilər — hərəkətin interpolyasiyası üçün getHistoricalX/Y istifadə edin.

Custom View performansının optimallaşdırılması

Custom View performansda dar boğaza çevrilə bilər, əgər onDraw tez-tez çağırılırsa (animasiya, scroll) və ya ağır əməliyyatlar ehtiva edirsə. Android fərdi komponentlərin rəsm çəkməsini optimallaşdırmaq üçün bir neçə mexanizm təqdim edir.

Mürəkkəb rəsm çəkmə üçün Layer type

View.setLayerType View-in renderini proqram (LAYER_TYPE_SOFTWARE) və ya aparat (LAYER_TYPE_HARDWARE) səviyyəsinə keçirməyə imkan verir. Software layer aparat sürətləndirilməsi ilə dəstəklənməyən mürəkkəb Canvas qrafikası üçün faydalıdır — məsələn, drawTextOnPath və ya mürəkkəb Path effektləri.

Yalnız lazımi sahənin invalidate edilməsi

invalidate(Rect) — bütün komponenti deyil, yalnız View-in göstərilən sahəsini yenidən çəkir. Bu, böyük Custom View-lər (qrafika, xəritələr, rəsm çəkmə canvases) üçün kritik əhəmiyyətlidir, burada hər kadrın tam yenidən çəkilməsi FPS-in düşməsinə səbəb olur. vsync ilə sinxronizasiya üçün postInvalidateOnAnimation() istifadə edin.

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

    // Obyektlər bir dəfə konstruktorda yaradılır
    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)
        // Yalnız nöqtə ətrafındakı sahəni yenidən çək
        rect.set(
            (x - 10).toInt(),
            (y - 10).toInt(),
            (x + 10).toInt(),
            (y + 10).toInt()
        )
        invalidate(rect)
    }
}

Aparat sürətləndirilməsi Android 3.0+ (API 11+) da standart olaraq aktivdir. drawCircle, drawRect, drawBitmap Canvas əməliyyatları aparat sürətləndirilir və GPU-da icra olunur. Lakin drawTextOnPath, drawVertices, mürəkkəb clipping əməliyyatları sürətləndirilmir — onlar üçün LAYER_TYPE_SOFTWARE istifadə edin.

Android Performance Patterns (Google, 2023) məlumatına görə, Custom View laglarının əsas səbəbləri onDraw-da alokasiyalar (hər kadrda obyekt yaratma), bir element dəyişdikdə bütün View-in yenidən çəkilməsi və statik məzmun üçün LayerType.HARDWARE-in olmamasıdır. Bu üç problemin həlli əksər ssenarilərdə FPS-in 30-dan 60-a yüksəlməsini təmin edir.

MetodTəyinatÇağırılma tezliyi
onDrawView məzmununun çəkilməsiHər invalidate() çağırışında
onMeasureView ölçülərinin təyiniLayout dəyişdikdə
onLayoutUşaq View-lərin yerləşdirilməsionMeasure-dən sonra
onTouchEventToxunuşların idarə edilməsiHər toxunuşda
onSizeChangedÖlçü dəyişikliyinə reaksiyaİlk layout və dəyişiklikdə

Tez-tez verilən suallar

Nə vaxt Custom View-dən Compose əvəzinə istifadə etməli?

Custom View (Canvas API) fərdi rəsm çəkmənin maksimum performansı lazım olduqda (qrafika, video redaktorları, xəritələr), mövcud View-based kodla inteqrasiya və ya API 21-dən aşağı Android versiyalarının dəstəklənməsi hallarında əsaslandırılır. Jetpack Compose əksər yeni layihələr üçün müasir yanaşmadır, deklarativ UI-dən istifadə edir və dinamik interfeyslər üçün daha uyğundur.

onMeasure-i override etmək məcburidir?

Bəli, əgər View XML kodunda wrap_content istifadə edirsə. onMeasure olmadan wrap_content match_parent kimi işləyəcək, çünki View.onMeasure-in standart tətbiqi defolt ölçü təyin etmir. Əgər View həmişə sabit ölçüyə və ya match_parent-ə malikdirsə, onMeasure-i override etmək lazım deyil.

Custom View-i necə animasiya etməli?

View-in xassələrini (rəng, radius, mövqe) dəyişmək üçün ValueAnimator və ya ObjectAnimator-dan istifadə edin və yenidən çəkmək üçün animasiya callback-də invalidate() çağırın. ValueAnimator əsas thread-də işləyir və vsync ilə sinxronizasiya olunur. Mürəkkəb animasiyalar (fizika, hissəciklər) üçün Choreographer.FrameCallback və ya Android Animation Framework-dən istifadə edin.

Custom View-də multitouch-u necə idarə etməli?

onTouchEvent-də barmaqların sayını təyin etmək üçün MotionEvent.getPointerCount() və hər barmağı unikal ID ilə izləmək üçün getPointerId(i) istifadə edin. ACTION_POINTER_DOWN / ACTION_POINTER_UP — barmağın əlavə edilməsi/silinməsi hadisələri. Miqyaslama üçün onTouchEvent ilə birlikdə ScaleGestureDetector istifadə edin — o, pinch-to-zoom aşkarlanmasını asanlaşdırır.

Jetpack Compose-da Custom View istifadə etmək olar?

Bəli, AndroidView vasitəsilə — View-based elementi Compose iyerarxiyasına daxil edən composable funksiyası. AndroidView View yaratmaq üçün fabrik və vəziyyəti sinxronlaşdırmaq üçün update callback qəbul edir. Bu, mövcud Custom View-ləri tam yenidən yazmadan yeni Compose layihələrində təkrar istifadə etməyə imkan verir.

Nəticə

  • Custom View — fərdi rəsm çəkmə və idarəetmə üçün onDraw, onMeasure və onTouchEvent-i override etməklə View-dən miras alınmaqla yaradılan fərdi Android komponenti.
  • onDraw və Canvas API — rəsm çəkmənin əsası: rəng, stil və şrift üçün Paint vasitəsilə konfiqurasiya ilə həndəsi fiqurlar, mətn, Bitmap, yollar.
  • onMeasure wrap_content-in düzgün işləməsi üçün vacibdir; ölçüləri hesablamaq üçün MeasureSpec (EXACTLY, AT_MOST, UNSPECIFIED) və resolveSize istifadə edir.
  • Fərdi atributlar attrs.xml və TypedArray vasitəsilə Android Studio-da avtomatik tamamlama dəstəyi ilə Custom View-i XML kodundan konfiqurasiya etməyə imkan verir.
  • onTouchEvent GestureDetector ilə toxunuşların, jestlərin və multitouch-un idarə edilməsini, hit-testing isə qarşılıqlı təsir sahəsini təyin edir.
  • Performans: obyektləri bir dəfə konstruktorda yaradın, qismən yenidən çəkmək üçün invalidate(Rect) və mürəkkəb effektlər üçün LayerType istifadə edin.
  • Custom View inteqrasiya olunur Jetpack Compose ilə AndroidView vasitəsilə, bu da hazır komponentləri yeni Compose layihələrində təkrar istifadə etməyə imkan verir.

Açar təslim mobil tətbiq hazırlayacağıq

IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.

Layihəni müzakirə et

Həm də oxuyun