XAML.cz Magazín moderních technologií založených na XAML

Animácie vo WPF

Napsáno pro WPF od Martin Bodocky [05.04.2010]

Dobrý den,

dneska sa Vám chcem predstaviť aj Vám ukázať základy z vytvárania animácii pomocou WPF.  Každý vo svojich programoch chcel niečo animovať buď kvôli zákaníkovi alebo len kvôli sebe aby to malo požadovaný efekt. Keď sme chceli niečo animovať alebo vyvolať akciu nad uživateľských prostredím siahli sme po objekte Timer a pomocou jeho události Tick sme danú akciu vykonali. Ale z príchodom WPF k nám preniká riešenie, ktoré svojou eleganciou a pohodlnosťou vyhráva. Ukážme si ako by sme animovali napríklad pomocou objektu DispatcherTimer :

DispatcherTimer tmr = new DispatcherTimer();
tmr.Interval = TimeSpan.FromSeconds(0.1);
tmr.Tick += new EventHandler(tmr_Tick);
tmr.Start();

void tmr_Tick(object sender, EventArgs e)
{
      //pri akcii sa zmeni velkost tlacitka
      btn.FontSize += 2;
      //az po maximalnu hodnotu
      if (btn.FontSize >= maximalnaFontSize)
      {
          //nastavi sa povodna velkost pisma a ukonci casovac
          btn.FontSize = povodnaFontSize;
         (sender as DispatcherTimer).Stop();
      }
}

Ako vidíte DispatcherTimer každú sekundu zavolá metódu tmr_Tick a tá zvyšuje veľkosť písma tlačitka po určitú hranicu vypne sa a nastaví pôvodnú hodnotu. A čo si myslíte, že sa stane keď na tlačítko kliknete behom vykonávania “animácie” ? Ako určite tušíte vytvorí sa novy objekt DispatcherTimer a budú upravovať veľkosť písma dve události Tick.

Toto sa vo WPF stať nemôže,tu sa pri ďaľšom vyvolaní události prechádzajúca animacia hneď ukončia.

Animácie

Vo WPF sa používa na animovanie 22 typov objektov (pribudli ďalšie v .NET 4.0). Tak a teraz si ukážeme ako by sme vytvorili rovnakú animáciu ale už pomocou animačných objektov WPF, pre tento príklad je priam stvorený objekt DoubleAnimation, ktorý pracuje z číslom double, ktorým budeme reprezentovať veľkosť písma :

//vytvorime animacny objekt DoubleAnimation
DoubleAnimation animacia = new DoubleAnimation();
//nastavime dlzku animacie
animacia.Duration = new Duration(TimeSpan.FromSeconds(2));
//nastavime zaciatocnu hodnotu
animacia.From = povodnaFontSize;
//nastavime koncovu hodnotu
animacia.To = maximalnaFontSize;
//nastavime konecne chovanie
animacia.FillBehavior = FillBehavior.Stop;
//spustime/pripojime animaciu
btn.BeginAnimation(Button.FontSizeProperty, animacia);

Objekt DoubleAnimation definuje vlastnost postupného zmenšovania/zväčšovania nejakej závislej vlastnosti objektu, ktorá dokáže pracovať z premennou v našom prípade to je Button a vlastnosť FontSize, celý priebeh animácie sa vykoná za určitý čas Duration 2 sekundy.Pomocou výčtu FillBehavior na Stop sme nastavili aby sa objekt po animácii vrátil do svojho pôvodneho nastavenia. V dnešnom dieli si opíšeme iba základné objekty na tvorbu animácii a to tieto:

  • DoubleAnimation
  • ColorAnimation

a predstavíme si tieto spôsoby spúšťania animácii:

  • Spúštanie v kóde – už sme si ukázali
  • Pomocou Eventtriggeru
  • Pomocou Trigger.EnterActions,Trigger.ExitActions

DoubleAnimation

Ešte raz sa vrátime k nášmu prvému príkladu a pretože toto je magazín o technológiach založených na XAML, musíme si túto animaciu ukázať v tomto jazyku, na vyvolanie použijeme udalosti definovane ako EventTrigger, ktorý sa na vyvolávanie animácii najviac používa ako aktivačnú udalosť sme použili Button.Click:

<Button Name="btn3" Margin="24" FontSize="16" Padding="5"
        Content="Tlačítko">
    <Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard SpeedRatio="2.0">
<DoubleAnimation Storyboard.TargetProperty="FontSize"
Duration="0:0:2"
From="16" To="35" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>

Tento príklad môžete použiť na vizuálne potvrdenie stlačenie tlačítka, aby užívateľ mal očividnú spätnú odozvu.

DoubleAnimation obsahuje atributy:

  • Storyboard.TargetProperty – popisuje akú vlastnosť budeme modifikovať

  • Storyboard.TargetName – od akého objektu budeme vlastnosť modifikovať, hned si ukážeme ako funguje

  • From – hodnoda, od ktorej sa animácia začína vykonávať

  • To – hodnota, pri ktorej sa animácia ukončí

  • Duration – trvanie animácie, presný čás priebehu medzi hodnotou From po To

  • AutoReserve – animácia sa vykoná ešte raz s opačnými hodnotami From a To

  • SpeedRatio – nám zrýchlí animáciu ukážeme si v ďaľšom príklade

  • IsAdditive – postupne zvyšuje hodnoty From a To, a to o ich rozdiel

  • RepeaterBehavior – označuje počet opakovaní animácie

  • FillBehavior – spôsob ukončenia animácie

  • BeginTime – môžeme skrátiť alebo predĺžiť animáciu

Atribút Duration

Atribút Duration má nasledovný format {hodiny}:{minuty}:{sekundy}, napríklad 12 hod = 12:0:0 alebo 5,6 sekundy 0:0:5.6.

Ukážeme si príklad z dvoma tlačítkami, ktoré budú na seba odkazovať :

<Button Name="btn1" Margin="24" FontSize="16" Padding="5"
Content="Tlačítko">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="FontSize"
Storyboard.TargetName="btn2"
Duration="0:0:2"
FillBehavior="Stop"
From="16" To="35"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>

<Button Name="btn2" Margin="24" FontSize="16" Padding="5"
Content="Tlačítko">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="FontSize"
Storyboard.TargetName="btn1"
Duration="0:0:2"
AutoReverse="True"
From="16" To="35"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>

Cez parameter Storyboard.TargetName sa odkazujeme k objektu, ktorého vlastnosť chceme animovať. Táto vlastnosť ma veľa využití keď chcete zviazať vlastnosti viacerých uživateľských objektov. Pri kliknutí na prvé tlačítko sa animácia vykoná na tlačítku druhom, a to jeho zväčšením a poďľa atribútu FillBehavior, ktorý je nastavený na Stop sa vráti na pôvodnú veľkosť. Keď klikneme na druhé tlačítko spustí sa animacia na prvom a poďla zapnutého atribútu AutoReverse sa po vykonaní animácie vykoná ešte raz ale spätne, laicky povedané vrátia sa do svojho pôvodného stavu tou istou rychlosťou ako animácia.

Atribút SpeedRatio

Teraz sa pozrieme na atribut SpeedRatio:

<Button Name="btn3" Margin="24" FontSize="16" Padding="5"
Content="Tlačítko">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard SpeedRatio="2.0">
<DoubleAnimation Storyboard.TargetProperty="FontSize"
Duration="0:0:2"
AutoReverse="True"
From="16" To="35"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>

Tento atribút násobne zrýchluje animáciu, v našom príklade dvakrát rýchlejšie, čo vo výsledku bude trvať iba jednu sekundu. Využitie je treba hľadať pri optimalizácii rýchlosti animácie, kde si najprv urobíme pomalú animáciu, kde navrhne všetky jej akcie a pri finálnom zostavení nemusíte prepisovať všetky časy, iba nastavit percento zrýchlenia alebo spomalenia.

Atribút IsAdditive

Ďaľšia ukážka bude na atribút IsAdditive:

<Button Name="btn4" Margin="24" FontSize="16" Padding="5"
Content="Tlačítko">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="FontSize"
Duration="0:0:2"
IsAdditive="True"
AutoReverse="True"
From="16" To="35"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>

Tento atribút, je od ostatných odlišný zvyšovaním hodnoty From postupne svojou vlastnou pôvodnou hodnotou. Ale pozor na situáciu keď nastavíte atribút FillBehavior na HoldEnd, to zostane objektu v konečnej animovanej podobe, z tohoto dôvodu bude From pri každom ďaľšom stlačení rovné To,a pri tom sa To zväčší o From. Táto vlastnosť sa dá použiť v grafoch, kde pri každom kliku zvýši hodnotu nejakého prvku napríklad stĺpca v grafe.

Atribút RepeaterBehavior

Ukážeme si ako na opakovanie s atribútom RepeaterBehavior:

<Button Name="btn5" Margin="24" FontSize="16" Padding="5"
Content="Tlačítko" >
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="FontSize"
Duration="0:0:0.5"
AutoReverse="True"
RepeatBehavior="2x"
From="16" To="35"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>

Tento príklad vykoná animáciu nad objektom presne dvakrát, ako mu to prikazuje atribút RepeatorBehavior. Tento atribút nesie písmenko “x” ako symbol násobku, ale keď chceme definovať nekonečno zapíšeme Forever.

Táto vlastnosť bude pre vás užitočná, ked budete niekedy robiť animáciu na spôsob banneru.

Atribút BeginTime

Čo ak by ste chceli začať animáciu od jej stredu alebo pred spustením počkať dve sekundy? Určite vás zaujme atribút BeginTime:

<Button Name="btn6" Margin="24" FontSize="16" Padding="5"
Content="Tlačítko">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="FontSize"
BeginTime="0:0:1"
Duration="0:0:2"
FillBehavior="Stop"
From="16" To="35"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>

V tomto prípadne sme chceli, aby naša animácia začala až po uplynutí jednej sekundy, táto vlastnosť sa dá použiť napríklad vtedy, ak chcete po stlačení tlačítka vystrašiť  užívateľa. A to keď nastavíme atrubút BeginTime na zápornú hodnotu:

<Button Name="btn7" Margin="24" FontSize="16" Padding="5"
Content="Tlačítko">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="FontSize"
BeginTime="-0:0:1"
FillBehavior="Stop"
Duration="0:0:2"
From="16" To="35"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>

Animácia začne hneď ale bude mať hodnoty ako keby bola už jej polovici, lebo sa odčíta BeginTime od Duration. Ale pozor, záporný BeginTime nesmie byt nikdy vačší ako Duration vyvolá to výnimku. Náš vysledok je taký , že naša animácia začína v prvej sekunde a musí mať aj hodnoty, ktoré by mala mať keď by nebola zadržaná a to ked animácia trvá 2 sekundy má prejsť od hodnoty 16 k 35, čiže v čase jednej sekundy bude mať hodnotu 27,5 (35-16/2 + 16). Ako samostaný prvok nemá opodstatnenie ale z kombinaćiou z inými ho využijete.

Trigger.EnterActions,Trigger.ExitActions

Týmto sme ukončili základné atribúty animačnej triedy, ďalej budem prezentovať ako sa dá animácia ešte spustiť. Ukážeme rozdiel medzi spúštaním animácii pomocou EventTriggeru a udalosťami Trigger.StartAcion a Trigger.EndAction. Vytvoríme si dve polia objektov tlačítok,na ktorých si to ukážeme.

<StackPanel>
    <StackPanel.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="Padding" Value="10" />
<Setter Property="FontSize" Value="14" />
<Style.Triggers>
<!--Vstúpime myšou nad tlačitko -->
<EventTrigger RoutedEvent="Button.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="FontSize"
To="36" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<!--Odídeme myšou z tlačítka-->
<EventTrigger RoutedEvent="Button.MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="FontSize"
To="12" Duration="0:0:0.25" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<Button>Tlačítko</Button>
<Button>Tlačítko</Button>
<Button>Tlačítko</Button>
<Button>Tlačítko</Button>
</StackPanel>

Prvá animácia sa nám spustí pri udalosti Button.MouseEnter, čiže vstup myšou nad objekt Button. Druhá pri udalosti Button.MouseLeave odchode myši z objektu Button. Ako vidíte, vo WPF máte nad všetkým voľnú ruku a môžete odchytiť události nad tlačítkom ľubovolne.

<StackPanel>
    <StackPanel.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="Padding" Value="10" />
<Setter Property="FontSize" Value="14" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="FontSize"
To="36" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="FontSize"
To="12" Duration="0:0:0.25" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<Button>Tlačítko</Button>
<Button>Tlačítko</Button>
<Button>Tlačítko</Button>
<Button>Tlačítko</Button>
</StackPanel>

Pri spustení tejto animácie stačí, aby vlastnosť objektu Button IsMouseOver bola nastavená na True, čo nastane pri prechode nad objektom Button. Tu vidíte druhý prístup, ako vyvolať animáciu, keď sa jedná o tlačítko. Nemusíte písať dva EventTriggery, stačí aj jeden obyčajný Trigger, ktorý iba kontroluje nejakú premennú čiže sa nemusí vôbec jednať o udalosť, nie je to pekné?

Ešte si ukážeme jeden príklad z tlačítkom, aby ste mali dostatok inšpirácie na písanie animácii pomocou triedy DoubleAnimation a tým bude “trasiace” sa tlačítko:

<StackPanel>
    <StackPanel.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="Padding" Value="10" />
<Setter Property="FontSize" Value="14" />
<Setter Property="RenderTransformOrigin" Value="0.5 0.5" />
<Setter Property="RenderTransform">
<Setter.Value>
<RotateTransform />
</Setter.Value>
</Setter>
<Style.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard TargetProperty="RenderTransform.Angle">
<DoubleAnimation
From="-15" To="15" Duration="0:0:0.05"
AutoReverse="True"
RepeatBehavior="5x"
FillBehavior="Stop" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<Button>Tlačítko</Button>
<Button>Tlačítko</Button>
<Button>Tlačítko</Button>
</StackPanel>

Ako vidíte nastavujeme vlastnosť RenderTransformOrigin, ktorá nám nastaví stred tlačítka určeného na otáčanie. Ďalej si nastavíme vlastnosť RenderTransform na novú instanciu objektu RotateTransform. Pri vykonávaní animácie sa odkážeme na vlastnosť Angle, ktorá nám pomocov stupňov dovolí otáčat tlačitko na strany. Zas v inom článku si môžeme ukázať, ako s tlačitkom urobiť rotáciu do strán alebo prekladanie. Všetky prípadne námety  pište pod článok.

ColorAnimation

Nakoniec nášho článku si ukážeme prácu z triedou ColorAnimation. Ako sme menili v prechádzajúcich príkladoch čísla typu double, tak teraz budeme meniť farby cez objekt Color.

<Button Name="btn" FontSize="50" Margin="30" Content="Tlačítko">
<Button.Background>
<LinearGradientBrush x:Name="brush">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="0.5" Color="Yellow"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Button.Background>
<Button.Triggers>
<EventTrigger RoutedEvent="Page.Loaded">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="brush"
Storyboard.TargetProperty="GradientStops[0].Color"
From="Red" To="Yellow" Duration="0:0:1"
AutoReverse="True" RepeatBehavior="Forever"/>
<ColorAnimation Storyboard.TargetName="brush"
Storyboard.TargetProperty="GradientStops[1].Color"
From="Yellow" To="Blue" Duration="0:0:1"
AutoReverse="True" RepeatBehavior="Forever"/>
<ColorAnimation Storyboard.TargetName="brush"
Storyboard.TargetProperty="GradientStops[2].Color"
From="Blue" To="Red" Duration="0:0:1"
AutoReverse="True" RepeatBehavior="Forever"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>

Tu sme si vytvorili tlačítko, ktorému sme nastavili farbu pomocou LinearGradientBrushu. Je to objekt, ktorý pomocou farebných stôp s názvom GradientStop poskladá požadovanú farbu. O týchto štetcoch sa pobavíme v iných článkoch. Vráťme sa späť k našej animácii tvorenej tromi animačnými objektmi ColorAnimation, každý z týchto objektov mení farbu jednému štetcu, ktorý tvorí farbu pozadia nášho tlačítka.

A to je pre dnešok všetko, všetky zdrojové kódy sú k dispozícii a zostavené v jednej aplikácii na spôsob navigačného panelu, tak si ho určite pozrite ;-)

Komentáře

ukládám komentář, vyčkejte prosím..
  1. Nazdárek, super článek pro nakopnutí pro začátek, ale nebylo by fajn přiložit i živé ukázky? Zdrojáky jsou fajn, ale přece jen by bylo lepší vložit živou ukázku, třeba na samostatnou stránku, abych měl hned lepší představu, co to vlastně dělá. Já vím, bude s tím dřina, zvláště u tak na příklady obsáhlého článku, ale lidé to jistě ocení :)

    05.04.2010 @ 11:31
  2. Ahoj, jenom se zeptám, jak by taková živá ukázka měla vypadat? Měl by to být vložený Silverlight nebo spíše XBAP?

    Díky

    05.04.2010 @ 13:19
  3. No, tak v tomto případě by byl asi lepší XBAP, protože stránka s tolika vloženými SL objekty by mohla myslím na chvíli pěkně zapřáhnout PC, ale v případě, kdy výstupem celého článku bude jeden konečný stav, tak může být klíďo vložený SL.

    05.04.2010 @ 13:59
  4. Ahoj, jsme domluveni, že pokud to půjde, tak by ukázky byly v Silverlightu, tam nevidím problém s vložením do stránky a jelikož se jedná o vývojářský web, tak se dá předpokládat, že asi většina bude mít nainstalovánu jednu z posledních či nejspíše poslední verzi. S čistým WPF si zkusíme nějak poradit, aby to bylo taktéž čtenářům příjemné.

    Určitě však díky za odezvu.

    06.04.2010 @ 08:57

@xamlcz

  • RT @jvanrhyn: XAML, It's a bit like olives. Takes a while to get used to. But once you're used to it. It is actually pretty good. <3 XAML
  • RT @moser_christian: WPF Inspector 0.9.7 is released. It supports .NET 3.5 and 4.0 The project is now open source and available on CodeP ...
  • Jeff Handley oznámil vydání WCF RIA Services v.1.0 SP1 RTM http://bit.ly/gOgckn ke stažení na http://bit.ly/gVAXdK
  • jedna výzva pro Brno. Byl někdo z vás na přednášce o RIA v MS Akvárku? Dejte o sobě vědět. Děkuji
  • také jste uvažovali o tom, že zkusíte na projekt použít Caliburn Micro nebo naopak Prism 4? A co tak obojí, šlo by to nebo ne? Již brzy