Özel görünümün 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 belgede, en yaygın işlemlerden bazıları ele alınmaktadır.
Daha fazla bilgi için Çekilebilir öğelere genel bakış başlıklı makaleyi inceleyin.
onDraw() işlevini geçersiz kılma
Özel bir görünüm çizmenin en önemli adımı, onDraw()
yöntemini geçersiz kılmaktır. onDraw() parametresi, görünümün kendisini çizmek için kullanabileceği bir Canvas nesnesidir. Canvas sınıfı, metin, çizgi, bit eşlem ve diğer birçok grafik öğesini çizme yöntemlerini tanımlar. onDraw()'da özel kullanıcı arayüzünüzü (UI) oluşturmak için bu yöntemleri kullanabilirsiniz.
Paint nesnesi oluşturarak başlayın.
Sonraki bölümde Paint daha ayrıntılı olarak ele alınmaktadır.
Çizim nesneleri oluşturma
android.graphics
çerçevesi, çizimi iki alana ayırır:
Canvastarafından işlenen ne çizileceği.Painttarafından yönetilen How to draw.
Örneğin, Canvas bir çizgi çizme yöntemi sunarken Paint bu çizginin rengini tanımlama yöntemleri sunar.
Canvas dikdörtgen çizme yöntemine sahiptir ve Paint
dikdörtgenin renkle doldurulup doldurulmayacağını tanımlar.
Canvas, ekranda çizebileceğiniz şekilleri tanımlar. Paint ise çizdiğiniz her şeklin rengini, stilini, yazı tipini vb. tanımlar.
Çizim yapmadan önce bir veya daha fazla Paint nesne oluşturun. Aşağıdaki örnekte bu işlem init adlı bir yöntemde yapılıyor. Bu yöntem, Java'daki oluşturucudan çağrılır ancak Kotlin'de satır içi olarak başlatılabilir.
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 optimizasyon yöntemidir. Görünümler sık sık yeniden çizilir ve birçok çizim nesnesi pahalı başlatma işlemleri gerektirir.
onDraw() yönteminizde çizim nesneleri oluşturmak performansı önemli ölçüde düşürür ve kullanıcı arayüzünüzün yavaşlamasına neden olabilir.
Düzen etkinliklerini işleme
Özel görünümünüzü düzgün şekilde çizmek için boyutunu öğrenin. Karmaşık özel görünümler, ekrandaki alanlarının boyutuna ve şekline bağlı olarak genellikle birden fazla düzen hesaplaması yapmalıdır. Ekrandaki görünümünüzün boyutuyla ilgili hiçbir zaman varsayımda bulunmayın. Görünümünüzü yalnızca bir uygulama kullanıyor olsa bile bu uygulamanın, hem dikey hem de yatay modda farklı ekran boyutlarını, birden fazla ekran yoğunluğunu ve çeşitli en-boy oranlarını işlemesi gerekir.
View
ölçümün işlenmesi için birçok yönteme sahip olsa da bunların çoğu geçersiz kılınmamalıdır. Görünümünüzün boyutu üzerinde özel bir kontrol gerekmiyorsa yalnızca bir yöntemi geçersiz kılın:
onSizeChanged().
onSizeChanged(), görünümünüze ilk kez bir boyut atandığında ve görünümünüzün boyutu herhangi bir nedenle değiştiğinde tekrar çağrılır. Çizim yaptığınız her seferde yeniden hesaplamak yerine, onSizeChanged() içinde konumları, boyutları ve görünümünüzün boyutuyla ilgili diğer tüm değerleri hesaplayın.
Aşağıdaki örnekte, onSizeChanged() görünümün, grafiğin sınırlayıcı dikdörtgenini ve metin etiketinin diğer görsel öğelere göre konumunu hesapladığı yerdir.
Görünümünüze bir boyut atandığında düzen yöneticisi, boyuta görünümün dolgusunun dahil edildiğini varsayar. Görünümünüzün boyutunu hesaplarken dolgu değerlerini işleyin. Bunu nasıl yapacağınızı gösteren onSizeChanged() adlı makaleden bir snippet'i aşağıda bulabilirsiniz:
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ı kontrol sağlamanız gerekiyorsa onMeasure() uygulayın.
Bu yöntemin parametreleri şunlardır:
View.MeasureSpec
Görünümünüzün üst öğesinin, görünümünüzün ne kadar büyük olmasını istediğini ve bu boyutun kesin bir maksimum değer mi yoksa yalnızca bir öneri mi olduğunu belirten değerler. Bu değerler, optimizasyon amacıyla paketlenmiş tam sayılar olarak depolanır. Her tam sayıda depolanan bilgileri paketinden çıkarmak için View.MeasureSpec statik yöntemlerini kullanırsınız.
onMeasure() için örnek bir uygulama aşağıda verilmiştir. Bu uygulamada, alanın grafiği etiketi kadar büyük hale getirmek için yeterince büyük olması amaçlanır:
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:
- Hesaplamalarda görünümün dolgusu dikkate alınır. Daha önce de belirtildiği gibi, bu görünümün sorumluluğundadır.
- Son genişlik ve yükseklik değerlerini oluşturmak için yardımcı yöntem
resolveSizeAndState()kullanılır. Bu yardımcı, görünümün gereken boyutunuonMeasure()içine aktarılan değerle karşılaştırarak uygun birView.MeasureSpecdeğeri döndürür. onMeasure()dönüş değeri içermez. Bunun yerine, yöntemsetMeasuredDimension()yöntemini çağırarak sonuçlarını bildirir. Bu yöntemin çağrılması zorunludur. Bu çağrıyı atlarsanızViewsınıfı bir çalışma zamanı istisnası oluşturur.
Çiz
Nesne oluşturma ve ölçüm kodunuzu tanımladıktan sonra onDraw()'yı uygulayabilirsiniz. Her görünüm onDraw() farklı şekilde uygular ancak çoğu görünümün paylaştığı bazı ortak işlemler vardır:
drawText()kullanarak metin çizin. Yazı tipinisetTypeface()ve metin renginisetColor()çağırarak belirtin.drawRect(),drawOval()vedrawArc()kullanarak temel şekiller çizin.setStyle()işlevini çağırarak şekillerin doldurulup doldurulmayacağını, ana hatlarının çizilip çizilmeyeceğini veya her ikisinin de yapılıp yapılmayacağını değiştirin.Pathsınıfını kullanarak daha karmaşık şekiller çizin.Pathnesnesine çizgiler ve eğriler ekleyerek bir şekil tanımlayın, ardındandrawPath()kullanarak şekli çizin. İlkel şekillerde olduğu gibi, yollar dasetStyle()bağlı olarak ana hatları çizilebilir, doldurulabilir veya her ikisi de yapılabilir.-
LinearGradientnesneleri oluşturarak gradyan dolgularını tanımlayın. Dolu şekillerdeLinearGradientkullanmak içinsetShader()tuşuna basın. drawBitmap()kullanarak bit eşlemler çizin.
Aşağıdaki kodda metin, çizgi ve şekil karışımı çizilir:
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 uygulama
Android 12 (API düzeyi 31), bulanıklaştırma, renk filtreleri ve Android gölgelendirici efektleri gibi yaygın grafik efektlerini
RenderEffectnesnelerine ve oluşturma hiyerarşilerine uygulayan View sınıfını ekler. Efektleri, iç ve dış efektlerden oluşan zincir efektler veya harmanlanmış efektler olarak birleştirebilirsiniz. Bu özelliğin desteği, cihazın işlem gücüne göre değişir.
Ayrıca, RenderNode için View temelindeki efektleri View.setRenderEffect(RenderEffect) çağırarak da uygulayabilirsiniz.
RenderEffect nesnesi 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 genişletebilir ve View binding ya da
findViewById() kullanarak alabilirsiniz.