Can you please tell me how to animate the CommunityToolkit.Maui's Expander control for opening and closing it slowly?

dg2k 1,386 Reputation points
2023-03-24T18:56:05.75+00:00

I would like to show some hidden tips with a simple implementation of CommunityToolkit.Maui.Views.Expander control -- such as using a Label control as its content. It would be visually nice if the Expander opens and closes slowly, say with some Easing function.

I understand basic animations such as X,Y translations, Horizontal or Vertical rotations, but closing and opening the Expander looks a bit tricky as I am not sure what handle to manipulate.

Your help is appreciated.

.NET MAUI
.NET MAUI
A Microsoft open-source framework for building native device applications spanning mobile, tablet, and desktop.
2,861 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,235 questions
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. dg2k 1,386 Reputation points
    2023-03-25T09:56:11.4633333+00:00

    Okay, I figured it out and let me answer my own question.

    • Expander.Content in my case is a question mark based on a Style resource as FontImage.
    • I use ScaleYTo animation of the content (see myAnim:ScaleYToAnimation Class reference) , and for this I chose a Border control to contain the Label to display the Tip message.
    • Since animation requires a trigger, I decided to use the SizeChanged event of the Border. For this to work, the user input trigger of Expander's ExpandedChanged event is used to do the trick for SizeChanged to trigger programatically.
    • A RelayCommand is invoked with the ExpandedChanged which does most of the tricks as shown in the RelayCommand method (see below).
    • TheClassId attached to the Label serves as an Id for what Tip message to display, because there are many Tips to be shown with the same RelayCommand and each is made distinct with its ClassId.
    • ScreenToGif recording of a working demo is shown at the end.

    This is what I did, and if you have a simpler and better solution, please share.

    xmlns:ctk="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
    xmlns:myAnim="clr-namespace:ExpanderTest.Animations"
    ...
    
    <ctk:Expander
        Grid.Row="9"
        Grid.Column="0"
        Grid.ColumnSpan="4"
        Margin="12"
        Padding="0"
        Command="{Binding TellMeMoreTipClickedCommand}"
        CommandParameter="{Binding Source={RelativeSource Self}}">
        <ctk:Expander.Header>
     
            <!--
                CircledQuestion is a styled FontImage with
                FontAwesome.QuestionCircle Glyph
            -->
     
            <ImageButton
                Margin="0"
                Aspect="AspectFit"
                BackgroundColor="{StaticResource CyanBlueWhite}"
                HorizontalOptions="Start"
                Source="{StaticResource CircledQuestion}"
                VerticalOptions="Center" />
     
        </ctk:Expander.Header>
     
        <ctk:Expander.Content>
     
            <Border
                Margin="12,0"
                Padding="18"
                BackgroundColor="PowderBlue"
                HorizontalOptions="Fill"
                StrokeThickness="1">
                <Border.StrokeShape>
                    <RoundRectangle CornerRadius="9" />
                </Border.StrokeShape>
                <Border.Behaviors>
                    <ctk:AnimationBehavior EventName="SizeChanged">
                        <ctk:AnimationBehavior.AnimationType>
                            <myAnim:ScaleYToAnimation Easing="{x:Static Easing.Linear}" Length="500" />
                        </ctk:AnimationBehavior.AnimationType>
                    </ctk:AnimationBehavior>
                </Border.Behaviors>
                <Label ClassId="_TipVeriCode" Text="This Id can serve as an identifier of what tip message to show." />
            </Border>
        </ctk:Expander.Content>
    </ctk:Expander>
    
    using CommunityToolkit.Maui.Animations;
    
    namespace ExpanderTest.Animations;
    
    class ScaleYToAnimation : BaseAnimation
    {
        public override async Task Animate(VisualElement view)
        {
            await view.ScaleYTo(0.0, Length, Easing);
            await view.ScaleYTo(1.2, Length, Easing);
            await view.ScaleYTo(1, Length, Easing);
        }
    }
    
    double originalBorderHeight = -1;
    [RelayCommand]
    private async void TellMeMoreTipClicked(Expander expander)
    {
    	if (expander is null) return;
    
    	// Nothing to do if Expander is collapsing
    	if (!expander.IsExpanded) return;
    
    	// Use VisualTreeHelper to get to child controls
    	// There is only one Border and one Label.
    
    	var label = expander.FirstChild<Label>();
    	string id = label.ClassId;
    	label.Text = "<message-from-database-with-id";
    
    	var border = expander.FirstChild<Border>();
    	border.ScaleY = 0;  // makes expansion to start from a collapsed Height.
    
    	if (border.Height > 0)
    	{
    		// capture initially set value
    		if (originalBorderHeight < 0) originalBorderHeight = border.Height;
    
    		// keep changing border.Height every time to trigger Border.SizeChanged() event
    		if (border.Height != originalBorderHeight)
    		{
    			border.HeightRequest = originalBorderHeight;
    		}
    		else
    		{
    			border.HeightRequest = originalBorderHeight + 1;
    		}
    
    	}
    	else
    	{
    		// This is startup and SizeChanged will trigger anyway.
    	}
    }
    

    The ScreenToGif recording is shown below.

    ExpanderAnimDemo.gif