Maison  >  Article  >  développement back-end  >  Introduction à l'utilisation de l'API Composition pour obtenir un plafond plafond dans UWP (2)

Introduction à l'utilisation de l'API Composition pour obtenir un plafond plafond dans UWP (2)

零下一度
零下一度original
2017-06-26 15:38:231872parcourir

Dans l'article précédent, nous avons discuté de l'opération de plafond qui n'implique pas de Pivot, mais de manière générale, la partie plafond est l'en-tête de Pivot, nous allons donc discuter ici de plusieurs aspects de Pivot Item. associé au même en-tête.

Comme d'habitude, créez d'abord une page simple. La page a une grille comme en-tête, un pivot avec la tête supprimée, et il y a trois ListViews dans le Pivot. à la même hauteur que l'en-tête de la page. En-tête systématiquement vide.

<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>

Le modèle de Pivot est trop long, je ne l'écrirai donc pas ici. Si nécessaire, recherchez une ressource de pinceau intégrée au système et appuyez sur F12 pour ouvrir generic.xaml, et. puis recherchez Pivot. Des modèles pour d’autres contrôles peuvent également être obtenus via cette méthode.

Modifiez ces phrases dans le modèle pour supprimer l'en-tête :

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

Ensuite, il y a le code d'arrière-plan. La méthode FindFirstChild de l'article précédent sera également utilisée. ici. Je ne le publierai pas ici.

Comme d'habitude, global _headerVisual, il est préférable d'initialiser les variables dont nous avons besoin dans l'événement Loaded de Page. J'étais paresseux et de les mettre directement dans la méthode UpdateAnimation ci-dessous.
Ensuite, nous écrivons une méthode UpdateAnimation pour mettre à jour les paramètres d'animation lorsque le PivotItem change.

Déterminez d'abord si la page n'est pas sélectionnée et revenez, puis récupérez le conteneur de l'élément actuellement sélectionné, puis récupérez le ScrollViewer du conteneur comme la dernière fois, mais il y a un piège ici, nous en parlerons plus tard.

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

Puis mettez à jour l'animation dans l'événement SelectionChanged de Pivot :

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

Cliquez pour exécuter, faites glisser de haut en bas, et rien ne se passe. Suivez le mouvement. Après avoir basculé vers la gauche et la droite, j'ai constaté que lorsque je suis passé à PivotItem pour la deuxième fois, j'ai pu suivre le mouvement. J'ai vu que _scrollviewer était nul lorsque j'ai exécuté "var _scrollviewer = FindFirstChild(SelectionItem);" temps. Après y avoir réfléchi longtemps, j'ai réalisé : est-ce un problème que le contrôle ne soit pas chargé, donc je ne peux pas obtenir le contrôle enfant ? Si vous dites changement, changez-le.

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

Exécutez-le à nouveau et suivez le mouvement. Mais il y a toujours un problème. L'en-tête reviendra à sa position d'origine à chaque fois qu'il sera commuté. C'est un autre écueil.
Je suppose que lors du changement de PivotItem, _manipulationPropertySet.Translation.Y deviendra 0 pendant un instant. S’il vous plaît, ne marchez pas sur les pièges sur lesquels j’ai encore marché.
Arrêtez l'animation avant d'essayer de la mettre à jour.

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

Exécutez, ça a échoué.
À ce moment-là, un éclair d'inspiration s'est produit. Il faut du temps pour que l'animation joue ! L'animation de ce commutateur est d'environ cinq étapes :

  1. déclenche SelectionChanged

  2. La page se déplace vers la gauche et disparaît progressivement

  3. Décharger la page ;

  4. Charger la nouvelle page

  5. La page se déplace de la droite vers le centre et apparaît progressivement ; .

SelectionChanged a été déclenché avant la première étape, puis a arrêté l'animation, a mis à jour l'animation et mon animation d'expression a commencé à jouer, mais sa première étape était toujours lente et n'était pas terminée... .

Simple, ajouter un délai à SelectionChanged peut résoudre (est-ce) le problème du repositionnement de Header (voici un autre écueil) :

private async void _pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    _headerVisual?.StopAnimation("Offset.Y");await Task.Delay(180);
    UpdateAnimation();
}
Exécuter, c'est parfait. Ensuite, je l'ai essayé sur mon téléphone et j'ai presque pleuré.

Pour les opérations clic et toucher, l'ordre de déclenchement des événements et de lecture des animations lors du changement de page est différent !
Le changement de page provoqué par le toucher suit à peu près les étapes suivantes :

  1. Le glissement provoque le déplacement de la page. Après avoir relâché prise, la page se déplace vers la gauche et disparaît progressivement

    .

  2. Sélection du déclencheur modifiée ;

  3. Décharger la page ;

  4. Charger la nouvelle page

  5. La page du côté droit se déplace vers le centre et émerge progressivement.

Mais après la disparition de la page, _manipulationPropertySet.Translation.Y deviendra 0 pendant un instant ! J’étais vraiment dévasté à ce moment-là, mais j’ai finalement trouvé une solution.

Ignorez-le simplement lorsque _manipulationPropertySet.Translation.Y devient 0. Vous ne pouvez pas être plus intelligent. De cette façon, il n'est pas nécessaire d'écrire un délai dans SelectionChanged, et j'ai l'impression que mon code devient soudainement beaucoup plus élégant.
Modifiez l'expression de _headerAnimation :

 _headerAnimation = _compositor.CreateExpressionAnimation(
Remarque : max, min et clamp sont toutes des fonctions intégrées dans l'animation d'expression. Pour des informations connexes, veuillez consulter l'annexe. .

Refaites le test et réussissez-le parfaitement. Un autre trou a été comblé. Après avoir joué avec cette démo pendant un moment, j'ai toujours l'impression qu'il y a encore quelques défauts. Lors du changement de page de gauche à droite, bouger la tête de haut en bas est trop rigide. Mon idée est de démarrer l'animation d'expression de la tête dans l'événement Complate qui ajuste l'animation de la position de la tête. Faisons-le :

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

滑动返回:

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn