Lekce 9 - Vlastní Android komponenta - Kreslený graf
V minulé lekci, Vlastní Android komponenta - Dokončení textového pole, jsme použili vlastních parametrů pro nastavení a dokončili vlastní vylepšené pole pro zadávání textu pro Android.
V dnešním Android tutoriálu si ukážeme jak vytvořit speciální komponentu, kterou nemáme ve standardní nabídce a kterou si tedy budeme muset od základu "nakreslit". Řekněme, že potřebujeme takovýto graf:
Protože budeme celý View
od nuly vykreslovat a nikoli skládat
z existujících komponent, odpadá nutnost vytváření XML návrhu tak, jak
jsme to dělali v předchozím příkladu s vlastním textovým polem.
Parametry pro nastavení komponenty
Nadefinujeme tedy parametry, kterými budeme moci grafu v XML nastavit vzhled
a nějaké ty vlastnosti. Opět vytvoříme soubor attrs.xml
a
opět ho umístíme do res\values\attrs.xml
. Jeho obsah je
následující:
<resources> <declare-styleable name="DrawView"> <attr name="dwDirection" format="enum"> <enum name="CW" value="1"/> <enum name="CCW" value="2"/> </attr> <attr name="dwStartAngle" format="integer" /> <attr name="dwMaxValue" format="integer" /> <attr name="dwValue" format="integer" /> <attr name="dwDisableText" format="boolean" /> <attr name="dwDisableAnimation" format="boolean" /> <attr name="dwColorResGraph" format="color" /> <attr name="dwColorResGraphBackground" format="color" /> <attr name="dwTextColor" format="color" /> <attr name="dwDisableQuerterLines" format="boolean" /> <attr name="dwQuerterLinesColor" format="color" /> <attr name="dwTextValueFormat" format="enum"> <enum name="percentages" value="1"/> <enum name="value" value="2"/> </attr> </declare-styleable> </resources>
Vysvětlíme si, k čemu jednotlivé parametry slouží:
dwDirection
- Směr, kterým hodnota grafu narůstá. Ve směru hodinových ručiček (CW
) nebo opačně (CCW
). HodnotaCW
je defaultní.dwStartAngle
- Pokud nebude nastaveno, nachází se počátek grafu vpravo, vztaženo k hodinám, v místě trojky. O velikost tohoto parametru (velikost úhlu ve stupních) se posune počátek grafu ve směru hodinových ručiček.dwMaxValue
- Maximální hodnota, kterou graf zobrazí a při které bude celý vyplněný. Při nenastavení tohoto parametru bude maximální hodnota defaultně na hodnotě100
.dwValue
- Aktuální hodnota grafu.dwDisableText
- Skrytí číselné hodnoty uvnitř grafu.dwColorResGraph
- Barva kruhové výseče grafu.dwColorResGraphBackground
- Barva pozadí kruhové výseče grafu.dwTextColor
- Barva číselné hodnoty uvnitř grafu.dwDisableQuerterLines
- Skrýt pomocné čáry označující čtvrtiny grafu.dwQuerterLinesColor
- Barva pomocných čar, označujících čtvrtiny grafu.dwTextValueFormat
- Formát číselné hodnoty ve středu grafu. Výběr z dvou možností - přímo zadaná hodnota nebo přepočet na procenta. Procentuální zobrazení je defaultní.
Tvorba třídy dědící od
View
Pokračujeme vytvořením nové třídy DrawView
dědící od
třídy View
:
public class DrawView extends View { public DrawView(Context context) { super(context); init(); } public DrawView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); applyAttributeSet(context, attrs); init(); } public DrawView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); applyAttributeSet(context, attrs); init(); } public DrawView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); applyAttributeSet(context, attrs); init(); } }
Třída obsahuje nám již známé čtyři konstruktory a i zde napíšeme
metody applyAttributeSet()
a init()
:
// Směr pohybu výseče grafu // CW - Ve směru hodinových ručiček // CWW - Proti směru hodinových ručiček public enum Direction {CW, CCW} // Způsob zobrazení zadané hodnoty grafu // PERCENTAGES - přepočítá na procenta // VALUE - zobrazí přímo zadanou hodnotu public enum ValueFormat {PERCENTAGES, VALUE} // Proměnné k uživatelskému nastavení grafu Direction direction = Direction.CW; ValueFormat valueFormat = ValueFormat.PERCENTAGES; int startAngle = 0; // Posunutí nulové hodnoty grafu int maxValue = 100; int value = 0; int colorResGraph; int colorResGraphBackground; int colorResText; boolean disableText; boolean disableQuarterLines; int colorQuarterLines; int graphPadding = 50; // Odsazení grafu od okrajů jeho okolního prostoru int valueInPercentages = 0; // Hodnota grafu přepočtená na procenta int valueInAngle = 0; // Hodnota grafu přepočítaná na úhel, na který je graf naplněn vůči počátku grafu int textSize = 0; // Proměnná pro vypočítanou velikost textu ve středu grafu private void applyAttributeSet(Context context, AttributeSet attrs) { if (attrs == null) return; TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.DrawView, 0, 0); if (ta == null) return; int tempDirection = ta.getInt(R.styleable.DrawView_dwDirection, 1); if (tempDirection == 1) { direction = Direction.CW; } else { direction = Direction.CCW; } int tempValueFormat = ta.getInt(R.styleable.DrawView_dwTextValueFormat, 1); if (tempValueFormat == 1) { valueFormat = ValueFormat.PERCENTAGES; } else { valueFormat = ValueFormat.VALUE; } try { startAngle = ta.getInt(R.styleable.DrawView_dwStartAngle, 0); maxValue = ta.getInt(R.styleable.DrawView_dwMaxValue, 100); value = valueToDraw = ta.getInt(R.styleable.DrawView_dwValue, 0); colorResGraph = ta.getColor(R.styleable.DrawView_dwColorResGraph, 0); colorResGraphBackground = ta.getColor(R.styleable.DrawView_dwColorResGraphBackground, 0); colorResText = ta.getColor(R.styleable.DrawView_dwTextColor, 0); disableText = ta.getBoolean(R.styleable.DrawView_dwDisableText, false); animationDisabled = ta.getBoolean(R.styleable.DrawView_dwDisableAnimation, false); disableQuarterLines = ta.getBoolean(R.styleable.DrawView_dwDisableQuerterLines, false); colorQuarterLines = ta.getColor(R.styleable.DrawView_dwQuerterLinesColor, 0); } finally { ta.recycle(); } // Nastavení defaultních barev v případě, že nejsou nastaveny z venčí if (colorResGraph == 0) { colorResGraph = context.getResources().getColor(R.color.colorGraph); } if (colorResGraphBackground == 0) { colorResGraphBackground = context.getResources().getColor(R.color.colorGraphBackground); } if (colorResText == 0) { colorResText = colorResGraph; } if (colorQuarterLines == 0) { colorQuarterLines = colorResGraph; } }
V metodě init()
máme volání metod, které si napíšeme pro
provedení několika výpočtů:
private void init() { computePercentages(); // Přepočet hodnoty grafu na procenta convertValueToAngle(); // Přepočet hodnoty na úhel, na který se graf natočí // Požadavek, aby došlo k novému vykreslení grafu na displeji, // protože byl změněn vzhled objektu. Metoda třídy View. invalidate(); } private void computePercentages() { if (maxValue == 0) return; if (value == 0) valueInPercentages = 0; valueInPercentages = (int) (100 * valueToDraw / maxValue); } private void convertValueToAngle() { if (value < 0 || maxValue == 0) { valueInAngle = 0; return; } if (valueFormat == ValueFormat.PERCENTAGES) { valueInAngle = (int) (valueInPercentages * PERCENTAGES_TO_ANGLE_CONSTANT); } else { valueInAngle = (int) ((float)valueToDraw/(float)maxValue*360f); } }
V metodě init()
si všimněte volání
invalidate()
. Jde o metodu třídy View
, která
zajistí obnovení View
na obrazovce. Doslova dle dokumentace:
Zruší platnost celého View
. Je-li View
viditelný, bude v budoucnu zavolána metoda onDraw()
. Díky
tomu objekt bude znovu vykreslen. Toto volání je nutné vždy, kdykoliv
provedeme nějaké změny mající vliv na vzhled objektu.
Dalším krokem bude přepsání metod onMeasure()
a
onDraw()
, což jsou metody třídy View
. Ale to až v
následující lekci našeho seriálu, Vlastní Android komponenta - Měření a kreslení