Use the Composition API in UWP to achieve ceiling (2) and uwpcomposition

Source: Internet
Author: User

Use the Composition API in UWP to achieve ceiling (2) and uwpcomposition

In the previous article, we discussed the top-sucking operations that do not involve the scheme, but in general, the top-sucking part is the scheme Header, so here we will discuss how to associate multiple items with the same Header.

The old one is to make a simple page first. The page has a Grid as the Header, and a shard with the Header removed. There are three ListView In the shard, listView sets a blank Header with the same height as the page Header.

<Page x: 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} "> <shortitemssource =" {x: Bind ItemSource} "x: name = "_ optional" SelectionChanged = "_ effect_selectionchanged"> <optional. template> <! -- Too long to paste here --> </strong. template> <strong. headerTemplate> <DataTemplate> </Pivot emplate. headerTemplate> <strong. itemTemplate> <DataTemplate> <ListView ItemsSource = "{Binding}"> <ListView. header> <Grid Height = "150"/> </ListView. header> <ListView. itemTemplate> <DataTemplate> <TextBlock Text = "{Binding}"/> </DataTemplate> </ListView. itemTemplate> </ListView> </DataTemplate> </plate. itemTemplate> </layout> <Grid Height = "150" VerticalAlignment = "Top" x: Name = "_ header"> <Grid. rowDefinitions> <RowDefinition Height = "100"/> <RowDefinition Height = "50"/> </Grid. rowDefinitions> <Grid Background = "LightBlue"> <TextBlock FontSize = "30" verticalignment = "Center" HorizontalAlignment = "Center"> I will be hidden </TextBlock> </Grid> <grid. row = "1"> <ListBox SelectedIndex = "{x: Bind _ partition. 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> </Page>

If necessary, find a built-in system brush resource and press F12 to open the generic. xaml, and then search for controls. Other control templates can also be obtained through this method.

Modify these statements in the template to remove the header:

<Effectpanel x: Name = "Panel" verticalignment = "Stretch"> <Grid x: Name = "effectlayoutelement"> <Grid. rowDefinitions> <RowDefinition Height = "0"/> <RowDefinition Height = "*"/> <! -- Too long to write --> </Grid. RowDefinitions>

Then there is the background code. Here we will use the FindFirstChild method in the previous article, so we will not post it here.

The global _ headerVisual is the best way to initialize the variables we need in the Loaded event of Page. I am lazy and put them directly in the following UpdateAnimation method.
Then we write an UpdateAnimation method to update the Animation Parameters during the titem switchover.

First, judge whether to return if the selected page is not selected, then get the container of the currently selected item, and then get ScrollViewer from the container as before, but there is a pitfall here. Let's 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 the notebook:

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

Click and run. The system slides down and does not move. After switching between the left and right, it is found that the system can be moved when the second switch is switched to effectitem. The next disconnection shows that the first run is "var _ scrollviewer = FindFirstChild <ScrollViewer> (SelectionItem ); "_ scrollviewer is null. After thinking for a long time, I realized that the control has no Loaded problem, so I cannot get the child control? 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);    }    else        SelectionItem.Loaded += (s, a) =>        {            UpdateAnimation();        };}

Run again. However, there is another problem. During each switchover, the Header will return to the original position once. This is another pitfall.
It is assumed that when switching titem, _ manipulationPropertySet. Translation. Y will instantly change to 0. If I step on it, you should not step on it again.
Stop the animation before trying to update the animation.

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

Run.
In this case, it takes time to play an animation! The animation for this switchover is about five steps:

Before the first step, SelectionChanged was triggered, and then the animation was stopped, updated, and I started playing the Expression Animation. His first step was not completed...
Simple: Add a latency in SelectionChanged to solve the Header homing problem (here, another pitfall is buried ):

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 cell phone and almost cried.
For click and touch operations, the order of triggering events and playing animations during page switching is different!
The switch page caused by the touch process is as follows:

But when the page disappears, _ manipulationPropertySet. Translation. Y will change to 0 in a moment! At this time, I really collapsed, but finally came up with a solution for me.
When _ manipulationPropertySet. Translation. Y is changed to 0, it is not good to ignore him. In this way, there is no need to write latency in SelectionChanged, and I feel that my code has become much more elegant.
Modify the expression of _ headerAnimation:

// Var _ headerAnimation = _ compositor. CreateExpressionAnimation ("_ manipulationPropertySet. Translation. Y>-100f? (_ ManipulationPropertySet. Translation. Y = 0? This. currentValue: _ manipulationPropertySet. translation. y):-100f "); // after sorting, var _ headerAnimation = _ compositor. createExpressionAnimation ("Clamp (_ manipulationPropertySet. translation. y,-100f, _ manipulationPropertySet. translation. Y = 0? This. CurrentValue: 0f) "); // Note: This. CurrentValue is one of the three fixed variables in the Expression Animation, representing the current value of the property set for the animation. The other two values are This. StartingValue, which indicates the starting value of the animation, and Pi, which indicates the circumference rate...
// NOTE 2: Clamp (value, min, max). If the value is smaller than min, min is returned. If the value is greater than max, max is returned. If the value is greater than max, the value itself is returned.

Note: max, min, and clamp are built-in functions in Expression Animation. For more information, see the appendix.

Then perform the test, pass the test perfectly, and fill in another pitfall. After playing with this Demo for a while, I always felt that there were still some shortcomings. When switching between the left and right pages, it was too stiff to move the head up and down. My idea is to start the Expression Animation of the head in the Complate event where the animation of the head position is adjusted. Let's just 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);

Create a Key Frame Animation. line is the easing effect. Key Frame Animation ScalarKeyFrameAnimation can be inserted into two types of frames. One is InsertKeyFrame (float, float, easingfunctuin), and the other is InsertExpressionKeyFrame (float, string, easingfunctuin ), insert an expression frame. The first parameter of the two is progress. The minimum parameter is 0 and the maximum parameter is 1. The third parameter is a function, which can be set to linear, besell curve function, and step.

At this time, I was surprised again! Day! Big! Secret! Password!
CompositionAnimation and CompositionAnimationGroup do not have a Complated event!
You can only manually delay the operation. Then...
No Expression Animation! Support! Hold! Delay! Time! So embarrassing.

It's also an animation. Look at the StoryBoard next door, CompositionAnimation. You're ashamed and ashamed.

After some Bing, I found that I was wrong with them. CompositionAnimation can also be used to perform the Complated event, but the method is somewhat tortuous.

Animation completion event

By using a key frame animation, developers can use an animation batch to aggregate a selected animation (or animation group. Only Key Frame Animation completion events can be processed in batches. Expression animations do not have an exact end point, so they do not trigger completion events. If the expression animation is started in batches, the animation will be executed as expected and will not affect the time when the batch is triggered.

When all animations in the batch are completed, a batch completion event is triggered. The time required to trigger a batch event depends on the animation with the longest length in the batch or the animation with the most serious delay. The aggregation end state is useful when you need to know when the selected animation group will be completed to plan some other work.

Release the batch after a completion event is triggered. You can also call Dispose () at any time to release resources as soon as possible. If the batch animation ends earlier and you do not want to finish the event, you may want to manually release the batch image. If the animation has been interrupted or canceled, a completion event is triggered and the event is included in the batch setting.

Before the animation starts, create a new ScopedBatch object, play the animation, and then close ScopedBatch. After the animation is run, the Completed event of ScopedBatch is triggered. When ScopedBatch is running, it collects all animations and starts to monitor the animation progress after it is disabled. Let's see the code.

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 is the ScrollViewer's scroll value. When the finger moves up, that is, when the visible part moves down, translate. Y is a negative number. _ HeaderAnimation. SetReferenceParameter ("_ manipulationPropertySet", _ manipulationPropertySet); _ headerVisual. StartAnimation ("Offset. Y", _ headerAnimation);}; Betch. End ();

We put the code for constructing and playing _ headerAnimation into the Complated event of ScopedBatch, and then run it again, which is perfect.

In fact, there is still a small problem. For example, if the Clip is not set for the Header and the Clip is moved up or down, it sometimes exceeds the expected range. We will continue to discuss it for some time. This article is long enough, it will scare people away.
The Demo has been put on Github, where it uses a very rough sliding return control. After busy working, the code will be open-source. I hope you can give me some advice.

Github: https://github.com/cnbluefire/TestListViewHeader

To sum up, the most important code to achieve ceiling suction is to get ScrollViewer, which is not necessarily ListView. To understand this, all controls containing ScrollViewer can be placed on this page.

Slide return:

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.