前幾天需要在UWP中實現吸頂,就在網上找了一些文章: 吸頂大法 -- UWP中的工具欄吸頂的實現方式之一 在UWP中頁面滑動導航欄置頂 發現前人的實現方式大多是控制ListViewBase的Header變換高度,或者建立一個ScrollViewer在裡面放置ListViewBase。經過測試,這兩種 ...
前幾天需要在UWP中實現吸頂,就在網上找了一些文章:
發現前人的實現方式大多是控制ListViewBase的Header變換高度,或者建立一個ScrollViewer在裡面放置ListViewBase。經過測試,這兩種方法或多或少的都有問題。所以我想試試用Composition API實現吸頂的效果。
首先先瞭解一下Composition API是什麼。
Windows.UI.Composition 是可以從任何通用 Windows 平臺 (UWP) 應用程式調用的聲明性保留模式 API ,從而可以直接在應用程式中創建合成對象、 動畫和效果。 該 API 是對諸如 XAML 等現有框架的一個強大補充,從而為 UWP 應用程式開發人員提供了一個熟悉的 C# 圖面以供添加到其應用程式。 這些 API 還可以用於創建 DX 樣式框架較少的應用程式。
XAML 開發人員可以使用 WinRT“下拉”到採用 C# 的合成層,以便在該合成層上執行自定義工作,而無需一直下拉到圖形層並針對任何自定義 UI 工作使用 DirectX 和 C++。 此技術可用於使用合成 API 對現有元素進行動畫處理,也可用於通過在 XAML 元素樹內創建 Windows.UI.Composition 內容的“視覺島”來增加 UI。
只看這幾句微軟給的介紹也是雲里來霧裡去的,還是看代碼吧。
CompositionAPI中有一種動畫叫表達式動畫。大致效果就是讓一個Visual或者PropertySet的屬性隨著自身另一個屬性,或者另一個Visual或者PropertySet的屬性的變化而變化。
對於不含Pivot的簡單情況,就有這樣一個基本的思路了:
- 獲取到ListViewBase的ScrollViewer;
- 獲取到ScrollViewer的ManipulationPropertySet和ListViewHeader的Visual;
- 讓ManipulationPropertySet和Visual發生關係。
我們先來建立一個簡單的頁面。
<Page x:Class="TestSwipeBack.ScrollTest" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:TestSwipeBack" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Loaded="Page_Loaded"> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <ListView x:Name="_listview" ItemsSource="{x:Bind ItemSource,Mode=OneWay}"> <ListView.Header> <Grid x:Name="_header"> <Grid.RowDefinitions> <RowDefinition Height="100" /> <RowDefinition Height="50" /> </Grid.RowDefinitions> <Grid Background="LightBlue"> <Button>123</Button> <TextBlock FontSize="25" HorizontalAlignment="Center" VerticalAlignment="Center">我會被隱藏</TextBlock> </Grid> <Grid Background="Pink" Grid.Row="1"> <Button>123</Button> <TextBlock FontSize="25" HorizontalAlignment="Center" VerticalAlignment="Center">我會吸頂</TextBlock> </Grid> </Grid> </ListView.Header> <ListView.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding }" /> </DataTemplate> </ListView.ItemTemplate> </ListView> </Grid> </Page>
原本ListViewBase里Header是在ItemsPanelRoot下方的,使用Canvans.SetZIndex把ItemsPanelRoot設置到下方。
Canvas.SetZIndex(_listview.ItemsPanelRoot, -1);
然後在後臺獲取ListView內的ScrollViewer。
var _scrollviewer = FindFirstChild<ScrollViewer>(_listview); static T FindFirstChild<T>(FrameworkElement element) where T : FrameworkElement { int childrenCount = VisualTreeHelper.GetChildrenCount(element); var children = new FrameworkElement[childrenCount];
for (int i = 0; i < childrenCount; i++) { var child = VisualTreeHelper.GetChild(element, i) as FrameworkElement; children[i] = child; if (child is T) return (T)child;
} for (int i = 0; i < childrenCount; i++) if (children[i] != null) { var subChild = FindFirstChild<T>(children[i]); if (subChild != null) return subChild; } return null;
}
獲取ListViewHeader的Visual和ScrollViewer的ManipulationPropertySet。
var _headerVisual = ElementCompositionPreview.GetElementVisual(_header); var _manipulationPropertySet = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(_scrollviewer);
創建表達式動畫,然後運行。
var _compositor = Window.Current.Compositor; var _headerAnimation = _compositor.CreateExpressionAnimation("_manipulationPropertySet.Translation.Y > -100f ? 0: -100f -_manipulationPropertySet.Translation.Y"); //_manipulationPropertySet.Translation.Y是ScrollViewer滾動的數值,手指向上移動的時候,也就是可視部分向下移動的時候,Translation.Y是負數。 _headerAnimation.SetReferenceParameter("_manipulationPropertySet", _manipulationPropertySet); _headerVisual.StartAnimation("Offset.Y", _headerAnimation);
現在滑動Demo看看,是不是在滾動100像素之後,Header就停住了?
註:在一個Visual或者propertySet被附加了動畫(即StartAnimation或者StartAnimationGroup)之後,取出(propertySet.TryGetScalar)相應的屬性就只能取到0,但是賦值或者插入數值是會生效的。