Home  >  Article  >  Backend Development  >  Introduction to using Composition API to achieve ceiling ceiling in UWP (2)

Introduction to using Composition API to achieve ceiling ceiling in UWP (2)

零下一度
零下一度Original
2017-06-26 15:38:231907browse

In the previous article, we discussed the ceiling operation that does not involve Pivot, but generally speaking, the ceiling part is the Header of Pivot, so here we will discuss multiple aspects of Pivot. Item is associated with the same Header.

As usual, first make a simple page. The page has a Grid as the header, a Pivot with the head removed, and there are three ListViews in the Pivot. The ListView is set to the same height as the page Header. Consistently blank header.

<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’s template is too long so I won’t write it here. If necessary, find a brush resource built into the system and press F12 to open generic.xaml, then search for Pivot and other controls. Templates can also be obtained through this method.

Modify these sentences in the template to remove the header:

<PivotPanel x:Name="Panel" VerticalAlignment="Stretch"><Grid x:Name="PivotLayoutElement"><Grid.RowDefinitions><RowDefinition Height="0" /><RowDefinition Height="*" /><!--太长不写--></Grid.RowDefinitions>

Then there is the background code. The FindFirstChild method from the previous article will also be used here, here I won’t post it anymore.

As always, for the global _headerVisual, it is best to initialize the variables we need in the Loaded event of the Page. I was lazy and put them directly in the UpdateAnimation method below.
Then we write an UpdateAnimation method to update the parameters of the animation when the PivotItem switches.

First determine if the page is not selected and return, then get the container of the currently selected item, and then get the ScrollViewer from the container like last time, but there is a pit here, we will talk about it later.

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);
    }
}

Then update the animation in the SelectionChanged event of Pivot:

private void _pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    UpdateAnimation();
}

Click to run, slide up and down, but it does not move. After switching left and right, I found that when I switched to PivotItem for the second time, I could follow the movement. I saw that _scrollviewer was null when I ran "var _scrollviewer = FindFirstChild(SelectionItem);" for the first time. After thinking about it for a long time, I realized, is it a problem that the control is not Loaded, so I can't get the child control? If you say change, change it.

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();
        };
}

Run it again and follow the movement. But there is still a problem. The Header will return to its original position every time it is switched. This is another pitfall.
I guess that when switching PivotItem, _manipulationPropertySet.Translation.Y will become 0 for an instant. Don’t step on the pitfalls I’ve stepped on again.
Stop the animation before trying to update it.

private void _pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    _headerVisual?.StopAnimation("Offset.Y");
    UpdateAnimation();
}

Run it and it failed.
At this moment, a flash of inspiration occurred. It takes time for animation to play! The animation of this switch is about five steps:

  1. triggers SelectionChanged;

  2. the page moves to the left and gradually disappears;

  3. Unload the page;

  4. Load the new page;

  5. The page moves from the right to the center and gradually appears.

SelectionChanged was triggered before the first step, then stopped the animation, updated the animation, and my expression animation started to play, but his first step was still slow and not finished... .
Simple, adding a delay to SelectionChanged can solve (is it) the problem of Header repositioning (here is another pitfall):

private async void _pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    _headerVisual?.StopAnimation("Offset.Y");await Task.Delay(180);
    UpdateAnimation();
}

Running, perfect . Then I tried it on my phone and almost cried.
For click and touch operations, the order of triggering events and playing animations when switching pages is different!
The switching page caused by touch is probably the following steps:

  1. Sliding causes the page to shift. After letting go, the page moves to the left and gradually disappears

  2. Trigger SelectionChanged;

  3. Unload the page;

  4. Load the new page;

  5. The page changes from The right side moves to the center and emerges gradually.

But after the page disappears, _manipulationPropertySet.Translation.Y will become 0 for a moment! I was really devastated at this time, but in the end I came up with a solution.
_manipulationPropertySet.Translation.Y When it becomes 0, just ignore him. You can't be smarter. In this way, there is no need to write a delay in SelectionChanged, and I feel that my code suddenly becomes a lot more elegant.
Modify the expression of _headerAnimation:

 _headerAnimation = _compositor.CreateExpressionAnimation(

Note: max, min, and clamp are all built-in functions in expression animation. For relevant information, please see the appendix.

Took the test again, passed it perfectly, and filled in another hole. After playing with this demo for a while, I still feel that there are still some shortcomings. When switching pages left and right, moving the head up and down is too stiff. My idea is to start the expression animation of the head in the Complate event that adjusts the head position animation. Let’s do it:

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的控件都可以放到这个这个页面使用。

滑动返回:

The above is the detailed content of Introduction to using Composition API to achieve ceiling ceiling in UWP (2). For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn