前回の記事では、Pivotを使わない天井操作について説明しましたが、一般的に天井部分はPivotのヘッダーとなるため、ここでは複数のPivotアイテムが同じヘッダー条件に関連付けられる方法について説明します。
いつものように、まず、ヘッダーとしてグリッド、ヘッドを削除したピボットを持つシンプルなページを作成します。ピボットには 3 つの ListView があり、ListView は、ヘッダーと同じ高さの空のヘッダーを設定します。ページのヘッダー。
<Pagex:Class="TestListViewHeader.TestHeader2"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="using:TestListViewHeader"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"mc:Ignorable="d"><Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><Pivot ItemsSource="{x:Bind ItemSource}" x:Name="_pivot" SelectionChanged="_pivot_SelectionChanged" ><Pivot.Template> <!--太长在这儿就不贴了--></Pivot.Template><Pivot.HeaderTemplate><DataTemplate></DataTemplate></Pivot.HeaderTemplate><Pivot.ItemTemplate><DataTemplate><ListView ItemsSource="{Binding }"><ListView.Header><Grid Height="150" /></ListView.Header><ListView.ItemTemplate><DataTemplate><TextBlock Text="{Binding }" /></DataTemplate></ListView.ItemTemplate></ListView></DataTemplate></Pivot.ItemTemplate></Pivot><Grid Height="150" VerticalAlignment="Top" x:Name="_header"><Grid.RowDefinitions><RowDefinition Height="100" /><RowDefinition Height="50" /></Grid.RowDefinitions><Grid Background="LightBlue"><TextBlock FontSize="30" VerticalAlignment="Center" HorizontalAlignment="Center">我会被隐藏</TextBlock></Grid><Grid Grid.Row="1"><ListBox SelectedIndex="{x:Bind _pivot.SelectedIndex,Mode=TwoWay}" ItemsSource="{x:Bind ItemSource}"><ListBox.ItemTemplate><DataTemplate><Grid><TextBlock Text="{Binding Title}" /></Grid></DataTemplate></ListBox.ItemTemplate><ListBox.ItemsPanel><ItemsPanelTemplate><VirtualizingStackPanel Orientation="Horizontal" /></ItemsPanelTemplate></ListBox.ItemsPanel></ListBox></Grid></Grid></Grid></Page>
Pivot のテンプレートは長すぎるため、ここでは書きません。必要に応じて、システムに組み込まれているブラシ リソースを見つけて F12 キーを押して generic.xaml を開き、他のコントロールのテンプレートを検索します。この方法でも取得できます。
テンプレート内の次の文を変更することでヘッダーを削除できます:
<PivotPanel x:Name="Panel" VerticalAlignment="Stretch"><Grid x:Name="PivotLayoutElement"><Grid.RowDefinitions><RowDefinition Height="0" /><RowDefinition Height="*" /><!--太长不写--></Grid.RowDefinitions>
次に、前の記事の FindFirstChild メソッドがここでも使用されるため、ここには投稿しません。
いつものように、global _headerVisual では、ページの Loaded イベントで必要な変数を初期化し、以下の UpdateAnimation メソッドに直接置くのが最善です。
次に、PivotItem が切り替わったときにアニメーション パラメーターを更新する UpdateAnimation メソッドを作成します。
まず、ページが選択されていないことを判断して戻り、現在選択されている項目のコンテナを取得し、前回と同様にコンテナからScrollViewerを取得しますが、ここには落とし穴があるので、それについては後ほど説明します。
void UpdateAnimation() {if (_pivot.SelectedIndex == -1) return;var SelectionItem = _pivot.ContainerFromIndex(_pivot.SelectedIndex) as PivotItem;if (SelectionItem == null) return;var _scrollviewer = FindFirstChild<ScrollViewer>(SelectionItem);if (_scrollviewer != null) { _headerVisual = ElementCompositionPreview.GetElementVisual(_header);var _manipulationPropertySet = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(_scrollviewer);var _compositor = Window.Current.Compositor;var line = _compositor.CreateCubicBezierEasingFunction(new System.Numerics.Vector2(0, 0), new System.Numerics.Vector2(0.6f, 1));var _headerAnimation = _compositor.CreateExpressionAnimation("_manipulationPropertySet.Translation.Y > -100f ? _manipulationPropertySet.Translation.Y: -100f"); _headerAnimation.SetReferenceParameter("_manipulationPropertySet", _manipulationPropertySet); _headerVisual.StartAnimation("Offset.Y", _headerAnimation); } }
次に、Pivot の SelectionChanged イベントでアニメーションを更新します。
private void _pivot_SelectionChanged(object sender, SelectionChangedEventArgs e) { UpdateAnimation(); }
クリックして実行し、上下にスライドさせますが、動きません。左右を切り替えた後、2 回目に PivotItem に切り替えたときに、最初に「var _scrollviewer = FindFirstChild
void UpdateAnimation() {if (_pivot.SelectedIndex == -1) return;var SelectionItem = _pivot.ContainerFromIndex(_pivot.SelectedIndex) as PivotItem;if (SelectionItem == null) return;var _scrollviewer = FindFirstChild<ScrollViewer>(SelectionItem);if (_scrollviewer != null) { _headerVisual = ElementCompositionPreview.GetElementVisual(_header);var _manipulationPropertySet = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(_scrollviewer);var _compositor = Window.Current.Compositor;var line = _compositor.CreateCubicBezierEasingFunction(new System.Numerics.Vector2(0, 0), new System.Numerics.Vector2(0.6f, 1));var _headerAnimation = _compositor.CreateExpressionAnimation("_manipulationPropertySet.Translation.Y > -100f ? _manipulationPropertySet.Translation.Y: -100f"); _headerAnimation.SetReferenceParameter("_manipulationPropertySet", _manipulationPropertySet); _headerVisual.StartAnimation("Offset.Y", _headerAnimation); }elseSelectionItem.Loaded += (s, a) =>{ UpdateAnimation(); }; }
もう一度走って、その動きに従ってください。しかし、ヘッダーが切り替わるたびに元の位置に戻ってしまうという問題がまだあります。これもまた落とし穴です。
PivotItemを切り替えると_manipulationPropertySet.Translation.Yが一瞬0になるのでしょう。私が踏んだ落とし穴を二度と踏まないでください。
更新する前にアニメーションを停止してください。
private void _pivot_SelectionChanged(object sender, SelectionChangedEventArgs e) { _headerVisual?.StopAnimation("Offset.Y"); UpdateAnimation(); }
実行すると予想通り失敗しました。
この瞬間、ひらめきが起こりました。アニメーションの再生には時間がかかります。この切り替えのアニメーションは約 5 つのステップです。
はページを左に移動し、
は新しいページを読み込みます。
private async void _pivot_SelectionChanged(object sender, SelectionChangedEventArgs e) { _headerVisual?.StopAnimation("Offset.Y");await Task.Delay(180); UpdateAnimation(); }
クリック操作とタッチ操作では、ページ切り替え時のイベントのトリガーとアニメーションの再生順序が異なります。
タッチによるページの切り替えは大まかに次の手順です。
スライドするとページが左に移動し、徐々に消えます
SelectionChangedをトリガーします
_headerAnimation のエクスプレッションを変更します:
_headerAnimation = _compositor.CreateExpressionAnimation(
注: max、min、clamp はすべてエクスプレッション アニメーションの組み込み関数です。関連情報については、付録を参照してください。
var line = _compositor.CreateCubicBezierEasingFunction(new System.Numerics.Vector2(0, 0), new System.Numerics.Vector2(0.6f, 1));var MoveHeaderAnimation = _compositor.CreateScalarKeyFrameAnimation(); MoveHeaderAnimation.InsertExpressionKeyFrame(0f, "_headerVisual.Offset.Y", line); MoveHeaderAnimation.InsertExpressionKeyFrame(1f, "_manipulationPropertySet.Translation.Y > -100f ? _manipulationPropertySet.Translation.Y: -100f", line); MoveHeaderAnimation.SetReferenceParameter("_headerVisual", _headerVisual); MoveHeaderAnimation.SetReferenceParameter("_manipulationPropertySet", _manipulationPropertySet); MoveHeaderAnimation.DelayTime = TimeSpan.FromSeconds(0.18d); MoveHeaderAnimation.Duration = TimeSpan.FromSeconds(0.1d);
创建一个关键帧动画,line是缓动效果。关键帧动画ScalarKeyFrameAnimation可以插入两种帧,一种是InsertKeyFrame(float,float,easingfunctuin),插入一个数值帧;一种是InsertExpressionKeyFrame(float,string,easingfunctuin),插入一个表达式帧,两者的第一个参数是进度,最小是0最大是1;第三个参数都是函数,可以设置为线性,贝塞尔曲线函数和步进。
这时候就又发现了一个惊!天!大!秘!密!
CompositionAnimation和CompositionAnimationGroup是没有Complated事件的!
只能手动给延时了。然后...
表达式动画不!支!持!延!时!好尴尬。
同样是动画,看看隔壁家的StoryBoard,CompositionAnimation你们羞愧不羞愧。
经过一番必应之后,我发现我错怪了他们,CompositionAnimation也可以做到Complated事件,只是方法有些曲折而已。
动画完成事件
通过使用关键帧动画,开发人员可以在完成精选动画(或动画组)时使用动画批来进行聚合。 仅可以批处理关键帧动画完成事件。 表达式动画没有一个确切终点,因此它们不会引发完成事件。 如果表达式动画在批中启动,该动画将会像预期那样执行,并且不会影响引发批的时间。
当批内的所有动画都完成时,将引发批完成事件。 引发批的事件所需的时间取决于该批中时长最长的动画或延迟最为严重的动画。 在你需要了解选定的动画组将于何时完成以便计划一些其他工作时,聚合结束状态非常有用。
批在引发完成事件后释放。 还可以随时调用 Dispose() 来尽早释放资源。 如果批处理的动画结束较早,并且你不希望继续完成事件,你可能会想要手动释放批对象。 如果动画已中断或取消,将会引发完成事件,并且该事件会计入设置它的批。
在动画开始前,新建一个ScopedBatch对象,然后播放动画,紧接着关闭ScopedBatch,动画运行完之后就会触发ScopedBatch的Completed事件。在ScopedBatch处于运行状态时,会收集所有动画,关闭后开始监视动画的进度。说的云里来雾里去的,还是看代码吧。
var Betch = _compositor.CreateScopedBatch(Windows.UI.Composition.CompositionBatchTypes.Animation); _headerVisual.StartAnimation("Offset.Y", MoveHeaderAnimation); Betch.Completed += (s, a) =>{var _headerAnimation = _compositor.CreateExpressionAnimation("_manipulationPropertySet.Translation.Y > -100f ? (_manipulationPropertySet.Translation.Y == 0?This.CurrentValue :_manipulationPropertySet.Translation.Y) : -100f");//_manipulationPropertySet.Translation.Y是ScrollViewer滚动的数值,手指向上移动的时候,也就是可视部分向下移动的时候,Translation.Y是负数。_headerAnimation.SetReferenceParameter("_manipulationPropertySet", _manipulationPropertySet); _headerVisual.StartAnimation("Offset.Y", _headerAnimation); }; Betch.End();
我们把构造和播放_headerAnimation的代码放到了ScopedBatch的Complated事件里,这时再运行一下,完美。
其实还是有点小问题,比如Header没有设置Clip,上下移动的时候有时会超出预期的范围之类的,有时间我们会继续讨论,这篇已经足够长,再长会吓跑人的。
Demo已经放到Github,里面用到了一个写的很糙的滑动返回控件,等忙过这段时间整理下代码就开源,希望能有大牛指点一二。
Github:
总结一下,实现吸顶最核心的代码就是获取到ScrollViewer,不一定要是ListView的,明白了这一点,所有含有ScrollViewer的控件都可以放到这个这个页面使用。
滑动返回:
以上がUWP で上限を達成するための Comboposition API の使用の概要 (2)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。