Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 10 - Vlastní Android komponenta - Měření a kreslení

V minulé lekci, Vlastní Android komponenta - Kreslený graf, jsme si připravili základ třídy grafu a metody pro obsluhu jeho atributů.

V dnešním Android tutoriálu se budeme věnovat měření a konečně i kreslení :)

Měření

Začneme přepsáním metody onMeasure().

onMeasure()

Tuto metodu přepíšeme, abychom získali rozměry prostoru, ve kterém se bude graf nacházet. Díky tomu pak můžeme i určit velikost grafu. Kód metody bude následující:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    width = getMeasuredWidth();
    height = getMeasuredWidth();
    initParams(width, height);
    setMeasuredDimension(width, height);
}

Metody getMeasuredWidth() a getMeasuredHeight() jsou metody třídy View, které vrací změřenou šířku a výšku prostoru určeného pro komponentu. Možnost získat tyto "naměřené" hodnoty máme díky volání super.onMeasure(widthMeasureSpec, heightMeasureSpec) v prvním řádku metody onMeasure().

V předchozí ukázce je dvakrát použita metoda getMeasuredWidth() pro získání šířky i výšky prostoru pro graf. To protože komponenta s grafem má čtvercový tvar a výška komponenty bude rovna její šířce. Pokud by se jednalo o obdélníkový tvar, byla by pro výšku použita metoda getMeasuredHeight().

Získanou šířku a výšku prostoru pak předáváme metodě initParams() (tu si uvedeme níže) a tam vypočítáme rozměry všech částí grafu. Rozměry, se kterými se pracuje v onMeasure(), jsou v pixelech.

Přepisujeme-li onMeasure(), je nutné na jejím konci volat setMeasuredDimension(). Do parametrů vložíme zjištěné rozměry. Pokud tak neučiníme, aplikace bude ukončena s výjimkou IllegalStateException.

V metodě onMeasure() jde o sladění prostorového požadavku grafu s tím, jaké prostorové možnosti mu rodičovský element poskytuje.

Výpočet rozměrů částí grafu

Pojďme se podívat na deklaraci konstant, které slouží k těmto výpočtům a na deklaraci metody initParams(), kterou voláme v metodě onMeasure() po zjištění rozměrů prostoru:

// Šíř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);
}

Metoda initParams() na základě zadané výšky a šířky nastaví zmíněné proměnné.

Jdeme kreslit

Graf není vykreslován "jedním tahem" a skládá se z více částí:

  • Inicializace barev a stylů
  • Textu grafu
  • Pozadí grafu
  • Výseč grafu
  • Čáry čtvrtí

Inicializace barev a stylů

Barvy jsme získali v minulé části z XML metodou applyAttributeSet() a uložili jsme je do proměnných, též deklarovaných v předcházející části kurzu. Nyní tedy můžeme založit jednotlivé části grafu a nastavit jim tyto barvy:

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 následující ukázce je zajímavý způsob, jakým text vycentrujeme vertikálně. Pro horizontální vycentrování máme parametr Paint.Align.CENTER. Pro vertikální zarovnání podpora není, proto si budeme muset poradit 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);
}

Pojďme si vysvětlit výpočet vertikální pozice textu. Protože graf bude umět plynule reagovat na změnu zobrazené hodnoty, je nutné, aby uměl v průběhu této změny přizpůsobovat velikost textu ve středu grafu. Pokud grafu, jako maximální hodnotu, nastavíme moc vysoké číslo, hodnoty u horní hranice rozsahu nebudou vidět celé - budou částečně překryté pozadím a výsečí grafu. Již víme, že náš graf bude umět v textu zobrazovat aktuální hodnotu ve dvou režimech:

  • Přepočet na procenta na základě zadané maximální hodnoty grafu
  • Přímé zobrazení hodnoty

V případě procent problém s velikostí nenastane, ale v tom druhém případě velice snadno. Proto jsme si napsali v metodě drawGraphText() kus kódu, který nám bude velikost textu přizpůsobovat v závislosti na jeho délce. V popsaném kódu si ještě blíže vysvětlíme tento řádek:

int yPos = (int) ((centerOfGraphY) - ((paintText.descent() + paintText.ascent()) / 2)) ;

Z kódu nemusí každému být jasné, co se zde odehrává. Měl by vám pomoci následující obrázek, kde jsou schématicky naznačeny některé parametry textu:

Parametry textu na Android zařízeních - Vlastní vzhled Android komponent

Vše kolem vertikální polohy textu začíná testem, zda šířka textu, kterou zjišťujeme v řádku paintText.measureText(text), není větší než vypočítaná maximální možná šířka textu. Maximální šířka textu se řídí velikostí otvoru ve středu grafu. Pokud je šířka textu moc velká, vstupuje do hry cyklus, ve kterém postupně po 10ti procentech velikost textu zmenšujeme a vždy ihned šířku textu znovu měříme. Ve chvíli, kdy se text svou šířkou vejde do středu grafu, cyklus končí. Jako poslední krok metody drawGraphText() je volání samotné metody drawText() třídy Canvas pro vykreslení textu.

Na následujícím obrázku je vidět text, který se musel pro svou délku zmenšit tak, aby se na šířku vešel do "díry" v grafu. V tomto kroku by ještě nebylo vykreslené pozadí ani samotný graf, ale chci aby bylo vidět, do jakého prostoru se text vmáčkl:

Zmenšení textu v grafu - Vlastní vzhled Android komponent

Naše výše deklarované metody přijímají jeden parametr typu Canvas. Možná vás napadla otázka, kde tuto instanci vezmeme. Všechny tyto metody budou později volány v překryté metodě onDraw(), která má tento objekt v parametru. K metodě onDraw() se brzy prokoušeme ;-) Pro dnešek končíme.

V následujícím kvízu, Kvíz - Tvorba vlastní komponenty v Androidu, si vyzkoušíme nabyté zkušenosti z předchozích lekcí.


 

Předchozí článek
Vlastní Android komponenta - Kreslený graf
Všechny články v sekci
Vlastní vzhled Android komponent
Přeskočit článek
(nedoporučujeme)
Kvíz - Tvorba vlastní komponenty v Androidu
Článek pro vás napsal Pavel
Avatar
Uživatelské hodnocení:
3 hlasů
Autor se věnuje programování v Javě, hlavně pro Android. Mezi jeho další zájmy patří Arduino, Minecraft.
Aktivity