Знакомство с Windows Animation Manager
Опубликовано 03.06.2010 г. 00:19
Привет, Windows Animation Manager! - сказал я. - Привет!, - сказал Windows Animation Manager, - давай дружить! Давай, - ответил я.
Знакомство на самом деле вышло случайным. Я искал в MSDN что то про Direct2D, и случайно в результатах поиска попалось словосочетание Windows Animation Manager. Интересно, - подумал я. И вот эта ссылка привела меня в раздел разработки пользовательского интерфейса. На самом деле на тот момент я очень хотел реализовать в Delphi пример с названием ListView из Direct2D. У кого есть Visual Studio (с win7 SDK) можете попробовать поглядеть о каком примере идет речь здесь. Суть данного примера, что он реализует list View для навигации по файловой системе, сами иконки и подписи файлов формируются с помощью Direct2D но при этом в пример добавлена анимация. При переходе в новую папку (либо при сортировке файлов) иконки плавно перемещаются, оставляя так сказать след. В общем смотрится весьма увлекательно. Но речь сейчас не об этом примере, хотя мб я его таки допишу и на Delphi, ибо все же он почти полностью переписан, за исключением чего то, не помню чего. Мб даже и весь, только работать не захотел :) Дак вот, возвращаясь обратно к Windows Animation Manager. Вкратце своими словами объясню что это такое и зачем это требуется. Представьте, что у вас есть некоторая переменная, числового характера, для определенности double. И допустим вам требуется ее каким либо образом изменять во времени, например, вы хотите чтобы в течении одной секунды ваша переменная А изменилась от начального значения 1 до конечного значения 53, и чтобы это изменение проходило например не с постоянным интервалом, а например с ускорением. Представьте так же, что такой процесс изменения переменной вам требуется совершать не в строго определенные моменты, а по возникновении какого то события. Возможно также после того как переменная А закончила свое изменение, вам надо опять изменить ее, но уже в течении 0.5 секунды, и с конечного значения 53 до нового 24, и уже не с ускорением, а по какому либо другому закону. Давайте добавим для интереса сюда переменную Б, которая должна начать изменяться через 0.2 секунды после старта первого изменения переменной А. От абстрактного к реальному. Давайте представим что в точке (х,у) формы у вас расположен некоторый графический объект. Вы хотите, чтобы кликнув мышью в точку (х1,у1) формы, ваш объект переместился туда. но не по прямой конечно. Вы хотите чтобы объект перемещался в течении секунды. но не плавно, хотите чтобы перемещение по Ох было быстрее чем по Оу, создавая дугу. Да и вообще, чтобы в начале пути объект двигался медленно, разгоняясь к концу. А еще вы хотите чтобы было так, что вы можете кликнуть сразу в другую точку, и объект не закончив своего первого передвижения сразу изменит свое направление и полетит в новый пункт назначения. Хотя конечно может вас не устраивает один из скажем 10 различных функциональных зависимостей поведения переменной во времени и вы хотите реализовать свою. А если вы реализовали данную модель, разбили интервал изменения на дискретные участки, и в моменты изменения переменной перерисовываете изображение, и случилось так что ваша сцена рисования настолько сложна, что выбранные ваши 100 итераций не успевают отрисоваться за 0.5 секунды, надо ведь тогда менять интервал, быстро все перестраивать, сделать меньше фпс, но уложиться в поставленное время. Не находите, что запрограммировать подобное действо было бы достаточно трудоемким процессом? А вот Windows Animation Manager говорит, что программировать ничего и не надо, и он все это умеет. Надо только сказать ему, что у нас будет 3 переменных, и как они будут изменяться, после какого события, и что надо делать в промежутки изменения (например перерисовать объект). Далее наверное будет какая часть исходного кода, тем кому это не интересно могут не читать, но для интереса посмотрите видео по этой ссылке, оно длинное правда, но вы поперематывайте, там показывают демонстрацию анимации. Или лучше вот это видео. Итак, что мы имеем: Библиотека UIAnimation.dll содержит описания COM интерфейсов для работы с Windows Animation Manager. Основными являются IUIAnimationManager, который руководит всем, IUIAnimationTimer для распределения и установки времени, IUIAnimationTransitionLibrary предоставляющая набор различных методов для изменения переменных, а также собственно сами переменные IUIAnimationVariable. Что же мы сделаем. Возьмем форму с Direct2DCanvas и расширим ее описание добавив переменные, описанных выше классов, в качестве переменных у нас будут выступать х,у координаты и R - радиус шарика, который мы будем перегонять с одного места на другое по форме, кликая мышкой.
private { Private declarations } fcanvas : TDirect2DCanvas; animationManager : IUIAnimationManager; animationTImer : IUIAnimationTimer; transitionLibrary : IUIAnimationTransitionLibrary; xPos, yPos : IUIAnimationVariable; radius : IUIAnimationVariable; procedure DrawClientArea(); procedure ChangePosition();Функция DrawClientArea будет вызывать в событии onPaint формы и рисовать шарик (: а функция ChangePosition будет вызываться при клике мыши по форме, и задавать новую точку назначения. Итак при создании формы мы сделаем следующее:
- Инициализируем использование d2dCanvas и создадим кисть с круговым бело-черным градиентом.
- Создадим наши объекты animationManager, animationTimer & transitionLibrary
- создадим обработчик событий eventHandler : IUIAnimationManagerEventHandler и привяжем его к менеджеру анимации.
- Инициализируем наши переменные х,у,radius, задав начальное значение, а также максимальные и минимальные границы (границы в дальнейшем менять нельзя оказалось).
procedure TMainForm.FormCreate(Sender: TObject); var eventHandler : IUIAnimationManagerEventHandler; begin if not TDirect2DCanvas.Supported then begin application.Terminate; exit; end; fCanvas := TDirect2DCanvas.Create(handle); ID2D1HwndRenderTarget(fcanvas.RenderTarget).SetDpi(96,96); canvas.Brush.handle := canvas.CreateBrush([clWhite,clBlack],point(0,0),point(0,0),50,50); animationManager := CoUIAnimationManager.Create(); animationTimer := CoUIAnimationTimer.Create(); transitionLibrary := CoUIAnimationTransitionLibrary.Create(); eventHandler := TAnimationManagerEventHandler.Create(self); animationManager.SetManagerEventHandler(eventHandler); animationManager.CreateAnimationVariable(0,radius); radius.SetLowerBound(0); radius.SetUpperBound(min(clientWidth,clientHeight)); animationManager.CreateAnimationVariable(0,xPos); xPos.SetLowerBound(0); xPos.SetUpperBound(clientWidth); animationManager.CreateAnimationVariable(0,yPos); yPos.SetLowerBound(0); yPos.SetUpperBound(clientHeight); ChangePosition(); end;Что мы будем делать в событии onPaint? По скольку наши анимации связаны со временем,и вобще могут быть запланированными и т.п, то надо получить время у таймера, и передать его менеджеру. Далее мы нарисуем наш картинку в методе DrawClientArea, получим у менеджера статус, говорящий о том стоит ли продолжать рисовать, либо время уже вышло, и больше перерисовывать не надо ничего.
procedure TMainForm.FormPaint(Sender: TObject); var secondsNow : double; updateResult : UI_ANIMATION_UPDATE_RESULT; status : UI_ANIMATION_MANAGER_STATUS; begin animationTimer.GetTime(secondsNow); animationManager.Update(secondsNow,updateResult); DrawClientArea(); animationManager.GetStatus(status); if status = UI_ANIMATION_MANAGER_BUSY then begin invalidate(); end; end;Как выглядит сама функция отрисовки? Опять таки ничего сложного, если окно не перекрыто ничем то будем рисовать следующее: Возьмем наши координаты, и радиус, установим их для нашей кисти, и закрасим с помощью нее клиентскую область приложения.
procedure TMainForm.DrawClientArea(); var r,x,y : integer; begin if (ID2D1HwndRenderTarget(canvas.RenderTarget).CheckWindowState and D2D1_WINDOW_STATE_OCCLUDED)> 0 then exit; canvas.BeginDraw; xPos.GetIntegerValue(x); yPos.getIntegerValue(y); radius.GetIntegerValue(r); with ID2D1RadialGradientBrush(canvas.Brush.handle) do begin setRadiusX(r); setRadiusY(r); setCenter(point(x,y)); end; canvas.FillRect(getClientRect); canvas.EndDraw; end;Процедура смены позиции наверное самая сложная. Здесь мы знакомимся с планировщиком анимации (storyBoard : IUIAnimationStoryboard), который определят что и как и в какое время будет изменяться. Итак, о чем эта функция?
- Создадим storyBoard для планирования анимации.
- придумаем новое значения радиуса. с максимумом в четверть формы.
- Определим для всех переменных методы изменения (в течении 1 секунды), в данном случае переменные будут сначала ускорятся, потом замедляться на всем промежутке времени. Только вот мы установили доли ускорения/замедления так, что Х всегда замедляется, а У наоборот, получим дугу. Добавим переменные и их изменения в планировщик, и отправим все на выполнение, поскольку задача у нас не отложенная.
procedure TMainForm.ChangePosition(); const DURATION = 1; var storyBoard : IUIAnimationStoryboard; secondsNow : double; schedResult : UI_ANIMATION_SCHEDULING_RESULT; radiusTransition : IUIAnimationTransition; xTransition, yTransition : IUIAnimationTransition; r: integer; begin animationManager.CreateStoryboard(storyBoard); r := random(min(clientHeight,clientWidth) div 4); transitionLibrary.CreateAccelerateDecelerateTransition(DURATION, r, 1, 0, radiusTransition); transitionLibrary.CreateAccelerateDecelerateTransition(DURATION, ScreenToClient(mouse.cursorPos).x, 0,1, xTransition); transitionLibrary.CreateAccelerateDecelerateTransition(DURATION, screenToClient(mouse.CursorPos).y, 1,0, yTransition); storyBoard.AddTransition(radius, radiusTransition); storyBoard.AddTransition(xPos, xTransition); storyBoard.AddTransition(yPos, yTransition); animationTimer.GetTime(secondsNow); storyBoard.Schedule(secondsNow,schedResult); end;Вот практически и все, осталась одна вещь. Откуда же менеджер узнает, что я хочу перерисовать форму, или что я вообще намерен делать в своей анимации (в случае отложенного запуска анимации, например). Может быть я не рисовать вообще хочу. Для этого при создании формы мы объявили переменную eventHandler : IUIAnimationManagerEventHandler; которая и будет говорить менеджеру что требуется сделать. Для этого собственно мы определили класс TAnimationManagerEventHandler, поддерживающий указанный выше интерфейс, и при смене статуса, будем вызывать метод invalidate формы, ну а указатель на форму передадим, переписав конструктор.
TAnimationManagerEventHandler = class (TInterfacedObject, IUIAnimationManagerEventHandler) private fForm : TForm; public constructor Create(Form : TForm); function OnManagerStatusChanged(newStatus: UI_ANIMATION_MANAGER_STATUS; previousStatus: UI_ANIMATION_MANAGER_STATUS): HResult; stdcall; end; constructor TAnimationManagerEventHandler.Create(Form: TForm); begin inherited create(); fForm := form; end; function TAnimationManagerEventHandler.OnManagerStatusChanged(newStatus, previousStatus: UI_ANIMATION_MANAGER_STATUS): HResult; begin if newStatus = UI_ANIMATION_MANAGER_BUSY then begin fForm.Invalidate; end; end;Статичная картинка, конечно, не продемонстрирует того, что в результате получилось, так что для желающих исходный код (вместе исполняемым файлом, на случай если лень импортировать библиотеку и компилировать) можно взять посмотреть здесь, в течении месяца.
05.06.2010 в 17:46
Я уже пару лет как мечтаю о красивых, качественных анимированных Delphi-контролах. А ещё лучше о каком-нибудь отдельном аниматоре позволяющем добавлять анимацию к стандартным контролам.
Даже начинал писать свой Animation Manager но запнулся на обработке aligned-контролов и забросил.
p.s. на тему выкладывания файлов. Для этого очень хорошо подходит DropBox или MS SkyDrive. Там можно хранить файлы больших объёмов без ограничений на возраст.
06.06.2010 в 13:16
переписал еще одну демку, так что скоро на эту тему будет еще одна статейка (:
01.11.2010 в 13:02
if newStatus = UI_ANIMATION_MANAGER_BUSY then begin
fForm.Invalidate;
end;
напишу
if newStatus = UI_ANIMATION_MANAGER_BUSY then begin
fForm.MyProc;
end;
в в ней
x: integer; глобальная
var
secondsNow: double;
updateResult: TUIAnimationUpdateResult;
status: TUiAnimationManagerStatus;
begin
secondsNow := aniTimer.GetTime();
updateResult := aniManager.Update(secondsNow);
x := xPos.GetIntegerValue();
status := aniManager.GetStatus();
if (status = UIAnimationManagerBusy) then
begin
invalidate();
end;
я так попробовал сделать, но вылетает ошибка "разрушительный сбой" при попытке обратиться к aniManager.
02.11.2010 в 21:02
к сожалению AnimationManager больше я не трогал, кроме последующих двух статей на тему анимации, так что вряд ли что подскажу дельного.