Custom View, Android’de View sınıfından veya alt sınıflarından (Button, TextView, ImageView) miras alınarak ve temel yöntemler geçersiz kılınarak oluşturulan özel bir arayüz bileşenidir: çizim için onDraw, boyutları ölçmek için onMeasure ve dokunmaları işlemek için onTouchEvent. Google Android Geliştirici Kılavuzu (2024)’ne göre, standart Android SDK bileşenleri gerekli davranışı veya görünümü sağlamadığında Custom View kullanılır — örneğin, özel animasyonlar, standart olmayan şekiller, uzmanlaşmış grafikler ve oyun öğeleri.
Anahtar Noktalar
Custom View, android.view.View’den (veya alt sınıflarından birinden) miras alan ve özel çizim, ölçüm ve olay işleme mantığını uygulamak için sistem yöntemlerini geçersiz kılan bir sınıftır. Custom View, benzersiz UI bileşenleri oluşturmak için Android’in temel bir mekanizmasıdır.
Android, Custom View oluşturmak için iki yaklaşım sunar: View’den miras alma (onDraw aracılığıyla tamamen özel çizim) ve mevcut bir View alt sınıfından miras alma (örneğin, temel davranışı korurken işlevsellik eklemek için Button veya TextView’i geçersiz kılma).
Temel yapıcı, Context ve AttributeSet’i kabul etmelidir — bu, sistemin XML işaretlemesinden View oluşturmasına olanak tanır. View yalnızca koddan kullanılacaksa, Context’li bir yapıcı yeterlidir. Stil içeren üçüncü bir yapıcı, Android tema desteği için gereklidir.
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)
}
}
Minimum Custom View bir yapıcı ve onDraw’dan oluşur. Ancak, Android Layout sisteminde doğru çalışması için onMeasure’nin de geçersiz kılınması gerekir — aksi takdirde View sıfır boyutlarda görünebilir veya wrap_content layout parametrelerine yanlış yanıt verebilir.
Google I/O 2023’e göre, Google Play ilk 100 uygulamasının %65’inde özel View’lar kullanılmaktadır. Custom View oluşturmanın en yaygın nedenleri: özel animasyonlar (%35), özel grafikler ve diyagramlar (%25), uzmanlaşmış kontrol öğeleri (%20) ve markalı bileşenler (%20).
onMeasure, View’ın alınan MeasureSpec’e (ebeveyn kısıtlamaları) göre istenen boyutlarını bildirdiği yöntemdir. Doğru bir onMeasure uygulaması olmadan, View wrap_content ile sıfır yüksekliğe sahip olabilir veya match_parent ile tüm ekran alanını kaplayabilir.
MeasureSpec bir mod (EXACTLY, AT_MOST, UNSPECIFIED) ve bir değerden oluşur. EXACTLY — ebeveyn tam bir boyut belirledi (match_parent veya sabit değer). AT_MOST — ebeveyn bir maksimum belirledi (wrap_content). UNSPECIFIED — kısıtlama yok (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 uygulamasını basitleştiren bir Android yardımcı yöntemidir. İstenen boyutu ve MeasureSpec’i alır ve doğru değeri döndürür: EXACTLY için — tam boyut, AT_MOST için — istenen ve maksimum arasındaki minimum, UNSPECIFIED için — istenen boyut.
onLayout View (ViewGroup değil) için genellikle geçersiz kılınmaz — alt öğeleri konumlandırmak için ebeveyn tarafından çağrılır. ViewGroup için onLayout zorunludur — her alt View için layout() çağrılmalıdır.
onDraw, Custom View’ın kalbidir. Bu yöntem, Android tarafından her View yeniden çiziminde çağrılır. Bir Canvas nesnesi iletilir ve tüm çizim (geometrik şekiller, metin, yollar, Bitmap ve animasyonlar) bu aracılıkla gerçekleştirilir.
Canvas API, tüm temel işlemler için yöntemler sağlar: drawCircle, drawRect, drawLine, drawPath, drawText, drawBitmap, drawArc, drawOval. Stili yapılandırmak için renk, kalınlık, doldurma stili, yazı tipi, gölgeler ve efektleri tanımlayan bir nesne olan Paint kullanılır.
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Arka plan
canvas.drawRect(0f, 0f,
width.toFloat(), height.toFloat(), bgPaint)
// Metin
canvas.drawText("Custom View",
paddingLeft.toFloat(),
paddingTop.toFloat() + textPaint.textSize,
textPaint)
// Daire
canvas.drawCircle(
width / 2f,
height / 2f,
radius, circlePaint)
// Yol (Bezier eğrisi)
val path = Path().apply {
moveTo(0f, height.toFloat())
quadTo(width / 2f, 0f,
width.toFloat(), height.toFloat())
}
canvas.drawPath(path, pathPaint)
}
Paint çizim stili yapılandırıcısıdır. Kenar yumuşatma (Paint.ANTI_ALIAS_FLAG) pürüzsüz kenarlar için zorunludur. Alt piksel metin (SUBPIXEL_TEXT_FLAG) metin kalitesini artırır. Style.FILL, STROKE ve FILL_AND_STROKE, şeklin doldurulup doldurulmayacağını, ana hatlarının çizilip çizilmeyeceğini veya her ikisini de belirler.
Önemli: onDraw içinde Paint, Path, Rect nesneleri oluşturmayın — bu, çizim döngüsünde ayırmalara neden olur ve çöp toplamayı tetikleyerek FPS’yi düşürür. Tüm çizim nesnelerini yapıcıda veya init yönteminde oluşturun. Animasyon için, yeniden çizimi tetiklemek üzere ValueAnimator veya ObjectAnimator’ı invalidate() ile kullanın.
Özel nitelikler, standart android:layout_width veya android:background gibi XML işaretlemesinden Custom View’ı yapılandırmaya olanak tanır. Nitelikler res/values/attrs.xml dosyasında ad, tür ve isteğe bağlı varsayılan değerle bildirilir.
Nitelik türleri: string, integer, float, boolean, color, dimension, enum, flag, fraction, reference (kaynak referansı). Her tür için Android, XML’den değeri otomatik olarak ayrıştırır ve TypedArray’e iletir.
<!-- 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 yapıcısında, nitelikler context.obtainStyledAttributes aracılığıyla okunur ve bir TypedArray döndürülür. TypedArray, türü belirtilmiş erişim yöntemleri sağlar: getColor, getDimension, getString, getBoolean, getInt. Okumadan sonra, kaynakları serbest bırakmak için TypedArray üzerinde 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’de kullanım: app ad alanını ekleyin (xmlns:app="http://schemas.android.com/apk/res-auto") ve app:circleColor="@color/red" gibi özel nitelikleri kullanın. attrs.xml içindeki bildirim doğruysa, Android Studio otomatik tamamlama yapar ve nitelik türlerini doğrular.
onTouchEvent, View’a her dokunulduğunda çağrılan yöntemdir. Olay türü (ACTION_DOWN, ACTION_MOVE, ACTION_UP, ACTION_CANCEL), koordinatlar, basınç ve parmak sayısı (çoklu dokunma) hakkında bilgi içeren bir MotionEvent nesnesi alır.
Karmaşık jestleri (kaydırma, sıkıştırma, uzun basma) işlemek için onTouchEvent ile birlikte GestureDetector veya ScaleGestureDetector kullanın. GestureDetector, onSingleTapUp, onFling, onLongPress, onDoubleTap ve diğer standart jestlerin tanınmasını basitleştirir.
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)
}
Hedef testi — bir dokunmanın View’ın belirli bir alanına isabet edip etmediğini belirleme. Dikdörtgen alanlar için Rect.contains(x, y) kullanın. Daireler için — merkezden uzaklık kontrolü: sqrt(dx^2 + dy^2) < radius. Rastgele şekiller için — Path.op() veya Region.contains().
Çoklu dokunma, MotionEvent.getPointerCount() ve getPointerId(index) aracılığıyla işlenir. Her parmak, ACTION_DOWN’dan ACTION_POINTER_UP’a kadar korunan benzersiz bir kimlik alır. ACTION_MOVE, tüm aktif parmakların verilerini içerebilir — hareket enterpolasyonu için getHistoricalX/Y kullanın.
Custom View, onDraw sık sık çağrılırsa (animasyon, kaydırma) veya ağır işlemler içeriyorsa performans darboğazı haline gelebilir. Android, özel bileşenlerin işlenmesini optimize etmek için çeşitli mekanizmalar sağlar.
View.setLayerType, View işlemesini bir yazılım katmanına (LAYER_TYPE_SOFTWARE) veya donanım katmanına (LAYER_TYPE_HARDWARE) geçirmeye olanak tanır. Yazılım katmanı, donanım hızlandırması tarafından desteklenmeyen karmaşık Canvas grafikleri için kullanışlıdır — örneğin, drawTextOnPath veya karmaşık Path efektleri.
invalidate(Rect), tüm bileşen yerine View’ın yalnızca belirtilen alanını yeniden çizer. Bu, her karenin tamamen yeniden çizilmesinin FPS düşüşlerine neden olduğu büyük Custom View’lar (grafikler, haritalar, çizim tuvalleri) için kritik derecede önemlidir. vsync senkronizasyonu için postInvalidateOnAnimation() kullanın.
class EfficientCustomView
@JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : View(context, attrs) {
// Yapıcıda bir kez oluşturulan nesneler
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ızca nokta etrafındaki alanı yeniden çiz
rect.set(
(x - 10).toInt(),
(y - 10).toInt(),
(x + 10).toInt(),
(y + 10).toInt()
)
invalidate(rect)
}
}
Donanım hızlandırması Android 3.0+ (API 11+) üzerinde varsayılan olarak etkindir. Canvas işlemleri drawCircle, drawRect, drawBitmap donanım hızlandırmalıdır ve GPU’da yürütülür. Ancak, drawTextOnPath, drawVertices, karmaşık kırpma işlemleri hızlandırılmaz — bunlar için LAYER_TYPE_SOFTWARE kullanın.
Android Performance Patterns (Google, 2023)’e göre, Custom View gecikmesinin ana nedenleri — onDraw’da ayırmalar (her karede nesne oluşturma), bir öğe değiştiğinde tüm View’ı yeniden çizme ve statik içerik için LayerType.HARDWARE eksikliği. Bu üç sorunu düzeltmek, çoğu senaryoda FPS’yi 30’dan 60’a çıkarır.
| Yöntem | Amaç | Çağrı Sıklığı |
|---|---|---|
| onDraw | View içeriğini çizme | Her invalidate()’de |
| onMeasure | View boyutlarını belirleme | Layout değişiminde |
| onLayout | Alt View’ları konumlandırma | onMeasure’den sonra |
| onTouchEvent | Dokunma işleme | Her dokunmada |
| onSizeChanged | Boyut değişikliğine tepki | İlk layout ve değişimde |
Sıkça Sorulan Sorular
Custom View (Canvas API), maksimum özel çizim performansı gerektiğinde (grafikler, video düzenleyiciler, haritalar), mevcut View tabanlı kodla entegrasyon veya API 21’in altındaki Android sürümlerinin desteği gerektiğinde haklıdır. Jetpack Compose, çoğu yeni proje için bildirimsel UI kullanan ve dinamik arayüzler için daha uygun olan modern bir yaklaşımdır.
Evet, View XML layout’ta wrap_content kullanıyorsa. onMeasure olmadan, wrap_content match_parent gibi davranır çünkü varsayılan View.onMeasure uygulaması varsayılan bir boyut belirlemez. View her zaman sabit bir boyuta veya match_parent’a sahipse, onMeasure geçersiz kılınmayabilir.
View özelliklerini (renk, yarıçap, konum) değiştirmek için ValueAnimator veya ObjectAnimator kullanın ve yeniden çizim için animasyon geri aramasında invalidate() çağırın. ValueAnimator ana iplikte çalışır ve vsync ile senkronizedir. Karmaşık animasyonlar (fizik, parçacıklar) için Choreographer.FrameCallback veya Android Animation Framework kullanın.
onTouchEvent içinde, parmak sayısını belirlemek için MotionEvent.getPointerCount() ve her parmağı benzersiz kimlikle izlemek için getPointerId(i) kullanın. ACTION_POINTER_DOWN / ACTION_POINTER_UP — parmak ekleme/kaldırma olayları. Sıkıştır-yakınlaştırma için onTouchEvent ile birlikte ScaleGestureDetector kullanın — sıkıştırmayı algılamayı basitleştirir.
Evet, AndroidView aracılığıyla — View tabanlı bir öğeyi Compose hiyerarşisine yerleştiren bir composable işlevi. AndroidView, View’ı oluşturmak için bir fabrika ve durum senkronizasyonu için bir update geri araması kabul eder. Bu, tamamen yeniden yazmadan mevcut Custom View’ların yeni Compose projelerinde yeniden kullanılmasına olanak tanır.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun