Carousel user control for Windows 8.1 and Windows Phone 8.1 : ManipulationDelta, ArrangeOverride and Animation

Carousel user control for Windows 8.1 and Windows Phone 8.1 : ManipulationDelta, ArrangeOverride and Animation

  • Comments 6

Hi;

Today, here is the last part of the carousel user control creation for Windows 8.1 and Windows Phone 8.1

For recap; here are the first parts of this series :

  1. Part 1 : Create a custom user control using Xaml and C# for Windows 8.
  2. Part 2 : Upgrading a Windows 8.0 component to Windows 8.1 and Windows Phone 8.1 (WinRT)

 

Introduction

In this last part, we will again upgrading our component to add some gestures. Here are two videos showing before and after adding gestures features

As you can see in the first video there is no gesture handled when you move your finger. the only event raised is the index change when you made a translation

Here is the carousel with then new gesture features. As you can see, the carousel is smoother :

And of course, the Windows Phone 8.1 version as well :

Github

Now that the control is terminated, you can get the full source code from Github : https://github.com/Mimetis/LightStone

image

 

Transformation : PlaneProjection

To handle our new gesture feature, we need to know, for all items where they must be placed.
As you know, we handle position with a PlaneProjection (because we move on X,Y and Z axis)

The GetProjection method was created to return a Tuple of X,Y and Z values. It tooks an item index and the movement delta from its original position :

1 private Tuple<Double, Double, Double, Double> GetProjection(int i, Double deltaX) 2 { 3 4 var isLeftToRight = deltaX > 0; 5 var isRightToLeft = !isLeftToRight; 6 7 Double newDepth = -this.Depth; 8 9 Double initialRotation = (i == this.SelectedIndex) ? 0d : ((i < this.SelectedIndex) ? Rotation : -Rotation); 10 Double newRotation = 0d; 11 12 Double offsetX = (i == this.SelectedIndex) ? 0 : (i - this.SelectedIndex) * desiredWidth; 13 Double translateX = (i == this.SelectedIndex) ? 0 : ((i < this.SelectedIndex) ? -TranslateX : TranslateX); 14 Double initialOffsetX = offsetX + translateX; 15 Double newOffsetX = 0d; 16 17 var translateY = TranslateY; 18 19 20 if (i == this.SelectedIndex) 21 { 22 // rotation is from -Rotation to Rotation 23 // We get the proportional of deltaX by desiredWidth 24 newRotation = initialRotation - Rotation * deltaX / desiredWidth; 25 26 // the offset max is the Sum(TranslateX + desiredWidth) 27 // We get the proportional too 28 newOffsetX = deltaX * (TranslateX + desiredWidth) / desiredWidth; 29 } 30 // only the first item on the left or right is moving on x, z, and d 31 else if ((i == this.SelectedIndex - 1 && isLeftToRight) || (i == this.SelectedIndex + 1 && isRightToLeft)) 32 { 33 // We get the rotation (proportional from delta to desiredwidth, always) 34 // by far the initial position is Rotation, so we made a subsraction 35 newRotation = initialRotation - Rotation * deltaX / desiredWidth; 36 37 // The Translation is decreasing to 0 38 newOffsetX = initialOffsetX - initialOffsetX * Math.Abs(deltaX) / desiredWidth; 39 } 40 41 // Other items just moved on x 42 else 43 { 44 newOffsetX = initialOffsetX + deltaX; 45 46 newRotation = initialRotation; 47 } 48 49 return new Tuple<Double, Double, Double, Double>(newOffsetX, translateY, newDepth, newRotation); 50 51 52 }

ManipulationDelta, ArrangeOverride, Animation

Here are the 3 most important methods in the animation of our control. To summarize :

  1. ArrangeOverride : called every time a render is needed (initial load for example) . Called after any manipulation or animation
  2. ManipulationDelta : called during any gesture (in conjonction with ManipulationEnd) This method is called when we will “shift” an item on right or left
  3. Animation : (UpdatePosition method) Called at the end of a manipulation to move the items to their final position

 

ArrangeOverride

To keep it simple, this method is called on every render, except when any manipulation / animation occured. It’s called for example during the first rendering of our control. ArrangeOverride is responsible of the initial position of each item.

As you can imagine, ArrangeOverride called the GetProjection method with a value of 0 for the delta parameter

Here is a simplified version of the ArrangeOverride method:

1 protected override Size ArrangeOverride(Size finalSize) 2 { 3 Double centerLeft = 0; 4 Double centerTop = 0; 5 6 this.Clip = new RectangleGeometry 7 { Rect = new Rect(0, 0, finalSize.Width, finalSize.Height) }; 8 9 for (int i = 0; i < this.internalList.Count; i++) 10 { 11 UIElement container = internalList[i]; 12 13 Size desiredSize = container.DesiredSize; 14 if (double.IsNaN(desiredSize.Width) || 15 double.IsNaN(desiredSize.Height)) continue; 16 17 // get the good center and top position 18 if (centerLeft == 0 && centerTop == 0 19 && desiredSize.Width > 0 && desiredSize.Height > 0) 20 { 21 desiredWidth = desiredSize.Width; 22 desiredHeight = desiredSize.Height; 23 24 centerLeft = (finalSize.Width / 2) - (desiredWidth / 2); 25 centerTop = (finalSize.Height - desiredHeight) / 2; 26 } 27 28 // Get position from SelectedIndex 29 var deltaFromSelectedIndex = Math.Abs(this.SelectedIndex - i); 30 31 // Get rect position 32 var rect = new Rect(centerLeft, centerTop, desiredWidth, 33 desiredHeight); 34 35 container.Arrange(rect); 36 Canvas.SetLeft(container, centerLeft); 37 Canvas.SetTop(container, centerTop); 38 39 // Apply Transform 40 PlaneProjection planeProjection = container.Projection 41 as PlaneProjection; 42 43 if (planeProjection == null) 44 continue; 45 46 // Get an initial projection (without move) 47 var props = GetProjection(i, 0d); 48 49 planeProjection.LocalOffsetX = props.Item1; 50 planeProjection.GlobalOffsetY = props.Item2; 51 planeProjection.GlobalOffsetZ = props.Item3; 52 planeProjection.RotationY = props.Item4; 53 54 } 55 56 return finalSize; 57 } 58

ManipulationDelta

This method is called for every manipulation engaged. We dont create animations, we just modify the PlaneProjection transformation within each item.

We will handle the ManipulationCompleted event too, to :

  1. Change the selected index and run the animation.
  2. Reset a maniupulation due to lack of movement
  3. Handle some situations like the first of the last item of the Carousel list

 

1 private void OnManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e) 2 { 3 var deltaX = e.Cumulative.Translation.X; 4 5 if (deltaX > desiredWidth) 6 deltaX = desiredWidth; 7 8 if (deltaX < -desiredWidth) 9 deltaX = -desiredWidth; 10 11 // Dont animate all items 12 var inf = this.SelectedIndex - (MaxVisibleItems * 2); 13 var sup = this.SelectedIndex + (MaxVisibleItems * 2); 14 15 for (int i = 0; i < this.internalList.Count; i++) 16 { 17 // Dont animate all items 18 if (i < inf || i > sup) 19 continue; 20 21 var item = internalList[i]; 22 23 PlaneProjection planeProjection = item.Projection as PlaneProjection; 24 25 if (planeProjection == null) 26 continue; 27 28 // Get the new projection for the current item 29 var props = GetProjection(i, deltaX); 30 planeProjection.LocalOffsetX = props.Item1; 31 planeProjection.GlobalOffsetY = props.Item2; 32 planeProjection.GlobalOffsetZ = props.Item3; 33 planeProjection.RotationY = props.Item4; 34 35 } 36 }

Animation

The UpdatePosition method is called when we have to move all the items to a new position. This method is used when:

  1. We change the selected index  (when Tap or with code)
  2. We stop a maniuplation and we need to move the items to their last position.
  3. We add / remove items

 

1 private void UpdatePosition() 2 { 3 storyboard = new Storyboard(); 4 5 for (int i = 0; i < this.internalList.Count; i++) 6 { 7 // Do not animate all items 8 var inf = this.SelectedIndex - (MaxVisibleItems * 2); 9 var sup = this.SelectedIndex + (MaxVisibleItems * 2); 10 11 if (i < inf || i > sup) 12 continue; 13 14 var item = internalList[i]; 15 16 PlaneProjection planeProjection = item.Projection as PlaneProjection; 17 18 if (planeProjection == null) 19 continue; 20 21 // Get target projection 22 var props = GetProjection(i, 0d); 23 24 storyboard.AddAnimation(item, TransitionDuration, props.Item1, "(UIElement.Projection).(PlaneProjection.LocalOffsetX)", this.EasingFunction); 25 storyboard.AddAnimation(item, TransitionDuration, props.Item2, "(UIElement.Projection).(PlaneProjection.GlobalOffsetY)", this.EasingFunction); 26 storyboard.AddAnimation(item, TransitionDuration, props.Item3, "(UIElement.Projection).(PlaneProjection.GlobalOffsetZ)", this.EasingFunction); 27 storyboard.AddAnimation(item, TransitionDuration, props.Item4, "(UIElement.Projection).(PlaneProjection.RotationY)", this.EasingFunction); 28 storyboard.AddAnimation(item, TransitionDuration, opacity, "Opacity", this.EasingFunction); 29 30 } 31 32 // When storyboard completed, Invalidate 33 storyboard.Completed += (sender, o) => 34 { 35 this.isUpdatingPosition = false; 36 this.InvalidateArrange(); 37 }; 38 39 storyboard.Begin(); 40 }

/Seb

Leave a Comment
  • Please add 2 and 5 and type the answer here:
  • Post
  • This is a cool representation of an other type of carousel control.

    Great job

    Serge

  • Thanks for the wonderful control this was really helpful :)

    It is possible to make the carousel rotating automatically?

    Waiting for your reply.

  • @Serge : Thx a lot !

    @Suresh : I didn't test, but I think a timer binded on the selectedIndex should be a good starting solution :)

  • @Suresh : Here is a code that works fine for me.

    DispatcherTimer timer = new DispatcherTimer();

    timer.Interval = TimeSpan.FromSeconds(1d);

    timer.Tick += (s, e) =>

    {

       timer.Stop();

       if (LightStoneElement.SelectedIndex == ((IList)LightStoneElement.ItemsSource).Count - 1)

           LightStoneElement.SelectedIndex = 0;

       else

           LightStoneElement.SelectedIndex += 1;

       timer.Start();

    };

    timer.Start();

  • Great work man, it's really cool.

    I have two requests:

    1) How can I eliminate the reflection effect at the the base of every image?

    2) How could I make the carousel  completely circular?

    Thanks in advance for the help.

  • Very good solution for carousel, but i have a question:

    1) why when changing color background, half of the screen changes color, the other half is black?

    I can not find a solution to this problem.

    Thanks in advance for the help.

Page 1 of 1 (6 items)