Jak vytvořit vlastní zobrazení?

Předtím, než se pustíte do procesu vytváření vlastního pohledu, bylo by vhodné uvést, proč bychom mohli potřebovat vytvořit vlastní zobrazení.

  • Jedinečnost: Vytvořte něco, co nelze dosáhnout běžnými pohledy.
  • Optimalizace: Mnohokrát máme tendenci přidávat více pohledů nebo omezení, abychom vytvořili požadovaný pohled, který lze drasticky optimalizovat z hlediska doby kreslení, měření nebo rozvržení.

Nejlepší způsob, jak začít, by bylo pochopit, jak android spravuje skupiny zobrazení a rozkládá zobrazení na obrazovce. Podívejme se na níže uvedený diagram.

Toto je základní životní cyklus zobrazení Android

onMeasure (widthMeasureSpec: Int, heightMeasureSpec: Int)
 Každý nadřazený pohled předává omezení výšky a šířky svému podřízenému pohledu, na základě kterého podřízený pohled rozhoduje o tom, jak velký chce být. Podřízený pohled pak volá setMeasuredDimension (), aby uložil jeho měřenou šířku a výšku.

Jak se tato omezení přenášejí?

Android používá 32bitový int nazvaný spec spec k zabalení dimenze a jejího režimu. Režim je omezením a může být tří typů:

  • MeasureSpec.EXACTLY: Pohled by měl být absolutně stejný jako rozměr předaný spolu se spec. Např. layout_width = “100dp”, layout_width = ”match_parent”, layout_weight = ”1”.
  • MeasureSpec.AT_MOST: V pohledu může být předána maximální výška / šířka kóty. Může však být také menší, pokud si to přeje. Např. Android: layout_width = ”wrap_content”
  • MeasureSpec.UNSPECIFIED: Pohled může mít libovolnou velikost. Toto je předáno, když jako rodič používáme ScrollView nebo ListView.

onLayout (změněno: Boolean, vlevo: Int, top: Int, right: Int, bottom: Int)
Android aplikuje jakékoli odchylky nebo okraje a volá tuto metodu, aby informoval váš názor o tom, kde přesně by byl umístěn na obrazovku. Na rozdíl od onMeasure se během průchodu nazývá jen jednou. Proto se v této metodě doporučuje provádět složité výpočty.

onDraw (canvas: Canvas)
A konečně, Android vám poskytne 2D kreslicí plochu, tj. Plátno, na které můžete kreslit pomocí objektu barvy.

Vlákno uživatelského rozhraní pak předává seznamy zobrazení pro vykreslení vlákna, které provádí mnoho optimalizací, a nakonec GPU zpracovává data, která mu byla předána pomocí vykreslovacího vlákna.

Jak definovat atributy pro svůj pohled?

Deklarování atributů XML je jednoduché. Stačí do svého attrs.xml přidat deklarovatelný styl a deklarovat formát pro každý atribut.
Například pokud vytváříte jednoduchý pohled, který zobrazuje kruh s jeho štítkem. Vaše atributy mohou vypadat takto.


    
    
    
    
    
    
        
        
        
        
    

Totéž se odkazuje při vytváření pohledu následujícím způsobem.

app: circleRadius = "5dp"
app: circleDrawable = "@ drawable / ic_bell"
app: circleColorType = "fillStroke"
...

Nyní musíme analyzovat tyto atributy ve vaší třídě java nebo kotlin.

  • Vytvořte třídu zobrazení, která rozšiřuje třídu android.view
  • Získat odkaz na atributy deklarované v XML. Zatímco attrs je předán v konstruktoru, druhý parametr je odkaz na styl, který jsme právě deklarovali. Poslední dva se používají k získání výchozích atributů stylu v motivu nebo k dodání výchozích atributů stylu.
val a = context.theme.obtainStyledAttributes (attrs, R.styleable.YourCustomViewClass, 0, 0)
  • Analýza argumentů atributu
radi = a.getDimension (R.styleable.CustomView_circleRadius, rezervoár)
showLabel = a.getDimension (R.styleable.CustomView_showName, rezervoár)
colorType = a.getInteger (R.styleable.CustomView_colorType, colorType)

Při analýze atributu dimenze Android automaticky zpracovává proces převodu dp nebo sp na správné množství pixelů podle velikosti obrazovky. Musíte však zajistit, aby byla záložní hodnota převedena na příslušnou hodnotu v pixelech, protože android vrací záložní hodnotu bez konverzí, pokud není v XML definována hodnota atributu.

Při analýze všech ostatních atributů je to celkem jednoduché. Budu vás informovat o tom, jak analyzovat příznaky. Deklarování atributů flags může být někdy užitečné, protože pomocí jediného atributu můžeme zkontrolovat více vlastností. To je stejný způsob, jakým Android zpracovává indikátor viditelnosti.

colorType je celé číslo, které představuje flagSet. Nyní, protože každý bit v celém čísle může být použit k reprezentaci indikace. Můžeme zkontrolovat, zda existuje příznak, a podle toho provádět své operace. Abychom zkontrolovali, zda úder typu vlajky existuje, můžeme jednoduše provést operaci nebo operaci na flagSet s hodnotou tahu. Pokud výsledek zůstane stejný, znamená to, že příznak skutečně existuje v flagSet.

  • Nakonec recyklujte zadané pole, které má použít pozdější volající.
a.recycle ()

Inicializace objektů
Vždy je lepší inicializovat objekty barvy a cesty nebo jiné proměnné v samotném konstruktoru. Vzhledem k tomu, že je deklarována, může jakákoli jiná metoda procházení vyústit v nesmyslné vytváření objektů znovu a znovu.

Výpočet velikosti pohledu

Výpočet velikosti pohledu může být někdy někdy velmi náročný. Musíte se ujistit, že váš pohled nezabírá žádný další pixel, nebo si vyžádat méně pixelů, protože to může nakonec ukázat zvláštní mezeru nebo úplné zobrazení. Toto jsou základní kroky, které musíte provést, abyste mohli vypočítat velikost svého pohledu.

  • Spočítejte si, kolik šířky a výšky váš pohled vyžaduje. Pokud například nakreslíte jednoduchý kruh s popiskem pod ním. Navrhovaná šířka by byla:
     (průměr kruhu + jakákoli další šířka, pokud je obsazena štítkem).
  • Vypočítejte požadovanou šířku přidáním navrhované šířky pomocí paddingStart a paddingEnd. Podobně by požadovaná výška byla navržena výška plus paddingTop & paddingBottom.
  • Vypočítejte skutečnou velikost s ohledem na omezení. Chcete-li to vypočítat, stačí předat specifikaci míry předanou vám v nástroji onMeasure () a požadovanou dimenzi v této metodě zvané resolSize (). Tato metoda vám řekne nejbližší možný rozměr vaší požadované šířky nebo výšky, přičemž bude stále respektovat omezení rodičů.
  • A co je nejdůležitější, musíte nastavit konečnou šířku a výšku v metodě onMeasure voláním sady setMeasuredDimension (změřená šířka, měřená výška), abyste uložili změřenou šířku a výšku tohoto pohledu, jinak byste mohli vidět, jak se váš názor zhroutí s IllegalStateException.

Umístění vašich názorů

Pomocí funkce onLayoutMethod můžeme umisťovat pohledy našich dětí. Kód může jednoduše zahrnovat iteraci nad jakýmkoli pohledem dítěte a přiřadit jim levý, horní, pravý a dolní limit v závislosti na měřených šířkách a výškách.

Nakreslení pohledu

Než začneme plátno používat, musíme pochopit několik věcí:

  • Malování: Třída Malování obsahuje informace o stylu a barvě o tom, jak nakreslit geometrie, text a bitmapy. Takto vytvoříme objekt malování.
mPaint = nová barva ();
mPaint.setColor (mDrawColor);
mPaint.setStyle (Paint.Style.STROKE); // default: FILL
// Vyhlazuje hrany toho, co je nakresleno, aniž by to ovlivnilo tvar interiéru.
mPaint.setAntiAlias ​​(true);
// Dithering ovlivňuje způsob vzorkování barev s vyšší přesností než u zařízení.
mPaint.setDither (true);
mPaint.setStrokeWidth (mStrokeWidth);

Více informací o vlastnostech si můžete přečíst zde.

  • Kreslení tvarů: Na plátno můžete přímo nakreslit tvary jako čára, oblouk, kruh atd. Podívejme se na níže uvedený diagram, abychom lépe porozuměli.
Popis, jak je na plátně nakreslen ovál, oblouk a obdélník
přepsat zábavu na Draw (canvas: Canvas) {

    super.onDraw (canvas)
    val cx = canvas.width / 2
    val cy = canvas.height / 2

    ovalRect.left = cx - circleRadius
    ovalRect.top = cy - circleRadius
    ovalRect.right = cx + circleRadius
    ovalRect.bottom = cy + circleRadius

    canvas.drawArc (ovalRect, 0F, 90F, true, mCircleFillPaint)
    canvas.drawOval (ovalRect, mCircleStrokePaint)
    canvas.drawRect (ovalRect, mRectStrokePaint)

}

Použití cest: Kreslení složitých tvarů výše uvedenými metodami může být trochu složitější, takže android nabízí třídu Path. Ve třídě Cesta si dokážete představit, že držíte pero a můžete nakreslit tvar, pak se možná přesunout na jiné místo a nakreslit jiný tvar. Nakonec, když jste hotovi, vytvořte cestu. Můžete jednoduše nakreslit cestu na plátně. Při používání cest můžete také použít různé efekty cest (podrobně diskutováno níže). Níže je uveden příklad tvaru vytvořeného pomocí cest.

val cx = canvas.width / 2
val cy = canvas.height / 2
val úhel = 2,0 * Math.PI / 5
// přesuňte se na pozici 1
path.moveTo (
        cx + (poloměry * Math.cos (0.0)) .Float (),
        cy + (poloměry * Math.sin (0.0)) .Float ())
// nakreslete všechny řádky do pozice 5
pro (i v 1 až 5) {
    path.lineTo (
            cx + (poloměry * Math.cos (úhel * i)) .Float (),
            cy + (poloměry * Math.sin (úhel * i)) .Float ())
}
// připojte se k poz. 5 s poz. 1
path.close ()
//, pokud chcete přidat kruh kolem mnohoúhelníku pomocí cesty
// path.addCircle (cx, cy, circleRadius, Path.Direction.CW)
// nakreslete mnohoúhelník
canvas.drawPath (cesta, mShapePaint)
  • Path Effects: Pokud použijete také efekt Rohové cesty na svůj objekt malování s určitým poloměrem, bude mnohoúhelník vypadat takto. Můžete také použít jiné efekty cesty, jako je DashPathEffect, DiscretePath atd. Pro kombinaci dvou různých efektů cesty můžete použít ComposePathEffect.
mShapePaint.pathEffect = CornerPathEffect (20f)
Mnohoúhelník využívající efekt rohové cesty
  • Kreslení rastrů: Chcete-li kreslit rastry na plátně, můžete použít
canvas.drawBitmap (bitamp, src, dest, paint)

bitmapa: Bitmapa, kterou chcete kreslit na plátně
src: Vezme správný objekt, který určuje část rastrové mapy, kterou chcete nakreslit. To může být null, pokud chcete nakreslit kompletní bitmapu.
dest: Řádkový objekt, který říká, kolik oblasti chcete na plátně pokrýt bitmapou
paint: Objekt malování, pomocí kterého chcete nakreslit bitmapu

Android automaticky provede všechna potřebná měřítka nebo překlad, aby se vešel do zdroje v cílové oblasti.

Na plátno můžete také kreslit kreslitelné předměty.

drawable.setBounds (vlevo, nahoru, vpravo, dole)
drawable.draw (canvas)

Než nakreslíte kreslitelnou část, budete muset nastavit ohraničení vaší kreslitelnosti. Vlevo, nahoře, vpravo a dole jsou popsány velikosti a jeho umístění na plátně. Upřednostňovanou velikost pro Drawables můžete najít pomocí metod getIntrinsicHeight () a getIntrinsicWidth () a podle toho rozhodnout o limitech.

Kreslení textů: Kreslení textů může být trochu bolestivé. Ne samotný výkres, ale zarovnání nebo měření textu. K tomu dochází, protože různé postavy mají různé výšky a aby bylo ještě horší, mohou existovat také různé typy písma. Aby bylo možné změřit výšku textu, bude třeba vypočítat konkrétní textové limity pro váš text, jako je tento.

textPaint.getTextBounds (yourText, startIndex, endIndex, rect)

Potom by konečný objekt předaný na konci obsahoval textové hranice skutečného textu, který má být nakreslen. Tímto způsobem můžete vypočítat skutečnou výšku textu, který má být nakreslen, a nastavit správnou základní linii y pro váš text. Chcete-li vypočítat šířku textu, měli byste použít textPaint.measureText (), protože je přesnější než šířka daná hranicemi malování textu (kvůli způsobu, jakým jsou tyto metody implementovány v knihovně lyží). Alternativně pro zajištění vodorovného vystředění textu na plátně stačí nastavit zarovnání barvy na TextAlign.CENTER a předat střed vašeho plátna v souřadnici x.

canvas.drawText (text, xPos, yPos, paint)

Kreslení víceřádkového textu: Pokud chcete zalomit čáry (\ n) nebo pokud máte pevnou šířku pro kreslení textu, můžete použít statické rozvržení nebo dynamické rozvržení. To by automaticky zpracovalo všechna zalomení slov nebo zalomení řádků a také by vám řeklo, kolik výšky by bylo potřeba k nakreslení textu v dané šířce.

// vytváření statického rozvržení
var builder = StaticLayout.Builder.obtain (text, 0, text.length (), textPaint, width);
StaticLayout myStaticLayout = builder.build ();
var heightRequired = myStaticLayout.height
// výkresová část
canvas.save ()
canvas.translate (xPos, yPos)
myStaticLayout.draw (canvas)
canvas.restore ()
  • Uložení a obnovení plátna: Jak jste si možná všimli, musíme plátno uložit a přeložit ho před nakreslením na něj a nakonec musíme plátno obnovit. Mnohokrát musíme kreslit něco s jiným nastavením, jako je otáčení plátna, jeho překládání nebo oříznutí určité části plátna při kreslení tvaru. V takovém případě můžeme zavolat canvas.save (), což by uložilo naše aktuální nastavení plátna do zásobníku. Poté změníme nastavení plátna (překlad atd.) A potom nakreslíme, co chceme s tímto nastavením. Nakonec, když skončíme s kresbou, můžeme zavolat canvas.restore (), což by vrátilo plátno do předchozí konfigurace, kterou jsme uložili.
  • Zpracování uživatelských vstupů: Nakonec jste si vytvořili své vlastní zobrazení pomocí atributů XML, ale co kdybyste chtěli změnit jakoukoli vlastnost za běhu, jako je poloměr kruhu, barva textu atd. Budete muset informovat Android API, aby odrážel Změny. Pokud nyní nějaká změna ve vlastnictví ovlivní velikost vašeho zobrazení, nastavíte proměnnou a zavoláte requestLayout (), která přepočítá velikost zobrazení a překreslí jej. Pokud se však změní vlastnost, jako je barva textu, budete ji muset pouze překreslit novou barvou textu a v tomto případě by bylo rozumné zavolat invalidate ().

Další poznámka: Nyní, pokud má váš pohled mnoho atributů, může se stát, že budete muset za každý setter psát invalidate () / requestLayout. Tento problém lze vyřešit pomocí kotlinových delegátů. Podívejme se na příklad níže, abychom byli jasnější.

var textColor: Int by OnValidateProp (Color.BLACK)
var circleColor: Int od OnValidateProp (Color.CYAN)
třída OnValidateProp  (soukromé pole var: T, soukromé vložené var func: () -> Unit = {}) {
    operátor fun setValue (thisRef: Any ?, p: KProperty <*>, v: T) {
        field = v
        invalidate ()

    }

    operátor fun getValue (thisRef: Any ?, p: KProperty <*>): T {
        návratové pole
    }

}

Nyní, pokud vím, že vlastnost, pokud by se změnila, by měla pouze překreslit pohled, inicializoval bych ho pomocí OnValidateProp, ale pokud by to mohlo ovlivnit velikost pohledu, inicializoval bych vytvořením nového delegáta OnLayoutProp.

Konečně! Začněte vytvořením vlastních vlastních pohledů. Pokud vás zajímá, jak vypadá skutečný vlastní kód zobrazení. Můžete se podívat na knihovnu, kterou jsem právě publikoval. Zobrazuje kroky spolu s popisy a pokrývá většinu věcí, o kterých jsem hovořil v tomto článku.

Na závěr zmíním některé z nejlepších postupů, které byste měli vždy zvážit při optimalizaci výkonu vlastního zobrazení.

  • Inteligentně zneplatnit: Nevolávat neplatnosti, dokud a dokud se něco viditelné pro uživatele nezmění. Zadruhé, když voláte neplatný, je-li to možné, předejte opravený objekt neplatnou metodou, abyste sdělili GPU, jakou část obrazovky je třeba nakreslit.
  • Nakreslete opatrně: Ne kreslete věci, které nejsou pro uživatele viditelné. Nakonec by to byl 2D povrch a bylo by zbytečné nakreslit něco, co se později překrývá s něčím jiným. Toho lze dosáhnout pomocí Canvas.clipRect (). Také nevykreslete něco, co je mimo hranice obrazovky. Toho lze dosáhnout pomocí canvas.quickReject ().
  • Nikdy nepřidělujte objekty v onDraw: onDraw () se nazývá 60krát za sekundu. i když jsou sběratelé odpadu opravdu rychlé, takže nemusí dojít k poklesu souvisejícímu s GC, ale běží na samostatném vláknu, což by znamenalo, že byste mohli jíst hodně baterie. Navíc od většiny času jsou objekty inicializované v programu onDraw kreslením objektů (obálky kolem C ++). Vede to k vyvolání mnoha destruktorů, a tedy spuštění finalizátorů, aby se získala paměť objektu, která není nikdy výkonná.