Freigeben über


Windows mit C++

DirectComposition: Transformationen und Animationen

Kenny Kerr

Kenny KerrDirectComposition-Visuals bieten viel mehr als nur den Versatz und die Content-Eigenschaften, die ich in meinem letzten paar Spalten gezeigt habe. Die Visuals kommen wirklich zu leben, wenn Sie beginnen, sie mit Transformationen und Animationen betreffen. In beiden Fällen die Windows-Komposition-Engine ist eine Art des Prozessors und es liegt an Ihnen zu berechnen oder die Transform-Matrizen, sowie die Animation Kurven mit kubische Funktionen und Sinuswellen zu konstruieren. Zum Glück bietet die Windows-API die nötige Unterstützung mit einem Paar sehr komplementäre APIs. Direct2D bietet große Unterstützung zur Definition von Transform-Matrizen, leichte Arbeit beschreiben, Drehung, Skalierung, Perspektive, Übersetzung und vieles mehr. Ebenso die Windows-Animation-Manager erspart Ihnen ein Assistent an Mathematik, so dass Sie Animationen mit einer reichhaltigen Bibliothek von Animation Übergänge zu beschreiben sein, eine Storyboard mit Key frames und Lasten mehr. Es ist, wenn Sie die Windows-Animation-Manager, Direct2D und DirectComposition kombinieren, in einer einzigen Anwendung, dass man wirklich Sie die schiere Kraft an Ihren Fingerspitzen erleben können.

In meinem letzten Artikel (msdn.microsoft.com/magazine/dn759437) ich habe gezeigt, wie die DirectComposition-API zusammen mit Direct2D verwendet werden kann, um das beste der retained Mode und Direktmodus Grafiken. Das Beispielprojekt in dieser Spalte enthaltenen illustriert dieses Konzept mit einer einfachen Anwendung, mit der Sie die Kreise erstellen, verschieben Sie sie um und kontrollieren ganz einfach ihre Z-Reihenfolge. In diesem Artikel möchte ich Ihnen zeigen, wie einfach es ist, einige starke Effekte mit Transformationen und Animationen hinzuzufügen. Das erste, was zu erkennen ist, dass DirectComposition Überladungen für viele seiner skalaren Eigenschaften bietet. Sie könnten dies bemerkt haben, wenn Sie in den letzten Monaten schon nach entlang ich die API erforscht haben. Beispielsweise kann eine visuelle Offset mit den Methoden SetOffsetX und SetOffsetY wie folgt festgelegt werden:

ComPtr<IDCompositionVisual2> visual = ...
HR(visual->SetOffsetX(10.0f));
HR(visual->SetOffsetY(20.0f));

Aber die IDCompositionVisual-Schnittstelle, die IDCompositionVisual2 wird von abgeleitet bietet auch Überladungen dieser Methoden, die ein Animationsobjekt, anstatt eine Gleitkommazahl doppelter Genauigkeit akzeptieren Wert zeigen. Diese Animationsobjekt materialisiert sich wie die IDCompositionAnimation-Schnittstelle. Zum Beispiel konnte ich eine visuelle Offset mit ein oder zwei Animationsobjekte, je nachdem, ob ich eine oder beide Achsen animieren muss festgelegt wie folgt:

ComPtr<IDCompositionAnimation> animateX = ...
ComPtr<IDCompositionAnimation> animateY = ...
HR(visual->SetOffsetX(animateX.Get()));
HR(visual->SetOffsetY(animateY.Get()));

Die Komposition-Engine kann aber weit mehr als nur eine visuelle Offset animieren. Visuals unterstützen auch 2D und 3D Transformationen. Eine visuelle SetTransform Methode kann verwendet werden, eine 2D Transformation angewendet einen skalaren Wert gegeben:

D2D1_MATRIX_3X2_F matrix = ...
HR(visual->SetTransform(matrix));

Hier setzt DirectComposition tatsächlich auf die 3 x 2-Matrix durch die Direct2D-API definiert. Kannst du Dinge wie drehen, zu übersetzen, zu skalieren und zu Sichtkontakt zerren. Diese beeinflusst die Koordinatenbereich, die das visuelle Inhalte in projiziert wird, aber es ist in zwei dimensionale Grafiken mit einer X- und Y-Achse beschränkt.

Natürlich, die IDCompositionVisual-Schnittstelle bietet eine Überladung der SetTransform-Methode, aber es nicht direkt ein Animationsobjekt akzeptieren. Sie sehen, ein Animationsobjekt nur für einen einzelnen Wert im Laufe der Zeit animieren zuständig ist. Eine Matrix, besteht aus einer Reihe von Werten per Definition. Vielleicht möchten eine beliebige Anzahl seiner Mitglieder, je nach gewünschtem Effekt zu animieren, die Sie erreichen möchten. Also, nimmt stattdessen die SetTransform-Überladung ein Transform-Objekt:

ComPtr<IDCompositionTransform> transform = ...
HR(visual->SetTransform(transform.Get()));

Es ist das Transform-Objekt, speziell die verschiedenen Schnittstellen, abgeleitet von IDCompositionTransform, die überladene Methoden bereitstellt, die skalare Werte oder Animationsobjekte akzeptieren. Auf diese Weise können Sie eine Drehmatrix oder mit einem animierten Winkel der Drehung, aber einen festen Mittelpunkt und Achsen definieren. Was Sie animieren, ist natürlich Ihnen überlassen. Hier ist ein einfaches Beispiel:

ComPtr<IDCompositionRotateTransform> transform = ...
HR(transform->SetCenterX(width / 2.0f));
HR(transform->SetCenterY(height / 2.0f));
HR(transform->SetAngle(animation.Get()));
HR(visual->SetTransform(transform.Get()));

Die IDCompositionRotateTransform-Schnittstelle wird von IDCompositionTransform abgeleitet und stellt eine 2D Transformation, die die Drehung um die z-Achse eines visuellen betrifft. Hier setze ich den Mittelpunkt auf einen festen Wert, basierend auf einige Breite und Höhe, und verwenden ein Animationsobjekt um den Winkel zu steuern.

Also das ist das Grundmuster. Ich habe nur 2D Transformationen beschrieben, aber 3D Transformationen wirken genauso. Nun lassen Sie mich in der Praxis zu veranschaulichen, zeigen Ihnen, wie Sie das Beispielprojekt in meinem vorigen Artikel enthalten nutzen können und verwandeln und auf verschiedene Weise mit Hilfe von Direct2D und die Windows-Animation-Manager zu animieren.

Zur Veranschaulichung, Transformationen und Animationen in einem print-Magazin beginnt sprengen Sie die Grenzen von, was leicht durch starren in eine statische Seite genutzt werden kann. Wenn Sie es in Aktion sehen möchten, schauen Sie sich mein online-Kurs, wo Sie beobachten können, wie es alle zum Leben erwacht (bit.ly/WhKQZT). Um die Begriffe ein wenig mehr druckfreundlichen machen, nehme ich die Freiheit vom zwicken des Beispielprojekts aus meinem vorigen Artikel Quadrate anstelle Kreisen. Dies sollte die verschiedenen Transformationen auf dem Papier ein wenig deutlicher machen. Zunächst werde ich die SampleWindow-M_geometry-Membervariable mit einer Rechteckgeometrie ersetzen:

ComPtr<ID2D1RectangleGeometry> m_geometry;

Dann in der Methode SampleWindow CreateFactoryAndGeometry bekomme ich die Direct2D-Fabrik einer Rechteckgeometrie statt die Ellipse Geometrie zu erstellen:

D2D1_RECT_F const rectangle =
  RectF(0.0f, 0.0f, 100.0f, 100.0f);
HR(m_factory->CreateRectangleGeometry(
  rectangle,
  m_geometry.GetAddressOf()));

Und das ist alles was, die man braucht. Der Rest der app wird benutzen Sie einfach die Geometrie-Abstraktion für das Rendering und Treffertests wie zuvor. Sie sehen das Ergebnis im Abbildung 1.

Illustrating Transforms and Animation with Squares Instead of Circles
Abbildung 1 illustriert Transformationen und Animationen mit Quadraten statt Kreise

Als nächstes werde ich einen einfachen Meldungshandler Reaktion auf die WM_KEYDOWN Nachricht hinzufügen. Ich füge in der SampleWindow MessageHandler-Methode eine If-Anweisung für das:

else if (WM_KEYDOWN == message)
{
  KeyDownHandler(wparam);
}

Wie üblich, wird der Handler die notwendige Fehlerbehandlung von Gerät Verlust erholen brauchen. Abbildung 2 bietet das übliche Muster der Freigabe der Geräteressourcen und damit ungültig machen Fenster, so dass der WM_PAINT-Meldungshandler den Device-Stack neu erstellen kann. Ich bin auch Begrenzung den Handler die Enter-Taste um Verwechslungen mit der STRG-Taste, die verwendet wird, wenn Sie Shapes hinzufügen.

Abbildung 2-Gerüstbau für Gerät Verlustwiederherstellung

void KeyDownHandler(WPARAM const wparam)
{
  try
  {
    if (wparam != VK_RETURN)
    {
      return;
    }
    // Do stuff!
  }
  catch (ComException const & e)
  {
    TRACE(L"KeyDownHandler failed 0x%X\n",
      e.result);
    ReleaseDeviceResources();
    VERIFY(InvalidateRect(m_window,
                          nullptr,
                          false));
  }
}

An diesem Punkt bin ich bereit, experimentieren mit Transformationen und Animationen. Beginnen wir einfach mit einem einfachen 2D Drehungstransformation. Zuerst muss festgelegt werden, der Mittelpunkt, der die Z-Achse darstellen oder den Bezugspunkt, um dem drehen. Da DirectComposition physischen Pixelkoordinaten erwartet, kann ich einfach die Funktion GetClientRect hier verwenden:

RECT bounds {};
VERIFY(GetClientRect(m_window, &bounds));

Ich kann dann den Mittelpunkt des Clientbereichs des Fensters wie folgt ableiten:

D2D1_POINT_2F center
{
  bounds.right / 2.0f,
  bounds.bottom / 2.0f
};

Ich kann mich auch auf die Direct2D Matrix Hilfsfunktionen zum Erstellen einer Matrix beschreibt eine 2D Drehungstransformation 30 Grad verlassen:

D2D1_MATRIX_3X2_F const matrix =
  Matrix3x2F::Rotation(30.0f, center);

Und dann kann ich einfach ein Visual Transform-Eigenschaft festgelegt und übernehmen Sie die Änderung der visuellen Struktur. Ich werde nur diese Änderung für den Stamm visual der Einfachheit halber gilt:

HR(m_rootVisual->SetTransform(matrix));
HR(m_device->Commit());

Natürlich können beliebig viele Änderungen auf beliebig viele Bilder und die Komposition-Engine kümmern alles mühelos zu koordinieren. Kann man die Ergebnisse dieser einfache 2D Transformation in Abbildung 3. Sie bemerken möglicherweise einige Aliasing. Obwohl Direct2D, Anti-Aliasing in Verzug, wird angenommen, dass die Zeichnung im Koordinatenraum angezeigt wird, in dem sie gemacht wurde. Die Komposition-Engine nicht keine Kenntnisse über die Geometrie, die, der die Zusammensetzung-Oberfläche mit, gerendert wurde, so es keine Möglichkeit hat dies zu korrigieren. In jedem Fall nach Animation der Mischung hinzugefügt wird, werden das Aliasing kurzlebig und schwer zu erkennen.

A Simple 2D Transform
Abbildung 3 einfache 2D Transformation

Ich muss diese Transformation Animation hinzu, die Matrix-Struktur, für eine Komposition-Transformation zu wechseln. Ich werde die D2D1_POINT_2F und die D2D1_MATRIX_3X2_F Strukturen mit einer einzigen Drehung Transformation ersetzen. Zuerst muss ich die drehen Transformation, die anhand des Zusammensetzung-Geräts zu erstellen:

ComPtr<IDCompositionRotateTransform> transform;
HR(m_device->CreateRotateTransform(transform.GetAddressOf()));

Denken Sie daran, dass auch diese scheinbar einfache Objekte muss verworfen werden, wenn das Gerät verloren und neu erstellt wird. Ich kann dann den Mittelpunkt und den Winkel mit Schnittstellenmethoden statt einer Direct2D-Matrix-Struktur festgelegt:

HR(transform->SetCenterX(bounds.right / 2.0f));
HR(transform->SetCenterY(bounds.bottom / 2.0f));
HR(transform->SetAngle(30.0f));

Und der Compiler nimmt die entsprechende Überladung, die Komposition-Transformation zu behandeln:

HR(m_rootVisual->SetTransform(transform.Get()));

Dieses laufen erzeugt den gleichen Effekt wie in Abbildung 3, weil ich noch Animation hinzugefügt haben. Erstellen einer Animationsobjekts ist einfach genug:

ComPtr<IDCompositionAnimation> animation;
HR(m_device->CreateAnimation(animation.GetAddressOf()));

Ich kann dann diese Animationsobjekt statt den konstanten Wert verwenden, wenn den Winkel einstellen:

HR(transform->SetAngle(animation.Get()));

Es wird interessanter, wenn Sie versuchen, die Animation zu konfigurieren. Einfache Animationen sind relativ unkompliziert. Wie bereits erwähnt, werden die Animationen mit kubische Funktionen und Sinuswellen beschrieben. Ich kann diese Drehwinkel mit einem linearen Übergang animieren, wo der Wert von 0 bis 360 über 1 zweiter weiterkommt durch Hinzufügen einer kubischen Funktion:

float duration = 1.0f;
HR(animation->AddCubic(0.0,
                       0.0f,
                       360.0f / duration,
                       0.0f,
                       0.0f));

Der dritte Parameter der AddCubic-Methode gibt die linearen Ausdehnungskoeffizienten, damit das Sinn macht. Wenn ich es dort links, würde das visuelle jede Sekunde für die Ewigkeit 360 Grad drehen. Ich kann entscheiden, um die Animation zu Ende zu bringen, sobald es 360 Grad, wie folgt erreicht:

HR(animation->End(duration, 360.0f));

Der erste Parameter der die End-Methode gibt den Offset vom Anfang der Animation, unabhängig vom Wert der Animation Funktion. Der zweite Parameter ist der Endwert der Animation. Denken Sie daran, weil die Animation "auf diesen Wert" ausgerichtet werden wenn es nicht mit der Animation Kurve Line-up, und zu, die einen Effekt an Erschütterungen erstellen würde.

Solche linearen Animationen sind einfach genug Grund, aber komplexere Animationen können äußerst kompliziert werden. Das ist, wo die Windows-Animation-Manager ins Spiel. Anstatt aufrufen, die verschiedenen Methoden der IDCompositionAnimation, sinusförmige und kubische Polynom Segmente und wiederholte Segmente hinzuzufügen, kann ich ein Animationsstoryboard mit der Windows-Animation-Manager und seine umfangreiche Bibliothek der Übergänge konstruieren. Wenn das erledigt ist, kann ich die resultierende Animation-Variable verwenden, um die Komposition-Animation zu füllen. Dazu gehört ein bisschen mehr Code, aber der Vorteil ist, sehr viel mehr Macht und Kontrolle über Ihre app-Animation. Zuerst muss ich die Animation Manager selbst erstellen:

ComPtr<IUIAnimationManager2> manager;
HR(CoCreateInstance(__uuidof(UIAnimationManager2),
  nullptr, CLSCTX_INPROC, __uuidof(manager),
  reinterpret_cast<void **>(manager.GetAddressOf())));

Ja, die Windows-Animation-Manager basiert auf COM-Aktivierung, so sicher sein, rufen Sie CoInitializeEx oder RoInitialize, um die Laufzeit zu initialisieren. Ich muss auch die Übergang-Bibliothek zu erstellen:

ComPtr<IUIAnimationTransitionLibrary2> library;
HR(CoCreateInstance(__uuidof(UIAnimationTransitionLibrary2),
  nullptr, CLSCTX_INPROC, __uuidof(library),
  reinterpret_cast<void **>(library.GetAddressOf())));

In der Regel werden Anwendungen in diese beiden Objekte lebenslang behalten wie sie für ständige Animation und insbesondere für den Abgleich der Geschwindigkeit benötigt werden. Als nächstes muss ich eine Animationsstoryboard erstellen:

ComPtr<IUIAnimationStoryboard2> storyboard;
HR(manager->CreateStoryboard(storyboard.GetAddressOf()));

Das Storyboard ist was ordnet Übergänge Animation Variablen und definieren Sie ihre relativen Zeitplan im Laufe der Zeit. Das Storyboard ist in der Lage, verschiedene Übergänge auf andere Animation Variablen angewendet zu aggregieren; Es stellt sicher, dass sie synchronisiert bleiben; und es ist das Storyboard, das als Ganzes geplant ist. Natürlich können Sie mehrere Storyboards planen Sie unabhängige Animationen erstellen. Jetzt muss ich den Animation-Manager, um eine Animation Variable erstellen, Fragen:

ComPtr<IUIAnimationVariable2> variable;
  HR(manager->CreateAnimationVariable(
    0.0, // initial value
    variable.GetAddressOf()));

Sobald eine Storyboard geplant ist, ist der Animation-Manager verantwortlich für die Variable aktuell zu halten, so dass die app den effektiven Wert jederzeit anfordern kann. In diesem Fall werde ich einfach verwenden Sie die Animation-Variable Zusammensetzung Animation mit Segmente zu füllen und dann verwerfen. Jetzt kann ich die mächtigen Übergang-Bibliothek verwenden, um eine interessante Übergangseffekt für die Animation-Variable zu erstellen:

ComPtr<IUIAnimationTransition2> transition;
HR(library->CreateAccelerateDecelerateTransition(
  1.0,   // duration
  360.0, // final value
  0.7,   // acceleration ratio
  0.3,   // deceleration ratio
  transition.GetAddressOf()));

Der Übergang führt dazu, dass die Animation-Variable zu beschleunigen und dann für die angegebene Dauer bis es zu einer Rast an den Endwert kommt verlangsamen. Die Verhältnisse wird beeinflusst, wie relativ schnell die Variable wird zu beschleunigen und dann zu verlangsamen. Denken Sie daran, die die Verhältnisse kombiniert darf der Wert 1 nicht überschreiten. Mit der Animation Variable und Übergang bereit zu gehen kann ich diese zum Storyboard hinzufügen:

HR(storyboard->AddTransition(variable.Get(),
                             transition.Get()));

Das Storyboard ist nun bereit, die geplant werden:

HR(storyboard->Schedule(0.0));

Der einzige Parameter der Methode Zeitplan sagt den Planer der heutigen Zeit der Animation. Dies eignet sich mehr für die Koordinierung von Animation und Koordination mit der Komposition-Engine-Refresh-Rate, aber es genügt fürs erste. Zu diesem Zeitpunkt die Animation-Variable wird vorbereitet und kann ich Fragen, wie es um die Zusammensetzung-Animation mit Kurven zu füllen:

HR(variable->GetCurve(animation.Get()));

In diesem Fall ist es als ob ich die Animation Zusammensetzung wie folgt aufgerufen:

HR(animation->AddCubic(0.0, 0.0f, 0.0f, 514.2f, 0.0f));
HR(animation->AddCubic(0.7, 252.0f, 720.0f, -1200.0f, 0.0f));
HR(animation->End(1.0, 360.0f));

Das ist sicherlich viel weniger Code als es mit dem Windows-Animation-Manager dauerte, aber sinnvoll, dass Mathe ist nicht so einfach. Die Windows-Animation-Manager bietet auch die Möglichkeit, zu koordinieren und reibungslos Übergang mit Animationen, was äußerst schwierig, manuell zu tun wäre.


Kenny Kerr ist ein Computerprogrammierer mit Sitz in Kanada, als auch ein Autor für Pluralsight und Microsoft MVP. Er Blogs auf kennykerr.ca und folgen Sie ihm auf Twitter bei twitter.com/kennykerr.

Dank den folgenden technischen Experten von Microsoft für die Überprüfung dieses Artikels: Leonardo Blanco und James Clarke