UWP中如何使用CompositionAPI實(shí)現(xiàn)吸頂

小編給大家分享一下UWP中如何使用Composition API實(shí)現(xiàn)吸頂,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

創(chuàng)新互聯(lián)是一家專業(yè)提供新泰企業(yè)網(wǎng)站建設(shè),專注與網(wǎng)站制作、網(wǎng)站建設(shè)、H5頁(yè)面制作、小程序制作等業(yè)務(wù)。10年已為新泰眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)的建站公司優(yōu)惠進(jìn)行中。

先做一個(gè)簡(jiǎn)單的頁(yè)面,頁(yè)面有一個(gè)Grid當(dāng)Header,一個(gè)去掉了頭部的Pivot,Pivot內(nèi)有三個(gè)ListView,ListView設(shè)置了和頁(yè)面Header高度一致的空白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>               <!--太長(zhǎng)在這兒就不貼了--></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">我會(huì)被隱藏</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的模板太長(zhǎng)在這兒就不寫(xiě)了,需要的話,找個(gè)系統(tǒng)內(nèi)置的畫(huà)筆資源按F12打開(kāi)generic.xaml,然后搜索Pivot就是了,其他控件的模板也能通過(guò)這個(gè)方法獲取。

模板里修改這幾句就能去掉頭部:

<PivotPanel x:Name="Panel" VerticalAlignment="Stretch"><Grid x:Name="PivotLayoutElement"><Grid.RowDefinitions><RowDefinition Height="0" /><RowDefinition Height="*" /><!--太長(zhǎng)不寫(xiě)--></Grid.RowDefinitions>

然后是后臺(tái)代碼,這里還會(huì)用到上一篇的FindFirstChild方法,在這兒就不貼出來(lái)了。

老樣子,全局的_headerVisual,最好在Page的Loaded事件里初始化我們所需要的這些變量,我偷懶了,直接放到了下面的UpdateAnimation方法里。
然后我們寫(xiě)一個(gè)UpdateAnimation方法,用來(lái)在PivotItem切換的時(shí)候更新動(dòng)畫(huà)的參數(shù)。

先判斷下如果未選中頁(yè)就return,然后獲取到當(dāng)前選中項(xiàng)的容器,再像上次一樣從容器里獲取ScrollViewer,不過(guò)這里有個(gè)坑,稍后再說(shuō)。

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事件里更新動(dòng)畫(huà):

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

點(diǎn)下運(yùn)行,上下滑一下,并沒(méi)有跟著動(dòng)。左右切換一下之后,發(fā)現(xiàn)在第二次切換到PivotItem的時(shí)候就可以跟著動(dòng)了,下斷看到第一次運(yùn)行到"var _scrollviewer = FindFirstChild<ScrollViewer>(SelectionItem);"的時(shí)候_scrollviewer為null。想了好久才意識(shí)到,是不是控件沒(méi)有Loaded的問(wèn)題,所以才取不到子控件?說(shuō)改就改。

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

再次運(yùn)行,跟著動(dòng)了。但是還有個(gè)問(wèn)題,在每次切換的時(shí)候,Header都會(huì)回歸原位一次。這又是一個(gè)坑。
猜想在切換PivotItem的時(shí)候,_manipulationPropertySet.Translation.Y會(huì)有一個(gè)瞬間變成0。我踩過(guò)的坑大家就不要再踩了。
嘗試更新動(dòng)畫(huà)前先停止動(dòng)畫(huà)。

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

運(yùn)行,果然失敗了。
這時(shí)靈光一閃,動(dòng)畫(huà)播放時(shí)需要時(shí)間的?。∵@個(gè)切換的動(dòng)畫(huà)大概是五步:

  1. 觸發(fā)SelectionChanged;

  2. 頁(yè)面左移并且逐漸消失;

  3. 卸載頁(yè)面;

  4. 裝載新頁(yè)面;

  5. 頁(yè)面從右側(cè)移動(dòng)到中心并且逐漸顯現(xiàn)。

第一步開(kāi)始之前觸發(fā)了SelectionChanged,然后停止動(dòng)畫(huà),更新動(dòng)畫(huà),我表達(dá)式動(dòng)畫(huà)都開(kāi)始播放了,他的第一步還慢悠悠的沒(méi)有走完...
簡(jiǎn)單,在SelectionChanged里加延時(shí),就能解決(是嗎)Header歸位的問(wèn)題(這里又埋下一個(gè)坑):

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

運(yùn)行,很完美。然后在手機(jī)上試了一下,差點(diǎn)哭了。
對(duì)于點(diǎn)擊和觸摸兩種操作方式,切換頁(yè)時(shí)觸發(fā)事件和播放動(dòng)畫(huà)的順序不一樣!
觸摸造成的切換頁(yè),大概是如下幾步:

  1. 滑動(dòng)造成頁(yè)面位移,松手后頁(yè)面左移并且逐漸消失

  2. 觸發(fā)SelectionChanged;

  3. 卸載頁(yè)面;

  4. 裝載新頁(yè)面;

  5. 頁(yè)面從右側(cè)移動(dòng)到中心并且逐漸顯現(xiàn)。

可是頁(yè)面消失后_manipulationPropertySet.Translation.Y會(huì)有一瞬間變成0??!這時(shí)候的我真的是崩潰的,不過(guò)最后還是給我想出了解決方案。
_manipulationPropertySet.Translation.Y變成0的時(shí)候不理他不就好了,不能再機(jī)智。這樣也不需要SelectionChanged里寫(xiě)延時(shí)了,感覺(jué)自己的代碼一下子變得優(yōu)雅了很多呢。
修改_headerAnimation的表達(dá)式:

 _headerAnimation = _compositor.CreateExpressionAnimation(

注:其中max,min,clamp都是表達(dá)式動(dòng)畫(huà)中內(nèi)置的函數(shù),相關(guān)的信息可以查看附錄。

再進(jìn)行測(cè)試,完美通過(guò),又填好一個(gè)坑。玩弄了這個(gè)Demo一會(huì)兒后,總覺(jué)得還有些不足,左右切換頁(yè)的時(shí)候,頭部上下移動(dòng)太生硬了。我的設(shè)想是在調(diào)整頭部位置動(dòng)畫(huà)的Complate事件里開(kāi)始頭部的表達(dá)式動(dòng)畫(huà),說(shuō)干咱就干:

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

創(chuàng)建一個(gè)關(guān)鍵幀動(dòng)畫(huà),line是緩動(dòng)效果。關(guān)鍵幀動(dòng)畫(huà)ScalarKeyFrameAnimation可以插入兩種幀,一種是InsertKeyFrame(float,float,easingfunctuin),插入一個(gè)數(shù)值幀;一種是InsertExpressionKeyFrame(float,string,easingfunctuin),插入一個(gè)表達(dá)式幀,兩者的第一個(gè)參數(shù)是進(jìn)度,最小是0最大是1;第三個(gè)參數(shù)都是函數(shù),可以設(shè)置為線性,貝塞爾曲線函數(shù)和步進(jìn)。

這時(shí)候就又發(fā)現(xiàn)了一個(gè)驚!天!大!秘!密!
CompositionAnimation和CompositionAnimationGroup是沒(méi)有Complated事件的!
只能手動(dòng)給延時(shí)了。然后...
表達(dá)式動(dòng)畫(huà)不!支!持!延!時(shí)!好尷尬。

同樣是動(dòng)畫(huà),看看隔壁家的StoryBoard,CompositionAnimation你們羞愧不羞愧。

經(jīng)過(guò)一番必應(yīng)之后,我發(fā)現(xiàn)我錯(cuò)怪了他們,CompositionAnimation也可以做到Complated事件,只是方法有些曲折而已。

動(dòng)畫(huà)完成事件

通過(guò)使用關(guān)鍵幀動(dòng)畫(huà),開(kāi)發(fā)人員可以在完成精選動(dòng)畫(huà)(或動(dòng)畫(huà)組)時(shí)使用動(dòng)畫(huà)批來(lái)進(jìn)行聚合。 僅可以批處理關(guān)鍵幀動(dòng)畫(huà)完成事件。 表達(dá)式動(dòng)畫(huà)沒(méi)有一個(gè)確切終點(diǎn),因此它們不會(huì)引發(fā)完成事件。 如果表達(dá)式動(dòng)畫(huà)在批中啟動(dòng),該動(dòng)畫(huà)將會(huì)像預(yù)期那樣執(zhí)行,并且不會(huì)影響引發(fā)批的時(shí)間。

當(dāng)批內(nèi)的所有動(dòng)畫(huà)都完成時(shí),將引發(fā)批完成事件。 引發(fā)批的事件所需的時(shí)間取決于該批中時(shí)長(zhǎng)最長(zhǎng)的動(dòng)畫(huà)或延遲最為嚴(yán)重的動(dòng)畫(huà)。 在你需要了解選定的動(dòng)畫(huà)組將于何時(shí)完成以便計(jì)劃一些其他工作時(shí),聚合結(jié)束狀態(tài)非常有用。

批在引發(fā)完成事件后釋放。 還可以隨時(shí)調(diào)用 Dispose() 來(lái)盡早釋放資源。 如果批處理的動(dòng)畫(huà)結(jié)束較早,并且你不希望繼續(xù)完成事件,你可能會(huì)想要手動(dòng)釋放批對(duì)象。 如果動(dòng)畫(huà)已中斷或取消,將會(huì)引發(fā)完成事件,并且該事件會(huì)計(jì)入設(shè)置它的批。

在動(dòng)畫(huà)開(kāi)始前,新建一個(gè)ScopedBatch對(duì)象,然后播放動(dòng)畫(huà),緊接著關(guān)閉ScopedBatch,動(dòng)畫(huà)運(yùn)行完之后就會(huì)觸發(fā)ScopedBatch的Completed事件。在ScopedBatch處于運(yùn)行狀態(tài)時(shí),會(huì)收集所有動(dòng)畫(huà),關(guān)閉后開(kāi)始監(jiān)視動(dòng)畫(huà)的進(jìn)度。說(shuō)的云里來(lái)霧里去的,還是看代碼吧。

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滾動(dòng)的數(shù)值,手指向上移動(dòng)的時(shí)候,也就是可視部分向下移動(dòng)的時(shí)候,Translation.Y是負(fù)數(shù)。_headerAnimation.SetReferenceParameter("_manipulationPropertySet", _manipulationPropertySet);
    _headerVisual.StartAnimation("Offset.Y", _headerAnimation);
};
Betch.End();

我們把構(gòu)造和播放_(tái)headerAnimation的代碼放到了ScopedBatch的Complated事件里,這時(shí)再運(yùn)行一下,完美。

UWP中如何使用Composition API實(shí)現(xiàn)吸頂

其實(shí)還是有點(diǎn)小問(wèn)題,比如Header沒(méi)有設(shè)置Clip,上下移動(dòng)的時(shí)候有時(shí)會(huì)超出預(yù)期的范圍之類的,有時(shí)間我們會(huì)繼續(xù)討論,這篇已經(jīng)足夠長(zhǎng),再長(zhǎng)會(huì)嚇跑人的。
Demo已經(jīng)放到Github,里面用到了一個(gè)寫(xiě)的很糙的滑動(dòng)返回控件,等忙過(guò)這段時(shí)間整理下代碼就開(kāi)源,希望能有大牛指點(diǎn)一二。

Github:

總結(jié)一下,實(shí)現(xiàn)吸頂最核心的代碼就是獲取到ScrollViewer,不一定要是ListView的,明白了這一點(diǎn),所有含有ScrollViewer的控件都可以放到這個(gè)這個(gè)頁(yè)面使用。

滑動(dòng)返回:

UWP中如何使用Composition API實(shí)現(xiàn)吸頂

以上是UWP中如何使用Composition API實(shí)現(xiàn)吸頂?shù)乃袃?nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!

當(dāng)前名稱:UWP中如何使用CompositionAPI實(shí)現(xiàn)吸頂
文章地址:http://muchs.cn/article18/iiddgp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供軟件開(kāi)發(fā)、服務(wù)器托管、網(wǎng)頁(yè)設(shè)計(jì)公司、網(wǎng)站設(shè)計(jì)公司、外貿(mào)網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

微信小程序開(kāi)發(fā)