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 — 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.
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 — 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).
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 — 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.
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.
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.
<!-- 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.
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 — 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.
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 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.
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.
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.
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.
| Metod | Təyinat | Çağırılma tezliyi |
|---|---|---|
| onDraw | View məzmununun çəkilməsi | Hər invalidate() çağırışında |
| onMeasure | View ölçülərinin təyini | Layout dəyişdikdə |
| onLayout | Uşaq View-lərin yerləşdirilməsi | onMeasure-dən sonra |
| onTouchEvent | Toxunuşların idarə edilməsi | Hər toxunuşda |
| onSizeChanged | Ölçü dəyişikliyinə reaksiya | İlk layout və dəyişiklikdə |
Tez-tez verilən suallar
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.
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.
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.
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.
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ə
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.
Həm də oxuyun