UWP實現吸頂的Pivot

話很少說,先上效果

這裏使用了一個ScrollProgressProvider.cs,咱們這篇文章先解析一下總體的動畫思路,之後再詳細解釋這個Provider的實現方式。html

結構

整個頁面大體結構是git

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Grid x:Name="Target">
        <TextBlock />
        <Header />
    </Grid>
    <Pivot.ItemTemplate Grid.RowSpan="2">
        <Pivot.ItemTemplate>
            <DataTemplate>
                <ScrollViewer x:Name="sv">
                    <StackPanel>
                        <Border Margin="0,250,0,0" />
                    </StackPanel>
                </ScrollViewer>
            </DataTemplate>
        </DataTemplate>
    </Pivot.ItemTemplate>
</Grid>

這個Header是修改的ListBox,固然也能夠用ListView代替。 隱藏Pivot默認Header的方式是在Pivot的樣式中找到以下行。github

<PivotPanel x:Name="Panel" VerticalAlignment="Stretch">
    <Grid x:Name="PivotLayoutElement">
        <Grid.RowDefinitions>
            <RowDefinition Height="0" /><!--修改這行爲0-->
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
    ...

動畫過程大體就是在Pivot頁面切換時,查找到當頁的ScrollViewer,綁定動畫。windows

查找

你們在爬視圖樹時,應該常常遇到元素還未加載的狀況,這裏爲了解決這種情況,封裝了一個WaitForLoaded方法。api

private async Task<T> WaitForLoaded<T>(FrameworkElement element, Func<T> func, Predicate<T> pre, CancellationToken cancellationToken)
{
    TaskCompletionSource<T> tcs = null;
    try
    {
        tcs = new TaskCompletionSource<T>();
        cancellationToken.ThrowIfCancellationRequested();
        var result = func.Invoke();
        if (pre(result)) return result;


        element.Loaded += Element_Loaded;

        return await tcs.Task;

    }
    catch
    {
        element.Loaded -= Element_Loaded;
        var result = func.Invoke();
        if (pre(result)) return result;
    }

    return default;


    void Element_Loaded(object sender, RoutedEventArgs e)
    {
        if (tcs == null) return;
        try
        {
            cancellationToken.ThrowIfCancellationRequested();
            element.Loaded -= Element_Loaded;
            var _result = func.Invoke();
            if (pre(_result)) tcs.SetResult(_result);
            else tcs.SetCanceled();
        }
        catch
        {
            System.Diagnostics.Debug.WriteLine("canceled");
        }
    }

}

使用起來是這樣的緩存

CancellationTokenSource cts;
private async void EventChanged(object sender, EventArgs e)
{
    if (cts != null) cts.Cancel();
    cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
    var child = await WaitForLoaded(element, () => find_element_method(), c => judge_find_success_method(), cts.Token);
}

咱們在Pivot的SelectionChanged事件裏,修改ScrollProgressProvider託管的ScrollViewer,provider就會自動將ScrollViewer設置到正確的位置。async

接下來在Page的Loaded事件中綁定動畫,這裏有兩種選擇。provider提供了ProgressChanged事件和GetProgressPropertySet方法。能夠在ProgressChanged事件中直接設置元素的值來實現動畫,不過因爲ScrollViewer的限制,ProgressChanged事件觸發頻率不是很高,因此更推薦使用GetProgressPropertySet獲取到CompositionPropertySet,經過Composition Api實現動畫。ide

var providerProp = provider.GetProgressPropertySet();
var gv = ElementCompositionPreview.GetElementVisual(Target); // 容器Visual
var tv = ElementCompositionPreview.GetElementVisual(HeaderText); //文本Visual

ScrollProgressProvider生成的PropertySet內有progress和threshold兩個字段能夠用做動畫。 Composition Api提供了Lerp(start, end, progress)方法,用在此處恰好合適。 咱們須要定義容器平移,文本平移和文本縮放三個動畫。動畫

容器平移向上移動閾值的高度

var gvOffsetExp = Window.Current.Compositor.CreateExpressionAnimation("Vector3(0f, -provider.threshold * provider.progress, 0f)");
gvOffsetExp.SetReferenceParameter("provider", providerProp);
gv.StartAnimation("Offset", gvOffsetExp);

文本平移動畫從容器中心平移到左下角

var startOffset = "Vector3((host.Size.X - this.Target.Size.X) / 2, (host.Size.Y - 50 - this.Target.Size.Y) / 2, 1f)";
var endOffset = $"Vector3(0f, provider.threshold, 1f)";
var offsetExp = Window.Current.Compositor.CreateExpressionAnimation($"lerp({startOffset}, {endOffset}, provider.progress)");
offsetExp.SetReferenceParameter("host", gv);
offsetExp.SetReferenceParameter("provider", providerProp);
tv.StartAnimation("Offset", offsetExp);

文本縮放

var scale = "(50f / this.Target.Size.Y)";
var startScale = "Vector3(1f, 1f, 1f)";
var endScale = $"Vector3({scale}, {scale}, 1f)";
var scaleExp = Window.Current.Compositor.CreateExpressionAnimation($"lerp({startScale}, {endScale}, provider.progress)");
scaleExp.SetReferenceParameter("host", gv);
scaleExp.SetReferenceParameter("provider", providerProp);
tv.StartAnimation("Scale", scaleExp);

觸摸

觸摸比起鼠標點擊要更復雜一些。 Pivot應該是UWP內置控件裏比較玄學的一個了。 對於鼠標操做,Pivot會先觸發SelectionChanged事件,再觸發PivotItemLoaded事件,而且播放動畫。 而對於觸摸事件,整個順序是相反的。手指開始滑動界面時,能夠被看到的Item會開始加載,而且觸發PivotItemLoaded事件,鬆手以後纔開始計算是否應該導航到其餘頁,而且決定是否觸發SelectionChanged事件。這樣就會有一個問題,咱們在SelectionChanged中修改ScrollViewer偏移以前,咱們已經能看到他了,這時的高度是不正確的。咱們須要抽象出一個能夠在鼠標和觸摸觸發事件時將下一個Item的ScrollViewer設置爲正確偏移的方法。 個人想法很簡單,將全部已加載的頁內的ScrollViewer緩存下來,隨着Progress的改變而改變,作法也很簡單。this

private HashSet<ScrollViewer> scrolls = new HashSet<ScrollViewer>();
private async void Pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    ...
    scrolls.Remove(provider.ScrollViewer);
}

private void Pivot_PivotItemLoaded(Pivot sender, PivotItemEventArgs args)
{
    var sv = (args.Item.ContentTemplateRoot as FrameworkElement).FindName("sv") as ScrollViewer;
    if (sv != provider.ScrollViewer)
    {
        sv.ChangeView(null, provider.Progress * provider.Threshold, null, true);
        scrolls.Add(sv);
    }
}


private void Pivot_PivotItemUnloading(Pivot sender, PivotItemEventArgs args)
{
    var sv = (args.Item.ContentTemplateRoot as FrameworkElement).FindName("sv") as ScrollViewer;
    if (sv != null)
    {
        scrolls.Remove(sv);
    }
}

private void Provider_ProgressChanged(object sender, double args)
{
    foreach (var sv in scrolls)
    {
        sv.ChangeView(null, provider.Progress * provider.Threshold, null, true);
    }
}

須要注意的是,咱們要在加載完成事件中獲取ScrollViewer,而在卸載開始事件中移除ScrollViewer。

GitHub: https://github.com/cnbluefire/ShyHeaderPivot
ExpressionAnimation: https://docs.microsoft.com/en-us/uwp/api/Windows.UI.Composition.ExpressionAnimation
CompositionAnimation: https://docs.microsoft.com/zh-cn/windows/uwp/composition/composition-animation 個人博客: 超威藍火

原文出處:https://www.cnblogs.com/blue-fire/p/11376450.html

相關文章
相關標籤/搜索