4. diel - Vlastné Android komponent - Meranie a kreslenie
V minulej lekcii, Vlastné Android komponent - Kreslený graf , sme si pripravili základ triedy grafu a metódy pre obsluhu jeho atribútov.
V dnešnom Android tutoriálu sa budeme venovať merania a konečne aj kreslenie:)
Meranie
Začneme prepísaním metódy onMeasure()
.
onMeasure()
Túto metódu prepíšeme, aby sme získali rozmery priestoru, v ktorom sa bude graf nachádzať. Vďaka tomu potom môžeme aj určiť veľkosť grafu. Kód metódy bude nasledujúci:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); width = getMeasuredWidth(); height = getMeasuredWidth(); initParams(width, height); setMeasuredDimension(width, height); }
Metódy getMeasuredWidth()
a getMeasuredHeight()
sú metódy triedy View
, ktoré vracia zmeranú šírku a výšku
priestoru určeného pre komponent. Možnosť získať tieto "namerané" hodnoty
máme vďaka volanie
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
v prvom riadku
metódy onMeasure()
.
V predchádzajúcej ukážke je dvakrát použitá metóda
getMeasuredWidth()
pre získanie šírky aj výšky priestoru pre
graf. To pretože komponenta s grafom má štvorcový tvar a výška komponenty
bude rovná jej šírke. Ak by sa jednalo o obdĺžnikový tvar, bola by pre
výšku použitá metóda getMeasuredHeight()
.
Získanú šírku a výšku priestoru potom odovzdávame metóde
initParams()
(tú si uvedieme nižšie) a tam vypočítame rozmery
všetkých častí grafu. Rozmery, s ktorými sa pracuje v
onMeasure()
, sú v pixeloch.
Prepisujeme Ak onMeasure()
, je
nutné na jej konci volať setMeasuredDimension()
.
Do parametrov vložíme zistené rozmery. Ak tak neurobíme, aplikácia bude
ukončená s výnimkou IllegalStateException
.
V metóde onMeasure()
ide o zladenie priestorového požiadavke
grafu s tým, aké priestorové možnosti mu rodičovský element poskytuje.
Výpočet rozmerov častí grafu
Poďme sa pozrieť na deklaráciu konštánt, ktoré slúžia na tento
výpočet a na deklaráciu metódy initParams()
, ktorú voláme v
metóde onMeasure()
po zistení rozmerov priestoru:
// Šířka výseče grafu (% z poloměru grafu) final int ARC_WIDTH_PERCENTAGES = 40; // Přesah pozadí grafu přes výseč // 1 = žádný přesah final int ARC_BACKGROUND_WIDTH_PERCENTAGES = 3; // Přepočet procent na úhly (1% => 3,6°) final float PERCENTAGES_TO_ANGLE_CONSTANT = 3.6f; // Poměr výšky textu k poloměru kruhové výseče grafu final float MAX_TEXT_SIZE_RATIO = 0.60f; // Vzdálenost grafu od okolních hran final float PADDING_RATIO = 0.05f; int width; int height; // Maximální poloměr grafu float maxRadius; // Souřadnice středu int centerOfGraphX; int centerOfGraphY; // Šířka výseče int arcWidth; // Přesah pozadí přes výseč grafu int backgroundOverlap = 5; private void initParams(int width, int height) { graphPadding = (int) (height * PADDING_RATIO); maxRadius = (width / 2) - graphPadding; centerOfGraphX = width/2; centerOfGraphY = height/2; arcWidth = (int) (maxRadius / 100 * ARC_WIDTH_PERCENTAGES); backgroundOverlap = (int) (maxRadius / 100 * ARC_BACKGROUND_WIDTH_PERCENTAGES); textSize = (int) (maxRadius * MAX_TEXT_SIZE_RATIO); }
Metóda initParams()
na základe zadanej výšky a šírky
nastaví spomínané premenné.
Ideme kresliť
Graf nie je vykresľovaný "jedným ťahom" a skladá sa z viacerých častí:
- Inicializácia farieb a štýlov
- textu grafu
- pozadie grafu
- výseč grafu
- čiary štvrtí
Inicializácia farieb a štýlov
Farby sme získali v minulej časti z XML metódou
applyAttributeSet()
a uložili sme ich do premenných, tiež
deklarovaných v predchádzajúcej časti kurzu. Teraz teda môžeme založiť
jednotlivé časti grafu a nastaviť im tieto farby:
Paint paint = new Paint(); // Výseč Paint paintBackground = new Paint(); // Pozadí Paint paintText = new Paint(); // Text Paint paintLines = new Paint(); // Pomocné čtvrtinové čáry private void setPaints() { paint.setColor(colorResGraph); paint.setAntiAlias(true); paintBackground.setColor(colorResGraphBackground); paintBackground.setAntiAlias(true); paintText.setColor(colorResText); paintText.setAntiAlias(true); paintLines.setColor(colorQuarterLines); paintLines.setAntiAlias(true); }
Text grafu
V nasledujúcej ukážke je zaujímavý spôsob, akým text vycentrujeme
vertikálne. Pre horizontálne vycentrovanie máme parameter
Paint.Align.CENTER
. Pre vertikálne zarovnanie podpora nie je,
preto si budeme musieť poradiť sami.
private void drawGraphText(Canvas canvas) { if (disableText) { // Zobrazení textu není povoleno return; } // Proměnná pro zobrazovaný text String text = ""; if (valueFormat == ValueFormat.PERCENTAGES) { // Zobrazení v % text = "" + valueInPercentages + "%"; } else { // Zobrazování skutečné hodnoty text = "" + valueToDraw; } // Inicializace stylu textu paintText.setTextSize(textSize); paintText.setAntiAlias(true); paintText.setTextAlign(Paint.Align.CENTER); // Horizontální zarovnání textu na střed // Měření šířky textu float textWidth = paintText.measureText(text); // Výpočet šířky prostoru pro text (průměr otvoru ve středu grafu) int maxTextWidth = (int) ((maxRadius - arcWidth - backgroundOverlap) * 2); // Výpočet vertikální pozice textu - vysvětlení v textu pod touto ukázkou int yPos = (int) ((centerOfGraphY) - ((paintText.descent() + paintText.ascent()) / 2)) ; // Test, zda se text vejde do grafu, případně i úprava velikosti textu if (textWidth > maxTextWidth) { int newTextSize = textSize; while (textWidth > maxTextWidth) { // Zmenšení textu o 10% newTextSize = scaleDownTextSize(newTextSize); paintText.setTextSize(newTextSize); // Přeměření šířky textu textWidth = paintText.measureText(text); // Přepočítání vertikální pozice textu yPos = (int) ((height / 2) - ((paintText.descent() + paintText.ascent()) / 2)) ; } } else { paintText.setTextSize(textSize); } canvas.drawText(text, centerOfGraphX, yPos, paintText); } private int scaleDownTextSize(int actualSize) { if (actualSize <= 0) return 0; return (int) (actualSize * 0.90); }
Poďme si vysvetliť výpočet vertikálnej pozície textu. Pretože graf bude vedieť plynule reagovať na zmenu zobrazenej hodnoty, je nutné, aby vedel v priebehu tejto zmeny prispôsobovať veľkosť textu v stredu grafu. Ak grafu, ako maximálnu hodnotu, nastavíme moc vysoké číslo, hodnoty pri hornej hranici rozsahu nebudú vidieť celé - budú čiastočne prekryté pozadím a výsekov grafu. Už vieme, že náš graf bude vedieť v texte zobrazovať aktuálnu hodnotu v dvoch režimoch:
- Prepočet na percentá na základe zadanej maximálnej hodnoty grafu
- Priame zobrazenie hodnoty
V prípade percent problém s veľkosťou nenastane, ale v tom druhom
prípade veľmi ľahko. Preto sme si napísali v metóde
drawGraphText()
kus kódu, ktorý nám bude veľkosť textu
prispôsobovať v závislosti na jeho dĺžke. V opísanom kóde si ešte
bližšie vysvetlíme tento riadok:
int yPos = (int) ((centerOfGraphY) - ((paintText.descent() + paintText.ascent()) / 2)) ;
Z kódu nemusí každému byť jasné, čo sa tu odohráva. Mal by vám pomôcť nasledujúci obrázok, kde sú schematicky naznačené niektoré parametre textu:
Všetko okolo vertikálnej polohy textu začína testom, či šírka textu,
ktorú zisťujeme v riadku paintText.measureText(text)
, nie je
väčšia ako vypočítaná maximálna možná šírka textu. Maximálna šírka
textu sa riadi veľkosťou otvoru v stredu grafu. Ak je šírka textu moc
veľká, vstupuje do hry cyklus, v ktorom postupne po 10tich percentách
veľkosť textu zmenšujeme a vždy ihneď šírku textu znova meriame. Vo
chvíli, keď sa text svojou šírkou vojde do stredu grafu, cyklus končí. Ako
posledný krok metódy drawGraphText()
je volanie samotné metódy
drawText()
triedy Canvas
pre vykreslenie textu.
Na nasledujúcom obrázku je vidieť text, ktorý sa musel pre svoju dĺžku zmenšiť tak, aby sa na šírku vošiel do "diery" v grafe. V tomto kroku by ešte nebolo vykreslené pozadia ani samotný graf, ale chcem aby bolo vidieť, do akého priestoru sa text vmáčkl:
Naše vyššie deklarovanej metódy prijímajú jeden parameter typu
Canvas
. Možno vás napadla otázka, kde túto inštanciu vezmeme.
Všetky tieto metódy budú neskôr volánmi v prekrytej metóde
onDraw()
, ktorá má tento objekt v parametri. K metóde
onDraw()
sa čoskoro prehrýzť;-) Pre dnešok končíme.
Nabudúce, v lekcii Vlastné Android komponent - Dokončenie kreslenie grafu , budeme pokračovať kreslením ďalších častí grafu.