• 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,大功告成。

    源代码下载

  • 相关阅读:
    第十五周总结
    第二阶段冲刺
    课堂练习
    第十四周总结
    WP8 NavigationInTransition实现页面切换效果
    WP8学习笔记:如何在页面显示前自动转向到其他页面
    WP8简单的计算器
    WP8滑动条(Slider)控件的使用
    Wp8滚动区域(ScrollViewer)控件的使用
    C#中ToString格式大全
  • 原文地址:https://www.cnblogs.com/blue-fire/p/7389002.html
Copyright © 2020-2023  润新知