Android’de Custom View — özü, çizim ve olay işleme

Yazar: IT Sectr Yayınlanma: 2026-07-20 Okuma süresi: 9 dk

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 — View veya alt sınıflarından miras alınarak onDraw, onMeasure, onTouchEvent geçersiz kılınarak oluşturulur.
  • Çizim Canvas API aracılığıyla gerçekleştirilir — geometrik şekiller, metin, yollar, Bitmap ve animasyon.
  • onMeasure, layout parametrelerini ve padding’i dikkate alarak View’ın doğru boyutlarını yönetir.
  • Özel XML nitelikleri res/values/attrs.xml içinde bildirilir ve View yapıcısında okunur.
  • onTouchEvent aracılığıyla dokunma işleme, jest desteği, sürükle-bırak ve çoklu dokunma eklenmesine olanak tanır.

Android’de Custom View Nedir?

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.

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

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 ve onLayout: Boyut Yönetimi

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

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 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 ve Canvas API: Çizim

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.

kotlin
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.

attrs.xml’de Özel XML Nitelikleri

Ö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.

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 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.

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’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 ile Dokunma İşleme

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.

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

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 Performans Optimizasyonu

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.

Karmaşık çizim için katman türü

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.

Yalnızca gerekli alanı geçersiz kılma

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.

kotlin
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öntemAmaçÇağrı Sıklığı
onDrawView içeriğini çizmeHer invalidate()’de
onMeasureView boyutlarını belirlemeLayout değişiminde
onLayoutAlt View’ları konumlandırmaonMeasure’den sonra
onTouchEventDokunma işlemeHer dokunmada
onSizeChangedBoyut değişikliğine tepkiİlk layout ve değişimde

Sıkça Sorulan Sorular

Compose yerine ne zaman Custom View kullanmalı?

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.

onMeasure’yi geçersiz kılmak zorunlu mu?

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.

Custom View nasıl canlandırılır?

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.

Custom View’da çoklu dokunma nasıl işlenir?

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.

Custom View Jetpack Compose’da kullanılabilir mi?

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

  • Custom View — özel çizim ve işleme için onDraw, onMeasure ve onTouchEvent’i geçersiz kılarak View’dan miras alınarak oluşturulan özel bir Android bileşeni.
  • onDraw ve Canvas API — çizimin temeli: geometrik şekiller, metin, Bitmap, yollar — renk, stil ve yazı tipi için Paint aracılığıyla yapılandırılır.
  • onMeasure doğru wrap_content davranışı için önemlidir; boyutları hesaplamak için MeasureSpec (EXACTLY, AT_MOST, UNSPECIFIED) ve resolveSize kullanır.
  • Özel nitelikler, attrs.xml ve TypedArray aracılığıyla Android Studio otomatik tamamlama desteğiyle XML işaretlemesinden Custom View yapılandırmasına olanak tanır.
  • onTouchEvent GestureDetector ile dokunma, jest ve çoklu dokunma işleme sağlarken, hedef testi etkileşim alanını belirler.
  • Performans: nesneleri yapıcıda bir kez oluşturun, kısmi yeniden çizim için invalidate(Rect) ve karmaşık efektler için LayerType kullanın.
  • Custom View, AndroidView aracılığıyla Jetpack Compose ile entegre olur ve yeni Compose projelerinde hazır bileşenlerin yeniden kullanılmasına olanak tanır.

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.

Projeyi tartış

Ayrıca okuyun