Custom View — bu View sinfidan yoki uning kichik sinflaridan (Button, TextView, ImageView) meros olish va asosiy metodlarni override qilish orqali yaratilgan foydalanuvchi interfeysining maxsus komponentidir: chizish uchun onDraw, o'lchamlarni o'lchash uchun onMeasure va teginishlarni qayta ishlash uchun onTouchEvent. Google Android Developer Guide (2024) ma'lumotiga ko'ra, Custom View standart Android SDK komponentlari kerakli xatti-harakatni yoki tashqi ko'rinishni ta'minlamaganida ishlatiladi — masalan, maxsus animatsiyalar, nostandart shakllar, ixtisoslashtirilgan grafiklar va o'yin elementlari uchun.
Asosiy fikrlar
Custom View — bu android.view.View (yoki uning kichik sinflaridan biri) sinfidan meros oladigan va o'zining chizish, o'lchash va hodisalarni qayta ishlash mantiqini amalga oshirish uchun tizim metodlarini override qiladigan sinfdir. Custom View noyob UI komponentlarini yaratish uchun Androidning asosiy mexanizmidir.
Android Custom View yaratish uchun ikkita yondashuvni taqdim etadi: View sinfidan meros olish (onDraw orqali to'liq maxsus chizish) va mavjud View kichik sinfidan meros olish (masalan, asosiy xatti-harakatni saqlab qolgan holda funksionallik qo'shish uchun Button yoki TextView-ni override qilish).
Asosiy konstruktor Custom View Context va AttributeSet-ni qabul qilishi kerak — bu tizimga XML kodidan View yaratishga imkon beradi. Agar View faqat kodda ishlatilsa, Context bilan konstruktor yetarli. Uchinchi konstruktor uslub bilan Android mavzularini qo'llab-quvvatlash uchun kerak.
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 va onDraw-dan iborat. Biroq, Android Layout tizimida to'g'ri ishlash uchun onMeasure-ni ham override qilish kerak — aks holda View nol o'lchamlar bilan ko'rsatilishi yoki wrap_content layout parametrlariga noto'g'ri munosabat bildirishi mumkin.
Google I/O 2023 ma'lumotiga ko'ra, maxsus View-lar Google Play top 100 ilovalarining 65 foizida ishlatiladi. Custom View yaratishning eng keng tarqalgan sabablari: maxsus animatsiyalar (35%), maxsus grafiklar va diagrammalar (25%), ixtisoslashtirilgan boshqaruv elementlari (20%) va brendlashtirilgan komponentlar (20%).
onMeasure — bu View o'zining istalgan o'lchamlarini MeasureSpec (ota-ona cheklovlari) asosida tizimga xabar qiladigan metoddir. onMeasure-ni to'g'ri amalga oshirmasdan, View wrap_content bilan nol balandlikka ega bo'lishi yoki match_parent bilan butun ekran maydonini egallashi mumkin.
MeasureSpec rejimdan (EXACTLY, AT_MOST, UNSPECIFIED) va qiymatdan iborat. EXACTLY — ota-ona aniq o'lchamni belgilagan (match_parent yoki sobit qiymat). AT_MOST — ota-ona maksimalni belgilagan (wrap_content). UNSPECIFIED — cheklovlarsiz (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 — bu onMeasure-ni amalga oshirishni soddalashtiradigan Androidning foydali metodidir. Istalgan o'lcham va MeasureSpec-ni qabul qiladi va to'g'ri qiymatni qaytaradi: EXACTLY uchun — aniq o'lcham, AT_MOST uchun — istalgan va maksimaldan kichigi, UNSPECIFIED uchun — istalgan o'lcham.
onLayout View (ViewGroup emas) uchun odatda override qilinmaydi — ota-ona tomonidan bola elementlarini joylashtirish uchun chaqiriladi. ViewGroup uchun onLayout majburiydir — unda har bir bola View uchun layout() chaqirilishi kerak.
onDraw — Custom View-ning yuragi. Bu metod Android tomonidan View har safar qayta chizilganda chaqiriladi. Unga Canvas obyekti uzatiladi, uning orqali barcha chizish amalga oshiriladi: geometrik shakllar, matn, yo'llar, Bitmap va animatsiyalar.
Canvas API barcha asosiy operatsiyalar uchun metodlarni taqdim etadi: drawCircle, drawRect, drawLine, drawPath, drawText, drawBitmap, drawArc, drawOval. Uslubni sozlash uchun Paint ishlatiladi — rang, qalinlik, to'ldirish uslubi, shrift, soyalar va effektlarni belgilaydigan obyekt.
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Fon
canvas.drawRect(0f, 0f,
width.toFloat(), height.toFloat(), bgPaint)
// Matn
canvas.drawText("Maxsus View",
paddingLeft.toFloat(),
paddingTop.toFloat() + textPaint.textSize,
textPaint)
// Doira
canvas.drawCircle(
width / 2f,
height / 2f,
radius, circlePaint)
// Yo'l (Bezier egri chizig'i)
val path = Path().apply {
moveTo(0f, height.toFloat())
quadTo(width / 2f, 0f,
width.toFloat(), height.toFloat())
}
canvas.drawPath(path, pathPaint)
}
Paint — bu chizish uslubining konfiguratoridir. Anti-aliasing (Paint.ANTI_ALIAS_FLAG) silliq qirralar uchun majburiydir. Subpixel text (SUBPIXEL_TEXT_FLAG) matn sifatini yaxshilaydi. Style.FILL, STROKE va FILL_AND_STROKE shaklning to'ldirilishini, kontur bilan chizilishini yoki ikkalasini ham belgilaydi.
Muhim: onDraw-da Paint, Path, Rect obyektlarini yaratmang — bu chizish siklida alokatsiyalarga sabab bo'ladi va garbage collection-ni chaqiradi, bu esa FPS-ni pasaytiradi. Barcha chizish obyektlarini konstruktorda yoki init metodida yarating. Animatsiya uchun ValueAnimator yoki ObjectAnimator-dan invalidate() bilan birgalikda foydalaning.
Maxsus atributlar Custom View-ni standart android:layout_width yoki android:background kabi XML kodidan sozlash imkonini beradi. Atributlar res/values/attrs.xml faylida nom, tur va ixtiyoriy standart qiymat bilan e'lon qilinadi.
Atribut turlari: string, integer, float, boolean, color, dimension, enum, flag, fraction, reference (resursga havola). Har bir tur uchun Android avtomatik ravishda XML dan qiymatni tahlil qiladi va uni TypedArray-ga uzatadi.
<!-- 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 konstruktorida atributlar context.obtainStyledAttributes orqali o'qiladi, u TypedArray qaytaradi. TypedArray tipli kirish metodlarini taqdim etadi: getColor, getDimension, getString, getBoolean, getInt. O'qigandan so'ng, resurslarni bo'shatish uchun TypedArray-da recycle() chaqirilishi kerak.
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 da foydalanish: app nomlar maydonini qo'shing (xmlns:app="http://schemas.android.com/apk/res-auto") va maxsus atributlardan app:circleColor="@color/red" kabi foydalaning. attrs.xml da e'lon to'g'ri bo'lsa, Android Studio atribut turlarini avtomatik to'ldiradi va tekshiradi.
onTouchEvent — View-ning har bir teginishida chaqiriladigan metod. U hodisa turi (ACTION_DOWN, ACTION_MOVE, ACTION_UP, ACTION_CANCEL), koordinatalar, bosim va barmoqlar soni (multitouch) haqida ma'lumotga ega MotionEvent obyektini oladi.
Murakkab imo-ishoralarni (surish, masshtablash, uzoq bosish) qayta ishlash uchun onTouchEvent bilan birgalikda GestureDetector yoki ScaleGestureDetector dan foydalaning. GestureDetector onSingleTapUp, onFling, onLongPress, onDoubleTap va boshqa standart imo-ishoralarni tanib olishni soddalashtiradi.
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 — teginish View-ning ma'lum bir maydoniga tushganligini aniqlash. To'rtburchak maydonlar uchun Rect.contains(x, y) dan foydalaning. Doiralar uchun — markazdan masofani tekshirish: sqrt(dx^2 + dy^2) < radius. Ixtiyoriy shakllar uchun — Path.op() yoki Region.contains().
Multitouch MotionEvent.getPointerCount() va getPointerId(index) orqali qayta ishlanadi. Har bir barmoq ACTION_DOWN dan ACTION_POINTER_UP gacha saqlanadigan noyob ID oladi. ACTION_MOVE barcha faol barmoqlar bo'yicha ma'lumotlarni o'z ichiga olishi mumkin — harakatni interpolyatsiya qilish uchun getHistoricalX/Y dan foydalaning.
Custom View samaradorlikning tor bo'yniga aylanishi mumkin, agar onDraw tez-tez chaqirilsa (animatsiya, scroll) yoki og'ir operatsiyalarni o'z ichiga olsa. Android maxsus komponentlarning chizilishini optimallashtirish uchun bir nechta mexanizmlarni taqdim etadi.
View.setLayerType View renderini dasturiy (LAYER_TYPE_SOFTWARE) yoki apparat (LAYER_TYPE_HARDWARE) qatlamiga o'tkazish imkonini beradi. Software layer apparat tezlashishi tomonidan qo'llab-quvvatlanmaydigan murakkab Canvas grafikasi uchun foydalidir — masalan, drawTextOnPath yoki murakkab Path effektlari.
invalidate(Rect) — butun komponentni emas, faqat View-ning ko'rsatilgan maydonini qayta chizadi. Bu katta Custom View-lar (grafikalar, xaritalar, chizish canvases) uchun juda muhim, bu erda har bir kadrni to'liq qayta chizish FPS ning pasayishiga sabab bo'ladi. vsync bilan sinxronlash uchun postInvalidateOnAnimation() dan foydalaning.
class EfficientCustomView
@JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : View(context, attrs) {
// Obyektlar bir marta konstruktorda yaratiladi
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)
// Faqat nuqta atrofidagi maydonni qayta chiz
rect.set(
(x - 10).toInt(),
(y - 10).toInt(),
(x + 10).toInt(),
(y + 10).toInt()
)
invalidate(rect)
}
}
Apparat tezlashishi Android 3.0+ (API 11+) da sukut bo'yicha yoqilgan. drawCircle, drawRect, drawBitmap Canvas operatsiyalari apparat tezlashadi va GPU da bajariladi. Biroq, drawTextOnPath, drawVertices, murakkab clipping operatsiyalari tezlashtirilmaydi — ular uchun LAYER_TYPE_SOFTWARE dan foydalaning.
Android Performance Patterns (Google, 2023) ma'lumotiga ko'ra, Custom View laglarining asosiy sabablari onDraw da alokatsiyalar (har bir kadrda obyekt yaratish), bir element o'zgarganda butun View ni qayta chizish va statik tarkib uchun LayerType.HARDWARE ning yo'qligi. Ushbu uchta muammoni tuzatish ko'p stsenariylarda FPS ni 30 dan 60 gacha oshirishni beradi.
| Metod | Vazifa | Chaqirish chastotasi |
|---|---|---|
| onDraw | View tarkibini chizish | Har bir invalidate() da |
| onMeasure | View o'lchamlarini aniqlash | Layout o'zgarganda |
| onLayout | Bola View-larni joylashtirish | onMeasure dan keyin |
| onTouchEvent | Teginishlarni qayta ishlash | Har bir teginishda |
| onSizeChanged | O'lcham o'zgarishiga reaksiya | Birinchi layout va o'zgarishda |
Tez-tez beriladigan savollar
Custom View (Canvas API) maksimal maxsus chizish samaradorligi (grafikalar, video muharrirlari, xaritalar), mavjud View-based kod bilan integratsiya yoki API 21 dan past Android versiyalarini qo'llab-quvvatlash zarur bo'lganda oqlanadi. Jetpack Compose ko'pgina yangi loyihalar uchun zamonaviy yondashuv bo'lib, deklarativ UI dan foydalanadi va dinamik interfeyslar uchun ko'proq mos keladi.
Ha, agar View XML kodida wrap_content dan foydalansa. onMeasure bo'lmasa, wrap_content match_parent kabi ishlaydi, chunki View.onMeasure ning standart amalga oshirilishi standart o'lchamni o'rnatmaydi. Agar View har doim sobit o'lchamga yoki match_parent ga ega bo'lsa, onMeasure ni o'tkazib yuborish mumkin.
View xususiyatlarini (rang, radius, joylashuv) o'zgartirish uchun ValueAnimator yoki ObjectAnimator dan foydalaning va qayta chizish uchun animatsiya callback ida invalidate() ni chaqiring. ValueAnimator asosiy oqimda ishlaydi va vsync bilan sinxronlanadi. Murakkab animatsiyalar (fizika, zarralar) uchun Choreographer.FrameCallback yoki Android Animation Framework dan foydalaning.
onTouchEvent da barmoqlar sonini aniqlash uchun MotionEvent.getPointerCount() va har bir barmoqni noyob ID bo'yicha kuzatish uchun getPointerId(i) dan foydalaning. ACTION_POINTER_DOWN / ACTION_POINTER_UP — barmoq qo'shilishi/olib tashlanishi hodisalari. Masshtablash uchun onTouchEvent bilan birga ScaleGestureDetector dan foydalaning — u pinch-to-zoom ni aniqlashni soddalashtiradi.
Ha, AndroidView orqali — View-based elementni Compose iyerarxiyasiga kiritadigan composable funksiya. AndroidView View yaratish uchun fabrika va holatni sinxronlash uchun update callback ni qabul qiladi. Bu mavjud Custom View larni to'liq qayta yozmasdan yangi Compose loyihalarida qayta ishlatish imkonini beradi.
Xulosa
Biz kalit topshirig'i bilan mobil ilovani ishlab chiqamiz
IT Sectr 2017-yildan beri startaplar va korxonalar uchun iOS va Android ilovalarini yaratadi. Biz sizga maslahat beramiz va eng yaxshi yechimni taklif qilamiz.
Shuningdek o'qing