-->

Slow storyboard animation inside LongListSelector

2019-07-23 10:18发布

问题:

I have a LongListSelector which is populated with some items. Each item has a submenu which can be visible or collapsed using a sliding animation. The problem is that the animation is extremely slow depending on which item you tap in the list. At the start and the end it's slow, in the middle it's smooth. I suspect that each animation frame invalidates the longlistselector which means the entire visual tree must update it's layout.

I use a datatrigger to start the animation. Because of that I can see that the triggers on lots of other items also get fired which indicates to me they are being redrawn. It also shows that when you tap in the middle of list a lot less other items get triggered as well. weird...

I hosted the testproject on github: https://github.com/jessetabak/wpanimationproblem

The LongListSelector:

<phone:LongListSelector x:Name="LongList" Margin="0" Padding="0" ItemsSource="{Binding Items}" 
        HorizontalAlignment="Stretch" Background="Transparent">
    <phone:LongListSelector.ItemTemplate>
        <DataTemplate>
            <wegGooiApp2:ItemView />
        </DataTemplate>
    </phone:LongListSelector.ItemTemplate>

</phone:LongListSelector>

The ItemView:

<UserControl.Resources>

        <wegGooiApp2:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />

        <Storyboard x:Key="ShowMenu">
            <DoubleAnimation Storyboard.TargetProperty="(Grid.Height)" 
                             Storyboard.TargetName="Submenu"
                             From="0" To="70" Duration="0:0:0.25" />

            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Submenu"
                                           Storyboard.TargetProperty="(Grid.Visibility)">
                <DiscreteObjectKeyFrame KeyTime="0:0:0">
                    <DiscreteObjectKeyFrame.Value>
                        <Visibility>Visible</Visibility>
                    </DiscreteObjectKeyFrame.Value>
                </DiscreteObjectKeyFrame>
            </ObjectAnimationUsingKeyFrames>

        </Storyboard>

        <Storyboard x:Key="HideMenu">       

            <DoubleAnimation Storyboard.TargetProperty="(Grid.Height)"
                            Storyboard.TargetName="Submenu"
                            From="70" To="0" Duration="0:0:0.25" />

            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Submenu"
                                           Storyboard.TargetProperty="(Grid.Visibility)">
                <DiscreteObjectKeyFrame KeyTime="0:0:0.25">
                    <DiscreteObjectKeyFrame.Value>
                        <Visibility>Collapsed</Visibility>
                    </DiscreteObjectKeyFrame.Value>
                </DiscreteObjectKeyFrame>
            </ObjectAnimationUsingKeyFrames>

        </Storyboard>        

    </UserControl.Resources>

    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <!-- TEST ITEM -->
        <Border Height="70" BorderBrush="Red" Background="Transparent" BorderThickness="1" HorizontalAlignment="Stretch">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>

                <TextBlock Text="Test Item" HorizontalAlignment="Stretch" FontSize="42" />
                <Button Content="v" Grid.Column="1" Tap="Button_Tap" Tag="{Binding}">

                </Button>
            </Grid>
        </Border>

        <!-- SUBMENU -->
        <Border x:Name="Submenu" Grid.Row="1" BorderBrush="Green" BorderThickness="1" 
                Visibility="{Binding SubMenuIsVisible, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneTime}"
                Background="Green" Height="0" Margin="0 0 0 0">

            <i:Interaction.Triggers>
                <ec:DataTrigger Binding="{Binding SubMenuIsVisible}" Value="True">
                    <ec:CallMethodAction MethodName="MenuEnabled"
                                            TargetObject="{Binding ElementName=ThisUserControl, Mode=OneWay}" />

                </ec:DataTrigger>

                <ec:DataTrigger Binding="{Binding SubMenuIsVisible}" Value="False">
                    <ec:CallMethodAction MethodName="MenuDisabled" 
                                         TargetObject="{Binding ElementName=ThisUserControl, Mode=OneWay}" />
                </ec:DataTrigger>
            </i:Interaction.Triggers>

            <TextBlock Text="SubMenu" FontSize="42" />

        </Border>


    </Grid>
</UserControl>

The ItemView codebehind:

public partial class ItemView : UserControl
    {            
        private Storyboard _showStoryboard;
        private Storyboard _hideStoryboard;        

        public ItemView()
        {
            InitializeComponent();
            _showStoryboard = (Storyboard) this.Resources["ShowMenu"];
            _hideStoryboard = (Storyboard) this.Resources["HideMenu"];            
            Debug.WriteLine("ItemView CONSTRUCTED");
        }

        private void Button_Tap(object sender, GestureEventArgs e)
        {
            var button = (Button)sender;
            var viewModelItem = (ItemViewModel)button.Tag;

            viewModelItem.SubMenuIsVisible = !viewModelItem.SubMenuIsVisible;            
        }

        public void MenuEnabled()
        {
            Debug.WriteLine("MENU ENABLED!");
            if (Submenu.Visibility == Visibility.Collapsed)
            {
                _showStoryboard.Begin();
            }
        }

        public void MenuDisabled()
        {
            Debug.WriteLine("MENU DISABLED!");
            if (Submenu.Visibility == Visibility.Visible)
            {
                _hideStoryboard.Begin();
            }
        }        

        private void ThisUserControl_LayoutUpdated(object sender, EventArgs e)
        {
            //Debug.WriteLine("ITEMVIEW LAYOUT UPDATED!");
        }
    }

And what it looks like:

/edit 1

I tried turning it into an independent animation using a ScaleTransform, but this won't animate the surrounding ui elements. To fix this you can use a LayoutTransform instead of the standard RenderTransform. After some tweaking this worked quite nice, but the layouttranform turned it back in a slow depenpendent animation...

回答1:

You are correct that changing the UserControl height causes a large portion of the visual tree to be invalidated, but this is required, and by design. The issue is that you are modifying a controls height in a storyboard to begin with, which is not an independent animation and can't run on the compositor.

Have a read of http://msdn.microsoft.com/en-us/library/windows/apps/jj819807.aspx#dependent although this is for Windows store apps (and there is EnableDependentAnimations flag in SL), the ideas remain the same. You need to figure out a way to expand items using independent animations, probably by using a ScaleTransform.