Najważniejszym elementem widoku niestandardowego jest jego wygląd. Rysunek niestandardowy może być łatwe lub złożone w zależności od potrzeb aplikacji. Ten dokument omawiają najczęściej używane operacje.
Więcej informacji: Omówienie elementów rysowalnych
Zastąp onDraw()
Najważniejszym krokiem podczas rysowania widoku niestandardowego jest zastąpienie
onDraw()
. Parametr onDraw()
to
Canvas
obiektu, którego widok może używać do rysowania. Zajęcia: Canvas
Określa metody rysowania tekstu, linii, map bitowych i wielu innych elementów graficznych
elementów podstawowych. Możesz użyć tych metod w aplikacji onDraw()
, aby utworzyć
niestandardowy interfejs użytkownika (UI).
Zacznij od utworzenia
Paint
obiekt.
W następnej sekcji znajdziesz więcej informacji o Paint
.
Tworzenie obiektów rysunkowych
android.graphics
rama dzieli rysowanie na 2 obszary:
- Co narysować, obsługiwane przez:
Canvas
. - Instrukcje rysowania. Obsługa:
Paint
.
Metoda Canvas
pozwala np. narysować linię, a
Paint
udostępnia metody definiowania koloru linii.
Canvas
udostępnia metodę rysowania prostokąta, a Paint
określa, czy prostokąt ma być wypełniony kolorem, czy pusty.
Canvas
definiuje kształty, które możesz rysować na ekranie.
Paint
określa kolor, styl, czcionkę itp.
co rysujesz.
Zanim cokolwiek narysowasz, utwórz co najmniej 1 obiekt Paint
.
Z tego przykładu można to zrobić w metodzie o nazwie init
. Ta metoda jest
jest wywoływana z konstruktora w Javie, ale może zostać zainicjowana w tekście
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)); ... }
Tworzenie obiektów z wyprzedzeniem to ważna optymalizacja. Widoki są
jest często odświeżany, a wiele obiektów rysunkowych wymaga kosztownego inicjowania.
znaczne tworzenie obiektów rysunkowych w ramach metody onDraw()
.
zmniejsza wydajność i może spowalniać interfejs użytkownika.
Obsługuj zdarzenia układu
Aby prawidłowo narysować widok niestandardowy, sprawdź jego rozmiar. Złożony niestandardowy często konieczne jest wykonywanie wielu obliczeń układu w zależności od rozmiaru i kształt ich obszaru na ekranie. Nigdy nie należy przyjmować żadnych założeń dotyczących rozmiaru widoku na ekranie. Nawet jeśli tylko jedna aplikacja korzysta z Twojego widoku, musi ona obsługuje różne rozmiary i gęstości ekranu oraz różne aspekty w trybie pionowym i poziomym.
Chociaż View
ma wiele metod prowadzenia pomiarów, ale większość z nich
zastąpiono. Jeśli widok nie wymaga specjalnej kontroli nad rozmiarem,
zastąpić jedną metodę:
onSizeChanged()
Funkcja onSizeChanged()
jest wywoływana, gdy do widoku zostanie po raz pierwszy przypisany
oraz jeszcze raz, jeśli rozmiar widoku zmieni się z jakiegokolwiek powodu. Obliczanie
pozycji, wymiarów i innych wartości związanych z rozmiarem widoku w
onSizeChanged()
, zamiast przeliczać je za każdym razem.
W poniższym przykładzie onSizeChanged()
jest miejscem, w którym widok
oblicza prostokątny ograniczający wykres i względną pozycję
etykiety tekstowej i innych elementów wizualnych.
Gdy przypisuje się rozmiarowi, menedżer układu zakłada, że dany rozmiar
obejmuje dopełnienie widoku. Obsługuj wartości dopełnienia przy obliczaniu
rozmiaru widoku. Oto fragment kodu onSizeChanged()
, który pokazuje, jak
w tym celu:
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); }
Jeśli chcesz mieć większą kontrolę nad parametrami układu widoku, zaimplementuj
onMeasure()
Parametry tej metody są
View.MeasureSpec
wartości określające, jak duży ma być element nadrzędny Twojego widoku,
czy to sztywna wartość maksymalna, czy tylko sugestia. W ramach optymalizacji
są one przechowywane jako spakowane liczby całkowite. Korzystasz z metod statycznych
View.MeasureSpec
, aby rozpakować informacje przechowywane w poszczególnych liczbach całkowitych.
Oto przykładowa implementacja onMeasure()
. W tym
stara się powiększyć obszar mapy, aby wykres był już tak duży
:
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); }
W tym kodzie należy pamiętać o 3 ważnych kwestiach:
- W obliczeniach uwzględniane jest dopełnienie widoku. Jak wspomniano za ten widok.
- Metoda pomocnicza
resolveSizeAndState()
służy do utworzenia ostatecznej wartości szerokości i wysokości. Ten pomocnik zwraca odpowiednią wartośćView.MeasureSpec
, porównując potrzebnego rozmiaru widoku do wartości przekazywanej doonMeasure()
. - Parametr
onMeasure()
nie ma wartości zwróconej. Zamiast tego metoda komunikuje swoje wyniki za pomocą funkcjisetMeasuredDimension()
Wywołanie tej metody jest obowiązkowe. Jeśli pominiesz to wywołanie, KlasaView
zgłasza wyjątek środowiska wykonawczego.
Rysowanie
Po zdefiniowaniu kodu tworzenia obiektu i pomiaru możesz zaimplementować
onDraw()
W każdym widoku implementacja onDraw()
jest implementowana w inny sposób,
ale istnieją pewne typowe działania, które są wspólne w większości widoków:
- Narysuj tekst za pomocą
drawText()
Określ krój czcionki, wywołującsetTypeface()
i kolor tekstu po wywołaniusetColor()
- Narysuj kształty podstawowe za pomocą
drawRect()
,drawOval()
, orazdrawArc()
Aby zmienić, czy kształty mają być wypełnione, konturowe czy oba, wywołajsetStyle()
- Narysuj bardziej złożone kształty za pomocą
Path
zajęcia. Zdefiniuj kształt, dodając linie i krzywe doPath
obiekt, a potem narysuj go za pomocądrawPath()
Podobnie jak w przypadku kształtów podstawowych, ścieżki można zaznaczać, wypełniać lub wykonywać jedno i drugie. w zależności odsetStyle()
. -
Zdefiniuj wypełnienia gradientowe, tworząc
LinearGradient
obiektów. Zadzwoń do nassetShader()
aby używać elementuLinearGradient
na wypełnionych kształtach. - Rysuj mapy bitowe za pomocą
drawBitmap()
Ten kod rysuje tekst, linie i kształty:
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; }
Zastosuj efekty graficzne
W Androidzie 12 (poziom API 31) dodano
RenderEffect
, która stosuje typowe efekty graficzne, takie jak rozmycie, filtry kolorów,
Efekty cieniowania Androida i inne funkcje
View
obiektów i
hierarchie renderowania. Efekty można łączyć w efekty łańcuchowe, które składają się
wewnętrzny i zewnętrzny efekt czy efekty mieszane. Obsługa tej funkcji
różni się w zależności od mocy obliczeniowej urządzenia.
Możesz też zastosować efekty do tła
RenderNode
–
View
, dzwoniąc
View.setRenderEffect(RenderEffect)
Aby wdrożyć obiekt RenderEffect
, wykonaj te czynności:
view.setRenderEffect(RenderEffect.createBlurEffect(radiusX, radiusY, SHADER_TILE_MODE))
Widok możesz utworzyć automatycznie lub zwiększać jego zasięg za pomocą układu XML,
pobrać je za pomocą Wyświetl powiązanie lub
findViewById()