Özel çizim oluşturma

"Oluştur" yöntemini deneyin
Jetpack Compose, Android için önerilen kullanıcı arayüzü araç setidir. Compose'da düzenlerle nasıl çalışacağınızı öğrenin.
Compose'da Tuval → 'nı inceleyin.

Özel görünümlerin en önemli kısmı görünümüdür. Özel çizim uygulamanızın ihtiyaçlarına göre kolay veya karmaşık olabilir. Bu doküman en yaygın işlemlerden bazılarını ele almaktadır.

Daha fazla bilgi için bkz. Çekilebilir öğelere genel bakış

onDraw() öğesini geçersiz kıl

Özel bir görünüm çizmenin en önemli adımı, onDraw(). yöntemidir. onDraw() parametresi Canvas görünümün kendini çizmek için kullanabileceği nesnedir. Canvas sınıfı metin, çizgiler, bit eşlemler ve daha birçok grafik çizme yöntemlerini tanımlar ilkel maddelerdir. onDraw() uygulamasında bu yöntemleri kullanarak özel kullanıcı arayüzü.

Öncelikle Paint nesne algılandı. Sonraki bölümde Paint daha ayrıntılı olarak ele alınmaktadır.

Çizim nesneleri oluşturma

İlgili içeriği oluşturmak için kullanılan android.graphics. çerçeve, çizimi iki alana ayırır:

  • Ne çizilecek? Canvas tarafından ele alınır.
  • Çizim, Paint tarafından işlenir.

Örneğin, Canvas çizgi çizmek için bir yöntem sağlar ve Paint, çizginin rengini tanımlamak için kullanılan yöntemler sunar. Canvas, dikdörtgen çizmek için bir yönteme sahip ve Paint o dikdörtgenin bir renkle doldurulup doldurulmayacağını belirler. Canvas, ekranda çizebileceğiniz şekilleri tanımlar ve Paint, her şeklin rengini, stilini, yazı tipini ve diğer özellikleri tanımlar anlamına gelir.

Herhangi bir şey çizmeden önce bir veya daha fazla Paint nesnesi oluşturun. İlgili içeriği oluşturmak için kullanılan aşağıdaki örnekte bunu init adlı bir yöntemde yapılmaktadır. Bu yöntem kurucusu tarafından çağrılmakla birlikte, satır içinde başlatılabilir. Kotlin.

Kotlin

@ColorInt
private var textColor    // Obtained from style attributes.

@Dimension
private var textHeight   // Obtained from style attributes.

private val textPaint = Paint(ANTI_ALIAS_FLAG).apply {
    color = textColor
    if (textHeight == 0f) {
        textHeight = textSize
    } else {
        textSize = textHeight
    }
}

private val piePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
    style = Paint.Style.FILL
    textSize = textHeight
}

private val shadowPaint = Paint(0).apply {
    color = 0x101010
    maskFilter = BlurMaskFilter(8f, BlurMaskFilter.Blur.NORMAL)
}

Java

private Paint textPaint;
private Paint piePaint;
private Paint shadowPaint;

@ColorInt
private int textColor;       // Obtained from style attributes.

@Dimension
private float textHeight;    // Obtained from style attributes.

private void init() {
   textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
   textPaint.setColor(textColor);
   if (textHeight == 0) {
       textHeight = textPaint.getTextSize();
   } else {
       textPaint.setTextSize(textHeight);
   }

   piePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
   piePaint.setStyle(Paint.Style.FILL);
   piePaint.setTextSize(textHeight);

   shadowPaint = new Paint(0);
   shadowPaint.setColor(0xff101010);
   shadowPaint.setMaskFilter(new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL));
   ...
}

Nesneleri önceden oluşturmak önemli bir optimizasyondur. Görüntüleme sayısı tekrar çizilir ve birçok çizim nesnesi pahalı başlatma işlemi gerektirir. onDraw() yönteminizde önemli ölçüde çizim nesneleri oluşturma performansı düşürür ve kullanıcı arayüzünüzü yavaşlatabilir.

Düzen etkinliklerini işleme

Özel görünümünüzü doğru şekilde çizmek için görünümünün ne olduğunu bulun. Karmaşık özel görünümlerin boyutuna bağlı olarak, genellikle birden çok düzen hesaplaması yapması gerekir alanı şekline karar verebilir. Çıkarınızın boyutu hakkında varsayımlarda bulunmayın. ekranda görüntüleyin. Görünümünüzü sadece tek bir uygulama kullansa bile Farklı ekran boyutlarını, birden fazla ekran yoğunluğunu ve çeşitli en boy oranlarını ele almak hem dikey hem de yatay modda çalışır.

View olmasına rağmen ölçüm için birçok yöntem sunar. Ancak bunların çoğu, geçersiz kılınır. Görünümünüz, boyutu üzerinde özel bir denetime ihtiyaç duymuyorsa yöntemi geçersiz kıl: onSizeChanged()

onSizeChanged(), görünümünüze ilk kez bir herhangi bir nedenle görünümünüzün boyutu değişirse boyutu da görebilirsiniz. Hesaplama konumları, boyutları ve görüntülemenizin boyutuyla ilgili diğer tüm değerleri onSizeChanged() kullanarak her çiziminizde yeniden hesaplamanız gerekmez. Aşağıdaki örnekte onSizeChanged(), görünümün grafiğin sınırlayıcı dikdörtgenini ve metin etiketi ve diğer görsel öğelerden oluşmalıdır.

Görünümünüze bir boyut atandığında, düzen yöneticisi bu boyutun görünümün dolgusunu içerir. görünümünün boyutudur. onSizeChanged() sitesinde bu programın nasıl çalıştığını gösteren bir snippet Bunun için:

Kotlin

private val showText    // Obtained from styled attributes.
private val textWidth   // Obtained from styled attributes.

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
    super.onSizeChanged(w, h, oldw, oldh)
    // Account for padding.
    var xpad = (paddingLeft + paddingRight).toFloat()
    val ypad = (paddingTop + paddingBottom).toFloat()

    // Account for the label.
    if (showText) xpad += textWidth.toFloat()
    val ww = w.toFloat() - xpad
    val hh = h.toFloat() - ypad

    // Figure out how big you can make the pie.
    val diameter = Math.min(ww, hh)
}

Java

private Boolean showText;    // Obtained from styled attributes.
private int textWidth;       // Obtained from styled attributes.

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    // Account for padding.
    float xpad = (float)(getPaddingLeft() + getPaddingRight());
    float ypad = (float)(getPaddingTop() + getPaddingBottom());

    // Account for the label.
    if (showText) xpad += textWidth;

    float ww = (float)w - xpad;
    float hh = (float)h - ypad;

    // Figure out how big you can make the pie.
    float diameter = Math.min(ww, hh);
}

Görünümünüzün düzen parametreleri üzerinde daha ayrıntılı denetime ihtiyacınız varsa onMeasure() Bu yöntemin parametreleri View.MeasureSpec. ebeveynlerinin görünümünüzün ne kadar büyük olmasını istediğini belirten sabit bir maksimum değer mi yoksa sadece bir öneri mi olduğunu kontrol edebilirsiniz. Optimizasyon olarak, bu değerler, paketlenmiş tam sayılar olarak depolanır ve statik yöntemleri kullanarak View.MeasureSpec tuşlarına basın.

Aşağıda, onMeasure() uygulamasının bir örneği verilmiştir. Burada alanını, grafiği olabildiğince büyük hale getirecek kadar büyük kullanın:

Kotlin

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    // Try for a width based on your minimum.
    val minw: Int = paddingLeft + paddingRight + suggestedMinimumWidth
    val w: Int = View.resolveSizeAndState(minw, widthMeasureSpec, 1)

    // Whatever the width is, ask for a height that lets the pie get as big as
    // it can.
    val minh: Int = View.MeasureSpec.getSize(w) - textWidth.toInt() + paddingBottom + paddingTop
    val h: Int = View.resolveSizeAndState(minh, heightMeasureSpec, 0)

    setMeasuredDimension(w, h)
}

Java

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   // Try for a width based on your minimum.
   int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
   int w = resolveSizeAndState(minw, widthMeasureSpec, 1);

   // Whatever the width is, ask for a height that lets the pie get as big as it
   // can.
   int minh = MeasureSpec.getSize(w) - (int)textWidth + getPaddingBottom() + getPaddingTop();
   int h = resolveSizeAndState(minh, heightMeasureSpec, 0);

   setMeasuredDimension(w, h);
}

Bu kodda dikkat edilmesi gereken üç önemli nokta vardır:

  • Hesaplamalar, görünümün dolgusunu dikkate alır. Bahsedildiği gibi görünümün sorumluluğundadır.
  • Yardımcı yöntem resolveSizeAndState(). değeri, nihai genişlik ve yükseklik değerlerini oluşturmak için kullanılır. Bu yardımcı geri döndü uygun bir View.MeasureSpec değeri belirlemek için görünümünün gerekli boyutunu onMeasure() içine aktarılan değere uygular.
  • onMeasure() için döndürülen değer yok. Bunun yerine sonuçlarını müşteriye telefon ederek setMeasuredDimension() Bu yöntemin çağrılması zorunludur. Bu çağrıyı çıkarırsanız View sınıfı, çalışma zamanı istisnası bildiriyor.

Çiz

Nesne oluşturma ve ölçüm kodunuzu tanımladıktan sonra, onDraw() Her görünüm onDraw() uygulamasını farklı şekilde uygular, ancak çoğu görüntülemenin ortak olduğu bazı yaygın işlemler vardır:

  • Şunu kullanarak metin çizin: drawText() Çağrı yaparak yazı karakterini belirtme setTypeface(). ve metin rengini çağırarak setColor().
  • İlkel şekilleri çizmek için: drawRect(), drawOval(), ve drawArc(). Şekillerin dolgulu, dış çizgili veya her ikisini birden çağırarak şekillerin durumunu değiştirebilirsiniz: setStyle()
  • Boşlukları kullanarak daha karmaşık şekiller çizin Path. sınıfını kullanır. Path öğesine çizgi ve eğri ekleyerek bir şekli tanımlayın ardından şekli drawPath(). İlkel şekillerde olduğu gibi, yollar dış çizgilerle doldurulabilir, doldurulabilir veya her ikisi birden hizmet: setStyle()
  • Gradyan dolgularını LinearGradient oluşturarak tanımlayın nesneler'i tıklayın. Telefonla arama setShader(). LinearGradient efektinizi dolgulu şekillerde kullanın.
  • Şununla bit eşlemler çizin: drawBitmap()

Aşağıdaki kod metin, çizgi ve şekillerin bir karışımını çizer:

Kotlin

private val data = mutableListOf<Item>() // A list of items that are displayed.

private var shadowBounds = RectF()       // Calculated in onSizeChanged.
private var pointerRadius: Float = 2f    // Obtained from styled attributes.
private var pointerX: Float = 0f         // Calculated in onSizeChanged.
private var pointerY: Float = 0f         // Calculated in onSizeChanged.
private var textX: Float = 0f            // Calculated in onSizeChanged.
private var textY: Float = 0f            // Calculated in onSizeChanged.
private var bounds = RectF()             // Calculated in onSizeChanged.
private var currentItem: Int = 0         // The index of the currently selected item.

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

    canvas.apply {
        // Draw the shadow.
        drawOval(shadowBounds, shadowPaint)

        // Draw the label text.
        drawText(data[currentItem].label, textX, textY, textPaint)

        // Draw the pie slices.
        data.forEach {item ->
            piePaint.shader = item.shader
            drawArc(
                bounds,
                360 - item.endAngle,
                item.endAngle - item.startAngle,
                true,
                piePaint
            )
        }

        // Draw the pointer.
        drawLine(textX, pointerY, pointerX, pointerY, textPaint)
        drawCircle(pointerX, pointerY, pointerRadius, textPaint)
    }
}

// Maintains the state for a data item.
private data class Item(
    var label: String,      
    var value: Float = 0f,

    @ColorInt
    var color: Int = 0,

    // Computed values.
    var startAngle: Float = 0f,
    var endAngle: Float = 0f,

    var shader: Shader
)

Java

private List<Item> data = new ArrayList<Item>();  // A list of items that are displayed.

private RectF shadowBounds;                       // Calculated in onSizeChanged.
private float pointerRadius;                      // Obtained from styled attributes.
private float pointerX;                           // Calculated in onSizeChanged.
private float pointerY;                           // Calculated in onSizeChanged.
private float textX;                              // Calculated in onSizeChanged.
private float textY;                              // Calculated in onSizeChanged.
private RectF bounds;                             // Calculated in onSizeChanged.
private int currentItem = 0;                      // The index of the currently selected item.

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    // Draw the shadow.
    canvas.drawOval(
            shadowBounds,
            shadowPaint
    );

    // Draw the label text.
    canvas.drawText(data.get(currentItem).label, textX, textY, textPaint);

    // Draw the pie slices.
    for (int i = 0; i < data.size(); ++i) {
        Item it = data.get(i);
        piePaint.setShader(it.shader);
        canvas.drawArc(
                bounds,
                360 - it.endAngle,
                it.endAngle - it.startAngle,
                true, 
                piePaint
        );
    }

    // Draw the pointer.
    canvas.drawLine(textX, pointerY, pointerX, pointerY, textPaint);
    canvas.drawCircle(pointerX, pointerY, pointerRadius, textPaint);
}

// Maintains the state for a data item.
private class Item {
    public String label;
    public float value;
    @ColorInt
    public int color;

    // Computed values.
    public int startAngle;
    public int endAngle;

    public Shader shader;
}    

Grafik efektleri uygula

Android 12 (API düzeyi 31), RenderEffect. ve renk filtreleri gibi yaygın grafik efektlerinin uygulandığı, Android gölgelendirici efektleri ve daha fazlası View nesne ve hiyerarşik olarak hazırlanır. Efektleri zincir efektler olarak birleştirebilirsiniz. bir iç ve dış efektin ya da karma efektlerin. Bu özellik için destek işlem gücüne bağlı olarak değişir.

Arka plandaki temel öğelere de Şunun için RenderNode: arayarak View için arama yapın View.setRenderEffect(RenderEffect).

Bir RenderEffect nesnesini uygulamak için aşağıdakileri yapın:

view.setRenderEffect(RenderEffect.createBlurEffect(radiusX, radiusY, SHADER_TILE_MODE))

Görünümü programatik olarak oluşturabilir veya bir XML düzeninden şişirerek Bağlamı görüntüle'yi kullanarak dosyayı alın veya findViewById().