Személyes eszközök
Keresés

 

A InfoWiki wikiből


Tartalomjegyzék


Animációk

Az animációk teszik lehetővé az igazi dinamikus viselkedésű felhasználói felület tervezését. Gyakori effetkusok amikor egy gomb fölé húzott egér hatására a gomb háttere felfénylik, az ikonok nagyobbak lesznek, stb.

Az animációk kezelése a WPF részét képezi, ezért nem kell kódot írni, időzítőket használni, stb. E helyett be kell konfigurálni, megadni mennyi idő alatt milyen változások történjenek. Az animációk ideje alatt a vezérlők megmaradnak vezérlőknek, tehát amíg pl egy nyomógomb (button) animálódik, addig is megmarad nyomógombmak, vagyis rá lehet klikkelni, eseményeket vált ki, stb.

A rendszer alapötlete az jellemző-alapú animáció (property-based animation). Ennek során adott idő alatt egy dependecy-property értéke változik meg egy kezdőértékről egy végértékre, folyamatos átmenet során. Pl. egy gomb háttérszínét is egy ilyen dep prop írja le, melynek értéke #f981a0-ről #9b0028-re változik 1 másodperc alatt. Ekkor azt fogjuk látni, hogy a gomb színe fokozatosan világos vörösről sötét vörösre vált. A gomb szélességét is egy dep prop írja le, amely 20-ról 40-re vált, akkor azt fogjuk látni, hogy a gomb szélessége folyamatosan megnő. Az animálás során egy időben több dep prop is változhat, illetve megoldható, hogy a különböző dep prop értékek más-más időintervallum alatt érjék el a végértéket.

A rossz hír az, hogy ha olyan animációt szeretnénk elvégezni, amely nem írható le dependency property változtatással, akkor a baj elég nagy. Ugyanis ezen rendszerrel egyszerűen csakis ezek oldhatók meg. Tehát a DOOM IV megírására ez nem feltétlenül alkalmas.

Triggerek

Az animációkat el is kell indítani. Ehhez triggerek alkalmasak. Trigger lehet pl "ha föléhúzzák az egeret", vagy "ha kattintanak" stb. Általában olyanok lehetnek triggerek, amelyek eseményeket váltanak ki, tehát másképp fogalmazva egy esemény jelentkezése indíthat el egy animációt. Az esemény és az animáció ugyanazon a vezérlőn van jelen, tehát "ha erre a gombra kattintanak, akkor ugyanezen a gomb sötétjen el" jellegű dologra lehet gondolni. Az "ha a betöltés gombra kattintanak, akkor a mentés gomb sötétedjen el" dolog már kicsit bonyolultabb eset.

Másik mód, amikor kódból direkt kényszerítjük ki egy animáció indítását. Így lehet egyszerűen elérni, hogy egy gombra kattintás hatására egy másik vezérlőn induljon el egy animáció.

Animációs osztály

Amikor egy dependency property értékét kell egy kezdőértéktől egy végértékig módosítani, amely mondjuk double típusú érték, akkor szükség van egy olyan animációs osztályra (animation class), amely képes double típusú értéket adott időintervallumban eltolni (léptetni). Ha a típus ennél bonyolultabb (pl dátum típusú érték), akkor ehhez a típushoz illő animációs osztályt kell alkalmazni. Ha az adott típushoz nincs ilyen osztály, akkor azt a dependecy property értéket a rendszer nem tudja animálni.

Pl. a Button.Padding property Thickness típusú. Ennek animálásához létezik a ThicknessAnimation osztály (szerencsére).

Amennyiben egy típushoz nem létezne animációs osztály, készíthetünk egyet. A rendszerbe eleve elkészített animációs osztályok a System.Windows.Media.Animation névtérben vannak (szerencsére elég szép számban).

Sok olyan típus van, amelyhez nincs értelme animációs osztályt készíteni. Tipikus példa erre a felsorolás típusok (enum). Egy panel ahol felsorolás írja le a tartalmazott elemek elhelyezkedését (HorizontalAlignment), ott ezen érték animációja nem vezethet semmi látványos effektushoz, ezért nincs kidolgozva.

Hasonlóképp referencia-típusokat nem lehet animálni (bár azok al-jellemzőit (sub-properties) igen. Egy Brush objektum animációja értelmetlen, de a Brush objektum Color propertyjének animációja már igen.

Amikor egy adott értéket viszünk át adott idő alatt egy kezdőértékből egy végértékig, több módon is megtehetjük azt:

  • lineáris átmenet (linear interpolation)
  • kulcsérték-átmenet (key frame animation)
  • ösvény-alapú átmenet (path-based animation)

Az utóbbi esetben adott időpillanatokhoz rögzítjük az adott értéket. Tehát például 0.1 másodperces időpontokhoz (key frame) megadjuk hogy ezen az időpillanatban mi legyen az éppen aktuális érték. Ha ezt elég kis időközönként megadjuk, akkor akár folyamatosnak tűnő animációt is leírhatunk vele, bár így ritkán használjuk. Ugyanakkor egy string esetén, ahol a lineáris átmenetet nem lehet értelmezni, a kulcsérték-átmenetet minden további nélkül. Sőt. Előzőekben indokoltuk, miért nem létezik minden típus esetén lineáris átmenet, vagy nem megvalósítható, de szögezzük le, hogy kulcsérték-átmenet minden típus esetén megvalósítható!

A lineáris átmenetet leíró animációs osztályok neve jellemzően <típusnévAnimation>, vagyis pl. DoubleAnimation. A kulcsérték-átmenetet megvalósító osztályok nevének végén a ...UsingKeyFrames áll, vagyis van például StringAnimationUsingKeyFrames osztály.

Az ösvény-alapú átmenet nagyon kifinomult technológia. Segítségével megadhatjuk hogy a változás milyen dinamikájú legyen. Pl. egy kontroll mozogjon át a képernyő bal felső sarkából a jobb alsó sarkába úgy, hogy közben két kunkort írjon le. Ezt a lineáris átmenettel nem lehet leírni, akkor egy egyenes mentén mozogna a vezérlő. A kulcsérték-átmenettel le lehetne írni, de nagyon sok kulcsértéket kellene megadni, hogy a mozgás a köriveken folyamatosnak tűnjön, ne legyen szögletes. Egy PathGeometry osztálybeli példányban megvalósított alak (shape) segítségével le lehet írni az ösvényt, amelyet a kontrollnak követnie kell. Ezen példány le fogja generálni a szükséges kulcsértékeket (key frames), amely leírja a mozgást.

Az ösvény alapú animációt támogató osztályok nevének vége ...UsingPath, vagyis létezik DoubleAnimationUsingPath osztály.

Természetesen a fenti három animációs technika mellett megvalósíthatunk saját technikákat is, tehát a rendszer bővíthető. A saját technikáink azonban ugyanazon az elven kell működjenek: értéket kell változtatni az idő előrehaladásának függvényében.

A rendszerbeli osztályok (és a saját osztályaink is) a <típusNévAnimationBase> osztály leszármazottja kell legyen.

Animációk a kódban

A legegyszerűbb animáció a lineáris, amikor is adott időszakasz alatt a az értéket kezdőértékből kiindulva át kell vinni a végértékbe. Ha a kezdőérték 1.0, a végérték 10.0, az idő pedig 2 másodperc, akkor lesz olyan időpont, amikor az értékünk 1.0, 1.1, 1.2, ... értékű, egészen amíg el nem éri a 10.0 -t.

A rendszer 60 frame / sec sebességgel animál, tehát 2 másodperc alatt 120 frame-t generál le, így a 1.0 -ból a 10.0 elérése 120 lépésben történik majd meg. Megjegyzés: a 60 frame / sec az alapérték, ez azonban tuningolható.

Amikor kódból akarunk animációt kezdeményezni, a lépések a következők:

  • példányosítunk egyet a szükséges animációs osztályból (pl. DoubleAnimation)
  • bekonfiguráljuk (kezdőérték, végérték, időintervallum, stb)
  • BeginAnimation()' -al elindítjuk az animációt
DoubleAnimation widthAnimation = new DoubleAnimation();
 widthAnimation.From = 160;
 widthAnimation.To = this.Width - 30;
 widthAnimation.Duration = TimeSpan.FromSeconds(5);
 cmdGrow.BeginAnimation(Button.WidthProperty, widthAnimation);

A fenti példában a cmdGrow egy vezérlő (jelenleg egy nyomógomb), amelynek a például a Click() metódusához rendelhetjük hozzá ezt a kódsort. Azonban vegyük észre, hogy ameddig az animáció tart (5 másodperc), a nyomógombra újra rá lehet kattintani, amely esetben az animáció újraindul. Ennek valódi oka, hogy egy dep prop-hoz egy időben csak egy aktív animáció tartozhat. Tehát a futó animációt a rendszer automatikusan leállítja, és új animációt indít el.

Animációk megszakítása

Ugyanakkor nagyon zavaró, hogy a gomb szélessége már elkezdett nőni, amikor is az animációt újraindítjuk - s így a gomb szélessége először is visszaugrik a kezdeti állapotra (160 egység), majd elkezd nőni, a helyett hogy az aktuális méretét használni kiindulási alapként. Ezt egyébként kódból könnyű korrigálni: amennyiben nem töltjük ki a From értéket az animációnál, úgy az aktuális értéket használja kiindulási alapként. Ez azonban csak akkor működik, ha a dep prop-nak van beállított értéke. Jelen példában ez azt jelenti, hogy a vezérlőnknek van beállított szélessége. Amennyiben a vezérlőnket egy layout panel-be tesszük bele, úgy a szélesség-értéket nem beállítjuk, hanem átveszi a konténerének Alignment propertyjéből, és a szélességének értéke Double.NaN (NaN = not a number). Ezt egy animáció nem használhatja kiindulási értékként.

Jelen példában (és sok más esetben is) használható az alábbi kód:

widthAnimation.From = cmdGrow.ActualWidth;

A To érték kitöltetlensége

Amennyiben kitöltetlenül hagyjuk a To értéket az animációs beállításokban, az is az alapértelmezett értéket veszi fel (amennyiben az létezik). Ha mind a From, mind a To értéket kitöltetlenül hagyjuk, úgy mindkettő ugyanazon alapértelmezett értéket használja, és az animáció nem csinál semmit.

A To érték kitöltetlenül hagyásának néha van értelme. Képzeljük el azt az esetet, amikor a nyomógombunkra írt egyik animáció a nyomógomb szélességét növeli meg 100 egységről 300 egységre 5 másodperc alatt. Ezen animáció alatt azt megszakítjuk, és elindítunk egy ilyen To nélküli animációt, ahol a From az aktuális szélességből indul ki. Ekkor a To az XAML-ben leírt szélesség lesz (a 100 egység), és az animációnk a gomb szélességét vissza fogja csökkenteni erre az eredeti méretre.

A By használata

A To megadása helyett használhatjuk a By értéket is, ahol nem konkrét végérték megadása szükséges, hanem valamekkora növekmény.

DoubleAnimation widthAnimation = new DoubleAnimation();
 widthAnimation.By = 10;
 widthAnimation.Duration = TimeSpan.FromSeconds(0.5);
 cmdGrowIncrementally.BeginAnimation(Button.WidthProperty, widthAnimation);

Ekkor az animáció jelentése: a szélességet növeld meg 10 egységgel, függetlenül az aktuális értéktől. Persze ez így is megadható lenne:

widthAnimation.To = cmdGrowIncrementally.Width + 10;

De ez a módot nem lehet leírni XAML-ben, csak kódban, így a By megadását elsősorban XAML-beli megadáskor használjuk.

Tegyük hozzá, hogy a By megadásának néha egyszerűen nincs értelme. Pl Color property animációját nehéz By módon megfogalmazni.

Az additív megadás

A By műkdödéséhez hasonló eredményt érhetünk el az additív animáció használatakor. Ezt az animáció IsAdditive=true megadásával érhetjük el.


DoubleAnimation widthAnimation = new DoubleAnimation();
 widthAnimation.From = 0;
 widthAnimation.To = -10;
 widthAnimation.Duration = TimeSpan.FromSeconds(0.5);
 widthAnimation.IsAdditive = true;

Ekkor a From és To értékek nem explicit értékeket képviselnek, hanem relatív értéket.


Az időhosszúság

Az időhosszúságot (Duration) ezredmásodpercben (millisecond) kell megadni. Ezt jellemzően kódban a TimeSpan segítségével állítjuk be, de az animációs osztályok ezt Duration típusban várnák. Szerencsére a .NET tartalmaz implicit típuskonverziót a TimeSpan és a Duration osztályok között. Ezért az alábbi kód szintaktikailag megfelelő:

widthAnimation.Duration = TimeSpan.FromSeconds(5);

De a valódi értelme az alábbi:

widthAnimation.Duration = (Duration) (TimeSpan.FromSeconds(5));

Valójában azért nem direktben TimeSpan típusú ez a mező, mert a Duration típus kicsit több annál, mint egy egyszerű ezredmásodperc érték. Tartalmaz még egy Atutomatic és egy Forever mezőt is, amelyekről később még szó lesz.

Párhuzamos animációk

Amikor több property-t akarunk animálni párhuzamosan, több animációs osztályt is példányosíthatunk, majd gyors egymás után kiadhatjuk mindegyik példányra a BeginAnimation()-t. A begin aránylag gyors lefutású metódus, így az animációk majdnem egy időben indulnak így el, de mégsem egyszerre. Másképp fogalmazva - az animációk nincsenek szinkronizálva. A korábban indított animációk kicsivel hamarabb be is fejeződnek. Ez persze zavaró is lehet.

Van arra mód, hogy sok animációt indítsunk el egymáshoz szinkronizált módon, de ehhez meg kell tanulni a storyboard fogalmát és használatát.

Animációs életciklus

Az animációk ideiglenes hatásúak. Futási idejük alatt a szóban forgó értéket módosítják az idő függvényében, de valójában nem változtatják meg az értéket magát. Ennek hátterében a dependency property működési mechanizmusa áll.

Amennyiben egy animáció lefutott, aktív marad, és a végértéket beállítva tartja. Ez azt jelenti, hogy kódból megpróbálhatjuk ezen értéket módosítani, de nem lesz hatással, mivel a még mindíg futó animáció végértéke magasabb prioritású, és nem engedi az új értéket módosíttatni.

Ekkor az alábbiak közül választhatunk:

  • az érték módosítása helyett létrehozhatunk egy másik animációt, és kihasználjuk hogy egy időben csak egy animáció lehet aktív. Ekkor ezen új animáció végértéke lesz a magasabb prioritású, és így be tudjuk állítani vele az új értéket.
  • készíthetünk egy megfordítható animációt (AutoReverse=true beállítás). Ekkor az animáció a végén automatikusan megfordul, és ugyanazon beállításokkal visszafele lejátsza magát, és a végén az eredeti kezdőértékre áll be. Ekkor az animáció teljes ideje természetesen a beállított duration idő kétszerese.
  • be tudjuk állítani a FillBehaviour tulajdonságot. Ennek alapértéke a HoldEnd, ami pontosan a fent leírtakat jelenti, vagyis az animáció a befejezéskor aktív marad, és a végértéket folyamatosan beállítva tartja. Másik lehetséges értéke a Stop. Ebben az esetben az animáció a végén leáll, és a property azonnal felveszi a kezdőértékét újra.
  • le tudjuk kezelni az animációs példány completed eseményét oly módon, hogy amikor az bekövetkezik, akkor az animációs példányt megszüntetjük. Ekkor az animáció nem marad aktív, s ily módon nem akadályozza tovább az érték módosítását.

Ez utóbbi az alábbi módon történhet meg. Először is fel kell íratkozni az eseményre:

widthAnimation.Completed += animation_Completed;

Majd el kell indítani egy új, null animációt, és gondoskodni kell róla, hogy a lokális értéke a dependency propertynek be legyen állítva arra az értékre, ahol az animáció leállt:

cmdGrow.BeginAnimation(Button.WidthProperty, null);
cmdGrow.Width = currentWidth;

A TimeLine osztály

Kép:wpf_animation.png

Mint a képen is látszik, a TimeLine osztály absztrakt ősosztályként szolgál minden animáció alapjaként. Ebből három gyermekosztály származik:

  • MediaTimeLine: audió és videó file-ok lejátszásakor használt
  • AnimationTimeLine: property alapú animációk készítésekor használt
  • TimeLineGroup: több animáció szinkronizált lejátszásakor használt

Magában a TimeLine osztályban több lényeges elem is megtalálható:

  • BeginTime az animáció kezdetekor késleltetést lehet vele megadni. Ezen idő a teljes időhöz adódik hozzá később, amikor a teljes időszükségletet számítjuk ki. Ez hasznos lehet ha sok animáció indul el egy időben, de valójában valamelyiket később akarjuk indítani.
  • Duration a futási időszükségletet definiálja
  • SpeedRatio alapértéke 1.0, ezzel tudjuk gyorsítani/lassítani a lefutást. Amikor az értéke 5.0, akkor az ötszörös sebességet jelent, a 0.5 érték pedig felezett sebességet jelent. De ezen beállítás nincs hatással a BeginTime értékére, csak a Duration-ra.
  • AccelerationRation és DecelerationRatio gyorsítás az elején, vagy lassítás a végén (lásd külön leírás)
  • AutoReverse ha ezt 'true'-ra állítjuk, az animációt lejátsza visszafele is, és a végén visszaáll minden az eredeti állapotába. Visszajátszáskor a 'BeginTime'-t nem fogja figyelembe venni.
  • FillBehavior a 'HoldEnd' érték estén az animáció végén beállítva tartja a végértékeket, 'StopEnd' esetén visszaáll az eredeti állapotba.
  • RepeatBehavior megadott ismétlésszámmal, vagy ismétlési időn belül újra és újra lejátsza az animációt (attól függően, hogy a beállított, használt

RepeatBehavior objektum miként rendelkezik).


Gyorsítás, lassítás

Az AccelerationRatio és DecelerationRatio segítségével az időszalag valamely szakaszát tömöríthetjük, hogy az ezen időszak alatt lezajlódó események gyorsabban hajtódjanak végre. Az időszalag maradék részei cserébe megnyúlnak, hogy kompenzálják ezt a viselkedést, és az animáció össz-ideje ne változzon meg.

Mindkét érték százalákban értendő. Pl. az AccelerationRatio értéke amennyiben 0.3, azt jelenti, hogy az időszalag első 30%-t kívánjuk gyorsítani, vagyis ha az animáció összideje 10 másodperc, akkor az első 3 másodperc lesz gyorsított. Természetesen a maradák 7 másodperc egy picit lassítva lesz, hogy kompenzálja ezt a gyorsítást, és a teljes idő maradjon 10 másodperc.

Amennyiben az 'Accel' értéke 0.3, a 'Decel' értéke 0.2, akkor az első 3 másodperc gyorsított, az utolsó 2 másodperc lassított, a középső 5 másodperc pedig adott (fix) sebességű lesz. Érthető okokból a két érték összege nem haladhajta meg az 1.0-t.

Ezen módszernél kifinomultabban tudjuk az animációnk sebességét szabályozni, akár több gyorsítás-lassitás effektet is alkalmazni menet közben a kulcsértékekkel (keyframe). Ekkor az animációnkat gyakorlatilag több szakaszra osztjuk fel, a szakaszokat ilyen kulcsértékekkel választva el, és a kulcsértékeket az időszalagon közelítve-távolítva egymástól elérhetünk gyorsított átmeneti szakaszokat. Ez persze manuálisan jelentősen több munkát jelent, de teljes mértékben uralhatjuk az animációnk viselkedését.

RepeatBehavior

Az alábbi kódban létrehozunk egy olyan RepeatBehavior példányt, amely kétszer ismételteti meg a teljes animációnkat.

widthAnimation.RepeatBehavior = new RepeatBehavior(2);

Amennyiben az animáció összeadó (IsCumulative), akkor az animáció ismétlése során nem mindíg ugyanarról a kezdőértékről indul, hanem az előző ismétlés végértékéről indul az új animáció.

Az alábbi változatban nem adott ismétlésszámmal, hanem adott időszakaszon belül történik az animáció újraindítása:

widthAnimation.RepeatBehavior = new RepeatBehavior(TimeSpan.FromSeconds(13));

Ha az animáció pl. 5 másodperces, akkor egy ilyen 13 másodperces ismétlési idő beállítása azt okozza, hogy az animáció kétszer lefut teljesen, és a harmadik ismétlődés közepénél abbamarad ott, ahol éppen tart. Ezen eszközzel lehet egy animációt részben lejátszani.

Az alábbi beállítással pedig az animációt örökké ismételni fogjuk:

widthAnimation.RepeatBehavior = RepeatBehavior.Forever;
Hernyák Zoltán
A lap eredeti címe: „http://wiki.ektf.hu/wiki/Wpf:page23
Nézetek
nincs sb_3.144.172.115 cikk