話很少說,先上效果
這裏使用了一個ScrollProgressProvider.cs,咱們這篇文章先解析一下總體的動畫思路,之後再詳細解釋這個Provider的實現方式。html
整個頁面大體結構是git
<Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Grid x:Name="Target"> <TextBlock /> <Header /> </Grid> <Pivot.ItemTemplate Grid.RowSpan="2"> <Pivot.ItemTemplate> <DataTemplate> <ScrollViewer x:Name="sv"> <StackPanel> <Border Margin="0,250,0,0" /> </StackPanel> </ScrollViewer> </DataTemplate> </DataTemplate> </Pivot.ItemTemplate> </Grid>
這個Header是修改的ListBox,固然也能夠用ListView代替。 隱藏Pivot默認Header的方式是在Pivot的樣式中找到以下行。github
<PivotPanel x:Name="Panel" VerticalAlignment="Stretch"> <Grid x:Name="PivotLayoutElement"> <Grid.RowDefinitions> <RowDefinition Height="0" /><!--修改這行爲0--> <RowDefinition Height="*" /> </Grid.RowDefinitions> ...
動畫過程大體就是在Pivot頁面切換時,查找到當頁的ScrollViewer,綁定動畫。windows
你們在爬視圖樹時,應該常常遇到元素還未加載的狀況,這裏爲了解決這種情況,封裝了一個WaitForLoaded方法。api
private async Task<T> WaitForLoaded<T>(FrameworkElement element, Func<T> func, Predicate<T> pre, CancellationToken cancellationToken) { TaskCompletionSource<T> tcs = null; try { tcs = new TaskCompletionSource<T>(); cancellationToken.ThrowIfCancellationRequested(); var result = func.Invoke(); if (pre(result)) return result; element.Loaded += Element_Loaded; return await tcs.Task; } catch { element.Loaded -= Element_Loaded; var result = func.Invoke(); if (pre(result)) return result; } return default; void Element_Loaded(object sender, RoutedEventArgs e) { if (tcs == null) return; try { cancellationToken.ThrowIfCancellationRequested(); element.Loaded -= Element_Loaded; var _result = func.Invoke(); if (pre(_result)) tcs.SetResult(_result); else tcs.SetCanceled(); } catch { System.Diagnostics.Debug.WriteLine("canceled"); } } }
使用起來是這樣的緩存
CancellationTokenSource cts; private async void EventChanged(object sender, EventArgs e) { if (cts != null) cts.Cancel(); cts = new CancellationTokenSource(TimeSpan.FromSeconds(2)); var child = await WaitForLoaded(element, () => find_element_method(), c => judge_find_success_method(), cts.Token); }
咱們在Pivot的SelectionChanged事件裏,修改ScrollProgressProvider託管的ScrollViewer,provider就會自動將ScrollViewer設置到正確的位置。async
接下來在Page的Loaded事件中綁定動畫,這裏有兩種選擇。provider提供了ProgressChanged事件和GetProgressPropertySet方法。能夠在ProgressChanged事件中直接設置元素的值來實現動畫,不過因爲ScrollViewer的限制,ProgressChanged事件觸發頻率不是很高,因此更推薦使用GetProgressPropertySet獲取到CompositionPropertySet,經過Composition Api實現動畫。ide
var providerProp = provider.GetProgressPropertySet(); var gv = ElementCompositionPreview.GetElementVisual(Target); // 容器Visual var tv = ElementCompositionPreview.GetElementVisual(HeaderText); //文本Visual
ScrollProgressProvider生成的PropertySet內有progress和threshold兩個字段能夠用做動畫。 Composition Api提供了Lerp(start, end, progress)方法,用在此處恰好合適。 咱們須要定義容器平移,文本平移和文本縮放三個動畫。動畫
var gvOffsetExp = Window.Current.Compositor.CreateExpressionAnimation("Vector3(0f, -provider.threshold * provider.progress, 0f)"); gvOffsetExp.SetReferenceParameter("provider", providerProp); gv.StartAnimation("Offset", gvOffsetExp);
var startOffset = "Vector3((host.Size.X - this.Target.Size.X) / 2, (host.Size.Y - 50 - this.Target.Size.Y) / 2, 1f)"; var endOffset = $"Vector3(0f, provider.threshold, 1f)"; var offsetExp = Window.Current.Compositor.CreateExpressionAnimation($"lerp({startOffset}, {endOffset}, provider.progress)"); offsetExp.SetReferenceParameter("host", gv); offsetExp.SetReferenceParameter("provider", providerProp); tv.StartAnimation("Offset", offsetExp);
var scale = "(50f / this.Target.Size.Y)"; var startScale = "Vector3(1f, 1f, 1f)"; var endScale = $"Vector3({scale}, {scale}, 1f)"; var scaleExp = Window.Current.Compositor.CreateExpressionAnimation($"lerp({startScale}, {endScale}, provider.progress)"); scaleExp.SetReferenceParameter("host", gv); scaleExp.SetReferenceParameter("provider", providerProp); tv.StartAnimation("Scale", scaleExp);
觸摸比起鼠標點擊要更復雜一些。 Pivot應該是UWP內置控件裏比較玄學的一個了。 對於鼠標操做,Pivot會先觸發SelectionChanged事件,再觸發PivotItemLoaded事件,而且播放動畫。 而對於觸摸事件,整個順序是相反的。手指開始滑動界面時,能夠被看到的Item會開始加載,而且觸發PivotItemLoaded事件,鬆手以後纔開始計算是否應該導航到其餘頁,而且決定是否觸發SelectionChanged事件。這樣就會有一個問題,咱們在SelectionChanged中修改ScrollViewer偏移以前,咱們已經能看到他了,這時的高度是不正確的。咱們須要抽象出一個能夠在鼠標和觸摸觸發事件時將下一個Item的ScrollViewer設置爲正確偏移的方法。 個人想法很簡單,將全部已加載的頁內的ScrollViewer緩存下來,隨着Progress的改變而改變,作法也很簡單。this
private HashSet<ScrollViewer> scrolls = new HashSet<ScrollViewer>(); private async void Pivot_SelectionChanged(object sender, SelectionChangedEventArgs e) { ... scrolls.Remove(provider.ScrollViewer); } private void Pivot_PivotItemLoaded(Pivot sender, PivotItemEventArgs args) { var sv = (args.Item.ContentTemplateRoot as FrameworkElement).FindName("sv") as ScrollViewer; if (sv != provider.ScrollViewer) { sv.ChangeView(null, provider.Progress * provider.Threshold, null, true); scrolls.Add(sv); } } private void Pivot_PivotItemUnloading(Pivot sender, PivotItemEventArgs args) { var sv = (args.Item.ContentTemplateRoot as FrameworkElement).FindName("sv") as ScrollViewer; if (sv != null) { scrolls.Remove(sv); } } private void Provider_ProgressChanged(object sender, double args) { foreach (var sv in scrolls) { sv.ChangeView(null, provider.Progress * provider.Threshold, null, true); } }
須要注意的是,咱們要在加載完成事件中獲取ScrollViewer,而在卸載開始事件中移除ScrollViewer。
GitHub: https://github.com/cnbluefire/ShyHeaderPivot
ExpressionAnimation: https://docs.microsoft.com/en-us/uwp/api/Windows.UI.Composition.ExpressionAnimation
CompositionAnimation: https://docs.microsoft.com/zh-cn/windows/uwp/composition/composition-animation 個人博客: 超威藍火
原文出處:https://www.cnblogs.com/blue-fire/p/11376450.html