UWP:使用Behavior实现FlipView简单缩放效果

先上效果图

首先安装Behavior SDK:在Nuget中搜索安装 Microsoft.Xaml.Behaviors.Uwp.Managed 。

然后新建类,AnimationFlipViewBehavior.cs,并继承DependencyObject和IBehavior接口:

namespace TestBehavior
{
    public class AnimationFlipViewBehavior: DependencyObject, IBehavior
    {
        public DependencyObject AssociatedObject { get; set; }
        public void Attach(DependencyObject associatedObject)
        {
            AssociatedObject  = associatedObject;
        }
        public void Detach()
        {

        }
    }
}

Attach是添加Behavior时被调用的方法,Detach是移除Behavior时被调用的方法。

这时在Attach中判断是否是FlipView,并且保存下来。然后按照老样子获取ScrollViewer,如果FlipView已经加载好了,就可以直接获取到ScrollViewer,否则要在FlipView的Loaded事件中获取。

 1 FlipView flipView;
 2 ScrollViewer scrollViewer;
 3 Compositor compositor;
 4 CompositionPropertySet scrollPropSet;
 5 
 6 public DependencyObject AssociatedObject { get; private set; }
 7 
 8 public void Attach(DependencyObject associatedObject)
 9 {
10     AssociatedObject = associatedObject;
11     if (associatedObject is FlipView flip) flipView = flip;
12     else throw new ArgumentException("对象不是FlipView");
13     scrollViewer = Helper.FindVisualChild<ScrollViewer>(flipView, "ScrollingHost");
14     if (scrollViewer == null)
15     {
16         flipView.Loaded += FlipView_Loaded;
17     }
18     else InitCompositionResources(scrollViewer);
19 }
20 
21 private void FlipView_Loaded(object sender, RoutedEventArgs e)
22 {
23     flipView.Loaded -= FlipView_Loaded;
24     var scroll = Helper.FindVisualChild<ScrollViewer>(flipView, "ScrollingHost");
25     if (scroll == null) throw new ArgumentNullException("ScrollViewer为空");
26     else scrollViewer = scroll;
27 
28     InitCompositionResources(scrollViewer);
29 }
30 
31 void InitCompositionResources(ScrollViewer scroll)
32 {
33     if (compositor == null) compositor = ElementCompositionPreview.GetElementVisual(flipView).Compositor;
34     if (scroll == null) return;
35 
36     scrollPropSet = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(scrollViewer);
37 }
View Code
 1 public static class Helper
 2 {
 3     public static T FindVisualChild<T>(DependencyObject obj, int Index = 0) where T : DependencyObject
 4     {
 5         if (Index == -1) return null;
 6         int count = VisualTreeHelper.GetChildrenCount(obj);
 7         int findedcount = 0;
 8         for (int i = 0; i < count; i++)
 9         {
10             DependencyObject child = Windows.UI.Xaml.Media.VisualTreeHelper.GetChild(obj, i);
11             if (child != null && child is T)
12             {
13                 if (findedcount == Index)
14                     return (T)child;
15                 else
16                 {
17                     findedcount++;
18                 }
19             }
20             else
21             {
22                 T childOfChild = FindVisualChild<T>(child, findedcount);
23                 if (childOfChild != null)
24                     return childOfChild;
25             }
26         }
27         return null;
28     }
29     public static T FindVisualChild<T>(DependencyObject obj, string name) where T : DependencyObject
30     {
31         int count = VisualTreeHelper.GetChildrenCount(obj);
32         int findedcount = 0;
33         for (int i = 0; i < count; i++)
34         {
35             DependencyObject child = Windows.UI.Xaml.Media.VisualTreeHelper.GetChild(obj, i);
36             if (child != null && child is T)
37             {
38                 if ((child as FrameworkElement).Name == name)
39                     return (T)child;
40                 else
41                 {
42                     findedcount++;
43                 }
44             }
45             else
46             {
47                 T childOfChild = FindVisualChild<T>(child, findedcount);
48                 if (childOfChild != null)
49                     return childOfChild;
50             }
51         }
52         return null;
53     }
54 }
View Code

然后创建两个表达式动画,分别作用在中心点和缩放上。

ExpressionAnimation CenterPointAnimation;
ExpressionAnimation ScaleAnimation;

void InitCompositionResources(ScrollViewer scroll)
{
    if (compositor == null) compositor = ElementCompositionPreview.GetElementVisual(flipView).Compositor;
    if (scroll == null) return;

    scrollPropSet = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(scrollViewer);
    if (CenterPointAnimation == null)
    {
        CenterPointAnimation = compositor.CreateExpressionAnimation("Vector3(visual.Size.X/2,visual.Size.Y/2,0)");
    }
    if (ScaleAnimation == null)
    {
        ScaleAnimation = compositor.CreateExpressionAnimation("Clamp(1- (visual.Offset.X + scroll.Translation.X) / visual.Size.X * 0.4, 0f, 1f)");
        ScaleAnimation.SetReferenceParameter("scroll", scrollPropSet);
    }
}

这里着重说一下ScaleAnimation。

表达式中的Clamp(value,min,max)是内置函数,当value在min和max之间的时候返回value,小于min则返回min,大于max则返回max。

FlipView中是一个ScrollViewer,横向滚动,ScrollViewer内的元素的Visual.Offset.X控制Visual的位置,而不是默认为0。所以只要判断visual.Offset.X和scroll.Translation.X的关系,就能做出动画来。

然后写一个方法,给所有Items的容器附加上这些动画。

因为默认的Items并不是Observable的,有两种解决方案,一是设置ItemsSource为一个ObservableCollection,然后注册CollectionChanged事件。这样做会让控件和页面后台代码耦合度提升。为了更干净的代码结构,这里用一个性能低一些的方法,注册FlipView的SelectionChanged事件,在这里调用InitAnimation方法。

如果每次只给SelectedItem和左右的Item附加动画,PC上测试很完美,但是手机上,或者说触摸操作的时候,会出现动画未加载的问题。这里涉及到一个FlipView和Pivot的大坑。

在键鼠操作和代码操作SelectedIndex切换页面的时候,是先触发SelectionChanged事件,再播放动画的。但是触摸操作的时候,只有当你滑屏再送手后,系统才知道到底应不应该切换页面。所以我们每次送手播放完动画和触发SelectionChanged并不同步,动画自然就不会附加到后面的Item上,所以每次我们都给所有的Item附加动画,虽然损失了部分性能,但是可以保证不出问题。

 1 void InitAnimation()
 2 {
 3     if (compositor != null)
 4     {
 5         for (int i = 0; i < flipView.Items.Count; i++)
 6         {
 7             var item = flipView.ContainerFromIndex(i);
 8             if (item is UIElement ele)
 9             {
10                 var visual = ElementCompositionPreview.GetElementVisual(ele);
11                 CenterPointAnimation.SetReferenceParameter("visual", visual);
12                 visual.StartAnimation("CenterPoint", CenterPointAnimation);
13                 visual.StopAnimation("Scale.X");
14                 visual.StopAnimation("Scale.Y");
15                 ScaleAnimation.SetReferenceParameter("visual", visual);
16                 visual.StartAnimation("Scale.X", ScaleAnimation);
17                 visual.StartAnimation("Scale.Y", ScaleAnimation);
18             }
19         }
20     }
21 }

 

最后在Loaded的最后也调用一次InitAnimation,大功告成。

源代码下载

posted on 2017-08-18 12:30 叫我蓝火火 阅读(...) 评论(...) 编辑 收藏

导航