uwpwin-universal-appwindows-10-universaluwp-xamlwindows-composition-api

UWP collapsible page header


I'm trying to implement the collapsible page header. The main idea is I have to change the height of page header (min height 60 and max is 160) depending on the changes in the scrollviewer.

Here is the result I want to achieve.

Initial state of the page header

Main layout

Page header when scrolled down

Main layout when scrolled down

My XAML code:

    <controls:CollapsablePageHeader>
        <controls:CollapsablePageHeader>
            <Grid x:Name="CollapsablePageHeaderGrid" MinHeight="150" Background="{StaticResource ApplicationAccentBackgroundColorBrush}">
                <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="25 905" HorizontalAlignment="Center" FontSize="50" Foreground="{StaticResource ApplicationAlternativeAccentColorBrush}"/>
                        <controls:TengeSignControl FontSize="50" Foreground="{StaticResource ApplicationAlternativeAccentColorBrush}"/>
                    </StackPanel>
                    <StackPanel Orientation="Horizontal">
                        <Image Source="/Assets/CreditCard.png" Height="20" Width="20" Margin="0,5,5,0"/>
                        <TextBlock Text="Bank of Arkham City" FontSize="{StaticResource BigTextSize}" Foreground="{StaticResource ApplicationAlternativeAccentColorBrush}"/>
                    </StackPanel>
                </StackPanel>
            </Grid>
        </controls:CollapsablePageHeader>
    </controls:CollapsablePageHeader>
    <ScrollViewer x:Name="MainScrollViewer" Grid.Row="1" IsTabStop="False" ScrollViewer.HorizontalScrollMode="Disabled" ScrollViewer.VerticalScrollBarVisibility="Hidden">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <!--Layout for user favorite payment templates-->
            <toolkitControls:DropShadowPanel Style="{StaticResource DefaultCardDropShadowEffect}">
                <Grid Style="{StaticResource CardConontrolElement}">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <TextBlock x:Uid="Favorites" Style="{StaticResource SectionHeaderTextBlock}" HorizontalAlignment="Left" />
                    <GridView x:Name="UserTemplatesSelector" Grid.Row="1" ItemsSource="{Binding PaymentTemplates}" ItemTemplate="{StaticResource UserTemplatesDataTemplate}" Margin="-10,0,-10,0" Padding="10,0,0,0" Style="{StaticResource DefaultHorizontalGridView}" ItemContainerStyle="{StaticResource DefaultHorizontalGridViewItemContainer}">
                        <GridView.ItemsPanel>
                            <ItemsPanelTemplate>
                                <StackPanel Orientation="Horizontal"/>
                            </ItemsPanelTemplate>
                        </GridView.ItemsPanel>
                    </GridView>
                </Grid>
            </toolkitControls:DropShadowPanel>
            <!-- Layout for last operations -->
            <toolkitControls:DropShadowPanel Grid.Row="1" Margin="0,20,0,0" Style="{StaticResource DefaultCardDropShadowEffect}">
                <Grid Style="{StaticResource CardConontrolElement}">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <TextBlock x:Uid="LastOperations" Style="{StaticResource SectionHeaderTextBlock}"/>
                    <ListView x:Name="LastOperationsListView" Grid.Row="1" ItemsSource="{Binding LastTransactions}" ItemTemplate="{StaticResource LastOperationsDataTemplate}" Margin="0,10,0,0" ItemContainerStyle="{StaticResource DefaultListViewItemContainer}"/>
                    <Button x:Name="ShowMoreOperationsButton" Content="Показать всё" Grid.Row="2" Style="{StaticResource TiltableAccentButton}"/>
                </Grid>
            </toolkitControls:DropShadowPanel>
            <!-- Layour for bound cards -->
            <toolkitControls:DropShadowPanel Grid.Row="2" Margin="0,20" Style="{StaticResource DefaultCardDropShadowEffect}">
                <Grid Style="{StaticResource CardConontrolElement}">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <TextBlock x:Uid="LastOperations" Style="{StaticResource SectionHeaderTextBlock}" Typography.Capitals="AllSmallCaps"/>
                    <ListView x:Name="BoundCardListView" Grid.Row="1" ItemsSource="{Binding BoundCards}" ItemTemplate="{StaticResource BoundCardsDataTemplate}" Margin="0,10,0,0" ItemContainerStyle="{StaticResource DefaultListViewItemContainer}"/>
                    <Button x:Name="ShowMoreCardsButton" Content="Показать всё" Grid.Row="2" Style="{StaticResource TiltableAccentButton}"/>
                </Grid>
            </toolkitControls:DropShadowPanel>
        </Grid>
    </ScrollViewer>`

I have tried to implement this using the Compositor.CreateExpressionAnimation but it did not work well. Could anyone please tell me how to do this?

UPDATE:

Throws an exception when try to animate the Scale.X or Scale.Y value, but I cannot figure out why. Other Visual items work excellent with Scale animation.

        ExpressionNode headerTranslationAnimation = EF.Conditional(progressNode < 1, 0, -scrollingProperties.Translation.Y - clampSizeNode);
        headerVisual.StartAnimation("Offset.Y", headerTranslationAnimation);

        ExpressionNode headerScaleAnimation = EF.Lerp(1, 1.25f, EF.Clamp(scrollingProperties.Translation.Y / 50, 0, 1));
        headerVisual.StartAnimation("Scale.X", headerScaleAnimation);
        headerVisual.StartAnimation("Scale.Y", headerScaleAnimation);

Exception at headerVisual.StartAnimation:

System.ArgumentException: 'The parameter is incorrect.The animation failed to connect.'

Updated xaml:

 <Grid x:Name="LayoutRoot" Style="{StaticResource DefaultLayoutRootStyle}">
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>

    <ScrollViewer x:Name="MainScrollViewer" IsTabStop="False" ScrollViewer.HorizontalScrollMode="Disabled" ScrollViewer.VerticalScrollBarVisibility="Hidden">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>

            <Grid x:Name="CollapsiblePageHeader" Height="150" VerticalAlignment="Top">
                <ScrollViewer>
                    <Grid>
                        <Rectangle Height="300" HorizontalAlignment="Left" VerticalAlignment="Top">
                            <Rectangle.Fill>
                                <ImageBrush ImageSource="ms-appx:///Assets/Samples/SampleParallaxImage.jpg"/>
                            </Rectangle.Fill>
                        </Rectangle>
                        <StackPanel>
                            <StackPanel x:Name="TitleContainer" VerticalAlignment="Top" HorizontalAlignment="Center" Orientation="Horizontal">
                                <TextBlock x:Name="TitleText" Text="25 905" FontSize="50" Foreground="{StaticResource ApplicationAlternativeAccentColorBrush}"/>
                                <controls:TengeSignControl x:Name="TitleImage" FontSize="50" Foreground="{StaticResource ApplicationAlternativeAccentColorBrush}"/>
                            </StackPanel>
                            <StackPanel x:Name="SubtitleContainer" HorizontalAlignment="Center" VerticalAlignment="Top" Grid.Row="1" Orientation="Horizontal">
                                <Image x:Name="SubtitleImage" Source="/Assets/CreditCard.png" Height="20" Width="20" Margin="0,5,5,0"/>
                                <TextBlock x:Name="SubtitleText" Text="Bank of Arkham City" FontSize="{StaticResource BigTextSize}" Foreground="{StaticResource ApplicationAlternativeAccentColorBrush}"/>
                            </StackPanel>
                        </StackPanel>
                    </Grid>
                </ScrollViewer>
            </Grid>

            <Grid Grid.Row="1">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>

                <!--Layout for user favorite payment templates-->
                <toolkitControls:DropShadowPanel Grid.Row="1" Style="{StaticResource DefaultCardDropShadowEffect}">
                    <Grid Style="{StaticResource CardConontrolElement}">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>
                        <TextBlock x:Uid="Favorites" Style="{StaticResource SectionHeaderTextBlock}" HorizontalAlignment="Left" />
                        <GridView x:Name="UserTemplatesSelector" Grid.Row="1" ItemsSource="{Binding PaymentTemplates}" ScrollViewer.HorizontalScrollMode="Enabled" ScrollViewer.VerticalScrollMode="Disabled" ItemTemplate="{StaticResource UserTemplatesDataTemplate}" Margin="-10,0,-10,0" Padding="10,0,0,0" Style="{StaticResource DefaultGridView}" ItemContainerStyle="{StaticResource DefaultGridViewItemContainer}"/>
                    </Grid>
                </toolkitControls:DropShadowPanel>

                <!-- Layout for last operations -->
                <toolkitControls:DropShadowPanel Grid.Row="2" Margin="0,20,0,0" Style="{StaticResource DefaultCardDropShadowEffect}">
                    <Grid Style="{StaticResource CardConontrolElement}">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>
                        <TextBlock x:Uid="LastOperations" Style="{StaticResource SectionHeaderTextBlock}"/>
                        <ListView x:Name="LastOperationsListView" Grid.Row="1"  ScrollViewer.VerticalScrollMode="Disabled"  ItemsSource="{Binding LastTransactions}" ItemTemplate="{StaticResource LastOperationsDataTemplate}" Margin="0,10,0,0" ItemContainerStyle="{StaticResource DefaultListViewItemContainer}"/>
                        <Button x:Name="ShowMoreOperationsButton" Content="Показать всё" Grid.Row="2" Style="{StaticResource TiltableAccentButton}"/>
                    </Grid>
                </toolkitControls:DropShadowPanel>

                <!-- Layour for bound cards -->
                <toolkitControls:DropShadowPanel Grid.Row="3" Margin="0,20" Style="{StaticResource DefaultCardDropShadowEffect}">
                    <Grid Style="{StaticResource CardConontrolElement}">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>
                        <TextBlock Text="Привязанные карты" Style="{StaticResource SectionHeaderTextBlock}" Typography.Capitals="AllSmallCaps"/>
                        <ListView x:Name="BoundCardListView" Grid.Row="1" ScrollViewer.VerticalScrollMode="Disabled" ItemsSource="{Binding BoundCards}" ItemTemplate="{StaticResource BoundCardsDataTemplate}" Margin="0,10,0,0" ItemContainerStyle="{StaticResource DefaultListViewItemContainer}"/>
                        <Button x:Name="ShowMoreCardsButton" Content="Показать всё" Grid.Row="2" Style="{StaticResource TiltableAccentButton}"/>
                    </Grid>
                </toolkitControls:DropShadowPanel>
            </Grid>
        </Grid>
    </ScrollViewer>

    <CommandBar x:Name="PaymentOperations" Grid.Row="1">
        <AppBarButton x:Name="VendorPayment" Label="Оплата поставщиков" Icon="Shop" Click="MerchantPayment_Click"/>
        <AppBarButton x:Name="p2pPayment" Label="P2P оплата" Icon="Remote" Click="PersonToPersonPayment_Click"/>
        <AppBarButton x:Name="w2wPayment" Label="Перевод с кошелька на кошелек" Icon="Manage" Click="WalletToWalletPayment_Click"/>
    </CommandBar>
</Grid>

Full xaml.cs:

    private void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        // Get the PropertySet that contains the scroll values from MyScrollViewer
        _scrollerPropertySet = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(MainScrollViewer);
        _compositor = _scrollerPropertySet.Compositor;

        // Create a PropertySet that has values to be referenced in the ExpressionAnimations below
        _props = _compositor.CreatePropertySet();
        _props.InsertScalar("progress", 0);
        _props.InsertScalar("clampSize", 150);
        _props.InsertScalar("scaleFactor", 0.7f);

        // Get references to our property sets for use with ExpressionNodes
        var scrollingProperties = _scrollerPropertySet.GetSpecializedReference<ManipulationPropertySetReferenceNode>();
        var props = _props.GetReference();
        var progressNode = props.GetScalarProperty("progress");
        var clampSizeNode = props.GetScalarProperty("clampSize");
        var scaleFactorNode = props.GetScalarProperty("scaleFactor");

        // Create and start an ExpressionAnimation to track scroll progress over the desired distance
        ExpressionNode progressAnimation = EF.Clamp(-scrollingProperties.Translation.Y / clampSizeNode, 0, 1);
        _props.StartAnimation("progress", progressAnimation);

        // Get the backing visual for the header so that its properties can be animated
        Visual headerVisual = ElementCompositionPreview.GetElementVisual(CollapsiblePageHeader);

        // Create and start an ExpressionAnimation to clamp the header's offset to keep it onscreen
        ExpressionNode headerTranslationAnimation = EF.Conditional(progressNode < 1, 0, -scrollingProperties.Translation.Y - clampSizeNode);
        // Works fine
        headerVisual.StartAnimation("Offset.Y", headerTranslationAnimation);

        ExpressionNode headerScaleAnimation = EF.Lerp(1, 1.25f, EF.Clamp(scrollingProperties.Translation.Y / 50, 0, 1));
        // Exception here
        headerVisual.StartAnimation("Scale.X", headerScaleAnimation);
        headerVisual.StartAnimation("Scale.Y", headerScaleAnimation);


        //Set the header's CenterPoint to ensure the overpan scale looks as desired
        headerVisual.CenterPoint = new Vector3((float)(CollapsiblePageHeader.ActualWidth / 2), (float)CollapsiblePageHeader.ActualHeight, 0);

        // Create and start an ExpressionAnimation to scale the profile image with scroll position
        ExpressionNode scaleAnimation = EF.Lerp(1, scaleFactorNode, progressNode);

        // Get backing visuals for the text blocks so that their properties can be animated
        Visual titleVisual = ElementCompositionPreview.GetElementVisual(TitleText);
        Visual titleImageVisual = ElementCompositionPreview.GetElementVisual(TitleImage);
        Visual subtitleVisual = ElementCompositionPreview.GetElementVisual(SubtitleText);
        Visual subtitleImageVisual = ElementCompositionPreview.GetElementVisual(SubtitleImage);

        // Create an ExpressionAnimation that moves between 1 and 0 with scroll progress, to be used for text block opacity
        ExpressionNode textOpacityAnimation = EF.Clamp(1 - (progressNode * 2), 0, 1);

        // Start opacity and scale animations on the text block visuals
        titleVisual.StartAnimation("Scale.X", scaleAnimation);
        titleVisual.StartAnimation("Scale.Y", scaleAnimation);

        titleImageVisual.StartAnimation("Scale.X", scaleAnimation);
        titleImageVisual.StartAnimation("Scale.Y", scaleAnimation);

        subtitleVisual.StartAnimation("Opacity", textOpacityAnimation);
        subtitleVisual.StartAnimation("Scale.X", scaleAnimation);
        subtitleVisual.StartAnimation("Scale.Y", scaleAnimation);

        subtitleImageVisual.StartAnimation("Opacity", textOpacityAnimation);
        subtitleImageVisual.StartAnimation("Scale.X", scaleAnimation);
        subtitleImageVisual.StartAnimation("Scale.Y", scaleAnimation);

        // Get the backing visuals for the text and button containers so that their properites can be animated
        Visual subtitleContainerVisual = ElementCompositionPreview.GetElementVisual(SubtitleContainer);

        // When the header stops scrolling it is 150 pixels offscreen.  We want the text header to end up with 50 pixels of its content
        // offscreen which means it needs to go from offset 0 to 100 as we traverse through the scrollable region
        ExpressionNode contentOffsetAnimation = progressNode * 100;

        subtitleContainerVisual.StartAnimation("Offset.Y", contentOffsetAnimation);
    }

Solution

  • This can be easily done without the community toolkit if your minimum target version is Anniversary Update. Considering you do, let's take you through the solution.

    1. You need to refer to the GitHub repository of the WindowsDevLabs. You can find it here.
    2. Now, once you've downloaded the repo, add the Expression Builder Project to your solution and that's it. You're all set.
    3. Now, for the last part just in the downloaded Repo in the samples section look for "Shrinking Header" page and copy paste the code from codeBehind and make necessary changes in your UI and code behind refering to the Desinger and Code Behind in the "Shrinking Header" Page of the downloaded repo.

    Please Note: you can see an active App on the store for the same repo on the store at the Link: Windows Dev Labs UI App