1. 程式人生 > >UWP:使用Behavior實現FlipView簡單縮放效果

UWP:使用Behavior實現FlipView簡單縮放效果

spl send 應該 code load isp start 個性 source

先上效果圖

技術分享

首先安裝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,大功告成。

源代碼下載

UWP:使用Behavior實現FlipView簡單縮放效果