2) CompositionTarget
The second method to create animation is by CompositionTarget. In official document, CompositionTarget object can create custom animation by each frame’s callback. In other words, the animation created by CompositionTarget is base on the event fired by UI redrawing, which is accordance with the UI refresh frequency, so the frequency is fixed, it is difficult to control it by ourselves.
But how to use it? The XAML file is the same as the one in the previous chapter, the difference is in the code-behind, as follows:
public partial class MainPage : UserControl { private Rectangle rect; //set the moving speed double speed = 5; //set the moving target Point moveTo; public MainPage() { InitializeComponent(); rect = new Rectangle() { Fill = new SolidColorBrush(Colors.Red), Width = 50, Height = 50, RadiusX = 5, RadiusY = 5 }; Carrier.Children.Add(rect); Canvas.SetLeft(rect, 0); Canvas.SetTop(rect, 0); //register UI refresh event CompositionTarget.Rendering += CompositionTarget_Rendering; } void CompositionTarget_Rendering(object sender, EventArgs e) { double rect_X = Canvas.GetLeft(rect); double rect_Y = Canvas.GetTop(rect); Canvas.SetLeft(rect, rect_X + (rect_X < moveTo.X ? speed : -speed)); Canvas.SetTop(rect, rect_Y + (rect_Y < moveTo.Y ? speed : -speed)); } private void Carrier_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { moveTo = e.GetPosition(Carrier); } }
1st, we must register Rendering event of the CompositionTarget as follows:
CompositionTarget.Rendering += CompositionTarget_Rendering;
2nd, because we want to make the rectangle move to whatever we click, so I use a variable named moveTo to save the position we clicked, and assign the value to it in the mouse click envet, as we can see in the method Carrier_MouseLeftButtonDown.
3rd, I create animation in the method CompositionTarget_Rendering, because it is an event fired by UI’s refreshment all the time, I dynamically set the rectangle’s Left and Top properties to simulate the moving.
Notice that the speed is minus or not in this method, it depends on the relationship between the new position and the old one. It is suitable to human’s feeling, for example, if you click on the left to the rectangle, the rect_X will be more than moveTo.X, so the speed will be minus, which makes rect_X lower in X direction, it means the rectangle move left to the original position. Is that right? Of course.
Please press Ctrl+F5, click anywhere in the window, you will find the rectangle move as we expected before.
But, we find a critical problem in this mechanism. The screen is trembled heavily, and the moving is not smooth. Is anything wrong in our code?
The reason that the rectangle can not move smoothly, is that the speed in the X and Y direction are different, so we need to use Math.Sin and Math.Cos function to separate original speed into speed_X and speed_Y, the key syntax is as follows:
void CompositionTarget_Rendering(object sender, EventArgs e) { double rect_X = Canvas.GetLeft(rect); double rect_Y = Canvas.GetTop(rect); Canvas.SetLeft(rect, rect_X + (rect_X < moveTo.X ? speed_X : -speed_X)); Canvas.SetTop(rect, rect_Y + (rect_Y < moveTo.Y ? speed_Y : -speed_Y)); } private void Carrier_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { double rect_X = Canvas.GetLeft(rect); double rect_Y = Canvas.GetTop(rect); moveTo = e.GetPosition(Carrier); double len = Math.Sqrt(Math.Pow(moveTo.X - rect_X, 2) + Math.Pow(moveTo.Y - rect_Y, 2)); speed_X = Math.Abs(speed * (moveTo.X - rect_X) / len); speed_Y = Math.Abs(speed * (moveTo.Y - rect_Y) / len); }
The reason that the screen is trembled heavily, depend on the speed value. Now the speed is 5. Let’s reduce its value, set to 1. The trembling is slightly, but still existing. Why the value is more large, the more heavily the screen is trembled? You can find the root cause in CompositionTarget_Rendering function.
As you know, CompositionTarget_Rendering is a non-stop function. So the sentence
Canvas.SetLeft(rect, rect_X + (rect_X < moveTo.X ? speed_X : -speed_X));
will be executed each time, even the rectangle has arrived the target. It doesn’t matter. The worst scenario is that the rectangle doesn’t arrive the target forever. For example, the rect_X is 100, the moveTo.X is 101, the speed_X is 3, then the left value of the rectangle will be set to 103, 100, 103,100, it means the rectangle won’t stop. So does the scenario in Y direction. That is why you see the screen trembled.
The solution to this issue is to set a critical value, the value is half of the speed. So when the distance between rect_X and moveTo.X is less than the critical value, we will treat the rectangle has already arrived the target in X direction, so Canvas.SetLeft sentence won’t be executed; otherwise it will be executed, which make the rect_X tend to the moveTo.X gradually.
So does the solution in Y direction. I modify the CompositionTarget_Rendering again, as follows:
void CompositionTarget_Rendering(object sender, EventArgs e) { double rect_X = Canvas.GetLeft(rect); double rect_Y = Canvas.GetTop(rect); if (Math.Abs(rect_X - moveTo.X) > speed_X / 2) Canvas.SetLeft(rect, rect_X + (rect_X < moveTo.X ? speed_X : -speed_X)); if (Math.Abs(rect_Y - moveTo.Y) > speed_Y / 2) Canvas.SetTop(rect, rect_Y + (rect_Y < moveTo.Y ? speed_Y : -speed_Y)); }
Now the animation looks like normal as before.
Summary: This chapter introduce how to use CompositionTarget to create animation base on frames, it is different from the traditional animation by switching pictures. Its principle is base on change the object’s property all the time.
Next chapter, I will introduce the 3rd method to create animation, and compare all these methods. Please focus on it.
Chinese friend, you can also visit this Chinese blog if you feel difficult to read English, http://www.cnblogs.com/alamiye010/archive/2009/06/17/1505331.html, part of my article is base on it.
Demo download: http://silverlightrpg.codeplex.com/releases/view/40978