Ö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 olarak kullanılan bazı işlemler ele alınmaktadır.
Daha fazla bilgi için Çekilebilir öğelere genel bakış konusuna bakın.
onDraw() işlevini geçersiz kıl
Ö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
nesnedir. Canvas
sınıfı metin, çizgiler, bit eşlemler ve diğer birçok temel grafik öğesini çizme yöntemlerini tanımlar. Özel kullanıcı arayüzünüzü (UI) oluşturmak için onDraw()
ürününde bu yöntemleri kullanabilirsiniz.
Bir 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çevesinde, çizim iki alana bölünür:
- Çizilecek Ne,
Canvas
tarafından işleniyor. - Nasıl çizilir?
Paint
tarafından ele alınıyor.
Örneğin, Canvas
bir çizgi çizme yöntemi, Paint
ise bu çizginin rengini tanımlamaya yönelik yöntemler sağlar.
Canvas
bir dikdörtgen çizme yöntemine sahiptir ve Paint
bu dikdörtgenin bir renkle doldurulmasını veya boş bırakılmasını tanımlar.
Canvas
ekranda çizebileceğiniz şekilleri, Paint
ise çizdiğiniz her bir şeklin rengini, stilini, yazı tipini ve benzeri unsurları tanımlar.
Bir şey çizmeden önce bir veya daha fazla Paint
nesnesi oluşturun. Aşağıdaki örnek, bunu init
adlı bir yöntemle yapar. Bu yöntem Java'daki kurucu tarafından çağrılır, ancak Kotlin'de satır içinde 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 optimizasyondur. Görünümler sık sık yeniden çizilir ve birçok çizim nesnesi pahalı başlatma gerektirir.
onDraw()
yönteminizde çizim nesneleri oluşturmak performansı önemli ölçüde azaltı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 boyutunu öğrenin. Karmaşık özel görünümlerin genellikle ekrandaki alanlarının boyutuna ve şekline bağlı olarak birden çok düzen hesaplaması gerekir. Ekrandaki görünümünüzün boyutu hakkında hiçbir zaman varsayımda bulunmayın. Görünümünüzü yalnızca bir uygulama kullansa 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 yapmak için birçok yönteme sahip olsa da çoğunun geçersiz kılınmasına gerek yoktur. Görünümünüz, boyutu üzerinde özel bir kontrole ihtiyaç duymuyorsa yalnızca tek bir yöntemi geçersiz kılın: onSizeChanged()
.
onSizeChanged()
, görünümünüze ilk kez boyut atandığında ve görünümünüzün boyutu herhangi bir nedenle değiştiğinde tekrar çağrılır. Her çiziminizde yeniden hesaplamak yerine, onSizeChanged()
içinde görünümünüzün boyutuyla ilgili konumları, boyutları ve diğer değerleri hesaplayın.
Aşağıdaki örnekte onSizeChanged()
, görünümün grafiğin sınırlayıcı dikdörtgenini ve metin etiketi ile diğer görsel öğelerin göreli konumunu hesapladığı yerdir.
Görünümünüze bir boyut atandığında düzen yöneticisi, boyutun görünümün dolgusunu içerdiğini varsayar. Görünümünüzün boyutunu hesaplarken dolgu değerlerini dikkate alın. Bunun nasıl yapılacağını gösteren onSizeChanged()
snippet'ini burada 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ı kontrole ihtiyacınız varsa onMeasure()
uygulayın.
Bu yöntemin parametreleri,
View.MeasureSpec
görünümünüzün üst tarafındaki görünümü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ğerlerdir. Optimizasyon olarak, bu değerler paketlenmiş tamsayılar olarak depolanır ve her bir tamsayıda depolanan bilgileri açmak için View.MeasureSpec
statik yöntemlerini kullanırsınız.
Aşağıda, onMeasure()
işlevinin bir örneği verilmiştir. Bu uygulamada, grafik alanını kendi etiketi kadar büyük hale getirecek kadar büyük hale getirmeye çalışı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:
- Hesaplamalar, görünümün dolgusunu dikkate alır. Daha önce de belirtildiği gibi bu, görünümün sorumluluğudur.
- Nihai genişlik ve yükseklik değerlerini oluşturmak için
resolveSizeAndState()
yardımcı yöntemi kullanılır. Bu yardımcı, görünümün gerekli boyutunuonMeasure()
işlevine geçirilen değerle karşılaştırarak uygun birView.MeasureSpec
değeri döndürür. onMeasure()
, döndürülen bir değer içermiyor. Bunun yerine, yöntemsetMeasuredDimension()
yöntemini çağırarak sonuçlarını bildirir. Bu yöntemi çağırmak zorunludur. Bu çağrıyı atlarsanızView
sınıfı bir çalışma zamanı istisnası atar.
Çiz
Nesne oluşturma ve ölçüm kodunuzu tanımladıktan sonra onDraw()
kodunu uygulayabilirsiniz. onDraw()
her görünümde farklı şekilde uygulanır, ancak çoğu görüntülemenin paylaştığı bazı ortak işlemler vardır:
drawText()
kullanarak metin çizin. Yazı tipini,setTypeface()
yöntemini çağırarak ve metin renginisetColor()
yöntemini çağırarak belirtin.drawRect()
,drawOval()
vedrawArc()
kullanarak basit şekiller çizin.setStyle()
yöntemini çağırarak şekillerin dolu, dış çizgili veya her ikisini de değiştirebilirsiniz.Path
sınıfını kullanarak daha karmaşık şekiller çizin.Path
nesnesine çizgiler ve eğriler ekleyerek bir şekil tanımlayın, ardındandrawPath()
kullanarak şekli çizin. İlkel şekillerde olduğu gibi yollar,setStyle()
değerine bağlı olarak ana hatlarıyla belirtilebilir, doldurulabilir veya her ikisi birden olabilir.-
LinearGradient
nesneleri oluşturarak gradyan dolgularını tanımlayın. Dolgulu şekillerdeLinearGradient
kullanmak içinsetShader()
numaralı telefonu arayın. drawBitmap()
kullanarak bit eşlemler çizin.
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 uygulayın
Android 12'de (API düzeyi 31), View
nesnelerine ve oluşturma hiyerarşilerine bulanıklaştırma, renk filtreleri ve Android gölgelendirici efektleri gibi yaygın grafik efektlerinin uygulandığı RenderEffect
sınıfını ekler. Efektleri, iç ve dış efektlerden
ya da karışık efektlerden oluşan zincir efektleri olarak birleştirebilirsiniz. Bu özellik için destek, cihazın işlem gücüne göre değişir.
Ayrıca, View.setRenderEffect(RenderEffect)
yöntemini çağırarak View
için temel RenderNode
efektine de efekt uygulayabilirsiniz.
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 XML düzeninden şişirip Görünüm bağlama ya da
findViewById()
kullanarak alabilirsiniz.