话很少说,先上效果
这里使用了一个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