UWP 网格行高在内容缩放变换后不更新

UWP grid rowheight not updating after scale transform of content

我有一个包含 2 行(header 和内容)的控件。 当指针进入或存在时,内容上有一个 ScaleY 变换,但是当内容缩放到 0 时,高度为 Auto 的行似乎不会折叠高度。

<Page
x:Class="ScaleTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ScaleTest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <local:TestControl VerticalAlignment="Bottom"/>

</Grid>

    <Style TargetType="local:TestControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:TestControl">
                <Grid VerticalAlignment="{TemplateBinding VerticalAlignment}">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="DisplayStates">
                            <VisualState x:Name="Minimized">
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="contentTransForm" 
                                                     Storyboard.TargetProperty="ScaleY" 
                                                     Duration="0:0:0.2" 
                                                     To="0"/>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Maximized">
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="contentTransForm" 
                                                     Storyboard.TargetProperty="ScaleY" 
                                                     Duration="0:0:0.2" 
                                                     To="1"/>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Grid Background="Red">
                        <TextBlock Text="Header"/>
                    </Grid>
                    <Grid Grid.Row="1" Background="Orange">
                        <Grid.RenderTransform>
                            <CompositeTransform x:Name="contentTransForm" ScaleY="0" ScaleX="1"/>
                        </Grid.RenderTransform>
                        <TextBlock Text="Content"/>
                    </Grid>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

我制作了一个小示例应用程序来说明问题。 sample

为什么网格没有更新。

确实如此。 RenderTransform其实只是为了渲染,不影响页面的布局。要使动画以您想要的方式显示,您需要为橙色的高度设置动画 Grid 而不是 ScaleY。问题在于这样的动画 属性 会导致布局更改,因此您必须将 EnableDependentAnimation="True" 添加到 DoubleAnimations。但是请注意,依赖动画的性能要差得多,因为它们需要对每一帧进行布局循环。如果您找到更好的解决方案,最好避免使用它们。

一种解决方案是在缩小内容时为 header 位置 (TranslateTransform) 设置动画,然后设置可见性以实际更新布局并确保一切就位。这将再次没有布局更新并且可以正常工作。

您需要在控件模板中为视觉状态添加设置器。

<ControlTemplate TargetType="local:TestControl">
    <Grid VerticalAlignment="{TemplateBinding VerticalAlignment}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid Background="Red">
            <TextBlock Text="Header"/>
        </Grid>
        <Grid x:Name="ContentGrid" Grid.Row="1" Background="Orange" MinHeight="0" Height="auto">
            <Grid.RenderTransform>
                <CompositeTransform x:Name="contentTransForm" ScaleY="0" ScaleX="1"/>
            </Grid.RenderTransform>
            <TextBlock Text="Content">
                <!--<TextBlock.RenderTransform>
                    <CompositeTransform x:Name="contentTransForm" ScaleY="0" ScaleX="1"/>
                </TextBlock.RenderTransform>-->
            </TextBlock>
        </Grid>


        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="DisplayStates">
                <VisualState x:Name="Minimized">
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetName="contentTransForm" 
                                         Storyboard.TargetProperty="ScaleY" 
                                         Duration="0:0:0.2" 
                                         To="0"/>
                    </Storyboard>
                    <VisualState.Setters>
                        <Setter Target="ContentGrid.Visibility" Value="Collapsed" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="Maximized">
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetName="contentTransForm" 
                                         Storyboard.TargetProperty="ScaleY" 
                                         Duration="0:0:0.2" 
                                         To="1"/>
                    </Storyboard>
                    <VisualState.Setters>
                        <Setter Target="ContentGrid.Visibility" Value="Visible" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
    </Grid>
</ControlTemplate>

Here 是来自您的应用程序的修改源

谢谢大家,

我实际上是通过从 wpf 工具包中实现 LayoutTansformer 并使用调解器和转换器来解决它的。 它提供了一个很好的流畅动画,没有熬夜然后突然崩溃。

LayoutTransformer:

    [TemplatePart(Name = "Presenter", Type = typeof(ContentPresenter))]
[TemplatePart(Name = "TransformRoot", Type = typeof(Grid))]
public sealed class LayoutTransformer : ContentControl
{
    public static readonly DependencyProperty LayoutTransformProperty = DependencyProperty.Register("LayoutTransform", typeof(Transform), typeof(LayoutTransformer), new PropertyMetadata(new PropertyChangedCallback(LayoutTransformer.LayoutTransformChanged)));

    private Size _childActualSize = Size.Empty;

    private const string TransformRootName = "TransformRoot";

    private const string PresenterName = "Presenter";

    private const double AcceptableDelta = 0.0001;

    private const int DecimalsAfterRound = 4;

    private Panel _transformRoot;

    private ContentPresenter _contentPresenter;

    private MatrixTransform _matrixTransform;

    private Matrix _transformation;

    public Transform LayoutTransform
    {
        get => (Transform)GetValue(LayoutTransformer.LayoutTransformProperty);
        set => SetValue(LayoutTransformer.LayoutTransformProperty, (object)value);
    }

    private FrameworkElement Child => _contentPresenter?.Content as FrameworkElement ?? _contentPresenter;

    public LayoutTransformer()
    {
        DefaultStyleKey = (object)typeof(LayoutTransformer);
        IsTabStop = false;
        UseLayoutRounding = false;
    }

    protected override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        _transformRoot = (Panel)(GetTemplateChild(TransformRootName) as Grid);
        _contentPresenter = GetTemplateChild(PresenterName) as ContentPresenter;
        _matrixTransform = new MatrixTransform();
        if (null != _transformRoot)
            _transformRoot.RenderTransform = (Transform)_matrixTransform;
        ApplyLayoutTransform();
    }

    private static void LayoutTransformChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        ((LayoutTransformer)o).ProcessTransform((Transform)e.NewValue);
    }

    public void ApplyLayoutTransform()
    {
        ProcessTransform(LayoutTransform);
    }

    private void ProcessTransform(Transform transform)
    {
        _transformation = LayoutTransformer.RoundMatrix(GetTransformMatrix(transform), DecimalsAfterRound);
        if (null != _matrixTransform)
            _matrixTransform.Matrix = _transformation;
        InvalidateMeasure();
    }

    private Matrix GetTransformMatrix(Transform transform)
    {
        if (null == transform) return Matrix.Identity;
        if (transform is TransformGroup transformGroup)
        {
            var matrix1 = Matrix.Identity;
            foreach (var transform1 in (TransformCollection)transformGroup.Children)
                matrix1 = LayoutTransformer.MatrixMultiply(matrix1, GetTransformMatrix(transform1));
            return matrix1;
        }
        if (transform is RotateTransform rotateTransform)
        {
            var num1 = 2.0 * Math.PI * rotateTransform.Angle / 360.0;
            var m12 = Math.Sin(num1);
            var num2 = Math.Cos(num1);
            return new Matrix(num2, m12, -m12, num2, 0.0, 0.0);
        }
        if (transform is ScaleTransform scaleTransform)
            return new Matrix(scaleTransform.ScaleX, 0.0, 0.0, scaleTransform.ScaleY, 0.0, 0.0);
        if (transform is SkewTransform skewTransform)
        {
            var angleX = skewTransform.AngleX;
            return new Matrix(1.0, 2.0 * Math.PI * skewTransform.AngleY / 360.0, 2.0 * Math.PI * angleX / 360.0, 1.0, 0.0, 0.0);
        }
        if (transform is MatrixTransform matrixTransform)
            return matrixTransform.Matrix;
        return Matrix.Identity;
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        if (_transformRoot == null || null == Child)
            return Size.Empty;
        _transformRoot.Measure(!(_childActualSize == Size.Empty) ? _childActualSize : ComputeLargestTransformedSize(availableSize));
        var x = 0.0;
        var y = 0.0;
        var desiredSize = _transformRoot.DesiredSize;
        var width = desiredSize.Width;
        desiredSize = _transformRoot.DesiredSize;
        var height = desiredSize.Height;
        var rect = LayoutTransformer.RectTransform(new Rect(x, y, width, height), _transformation);
        return new Size(rect.Width, rect.Height);
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        var child = Child;
        if (_transformRoot == null || null == child)
            return finalSize;
        var a = ComputeLargestTransformedSize(finalSize);
        if (LayoutTransformer.IsSizeSmaller(a, _transformRoot.DesiredSize))
            a = _transformRoot.DesiredSize;
        var rect = LayoutTransformer.RectTransform(new Rect(0.0, 0.0, a.Width, a.Height), _transformation);
        _transformRoot.Arrange(new Rect(-rect.Left + (finalSize.Width - rect.Width) / 2.0, -rect.Top + (finalSize.Height - rect.Height) / 2.0, a.Width, a.Height));
        if (LayoutTransformer.IsSizeSmaller(a, child.RenderSize) && Size.Empty == _childActualSize)
        {
            _childActualSize = new Size(child.ActualWidth, child.ActualHeight);
            InvalidateMeasure();
        }
        else
            _childActualSize = Size.Empty;
        return finalSize;
    }

    private Size ComputeLargestTransformedSize(Size arrangeBounds)
    {
        var size = Size.Empty;
        var flag1 = double.IsInfinity(arrangeBounds.Width);
        if (flag1)
            arrangeBounds.Width = arrangeBounds.Height;
        var flag2 = double.IsInfinity(arrangeBounds.Height);
        if (flag2)
            arrangeBounds.Height = arrangeBounds.Width;
        var m11 = _transformation.M11;
        var m12 = _transformation.M12;
        var m21 = _transformation.M21;
        var m22 = _transformation.M22;
        var num1 = Math.Abs(arrangeBounds.Width / m11);
        var num2 = Math.Abs(arrangeBounds.Width / m21);
        var num3 = Math.Abs(arrangeBounds.Height / m12);
        var num4 = Math.Abs(arrangeBounds.Height / m22);
        var num5 = num1 / 2.0;
        var num6 = num2 / 2.0;
        var num7 = num3 / 2.0;
        var num8 = num4 / 2.0;
        var num9 = -(num2 / num1);
        var num10 = -(num4 / num3);
        if (0.0 == arrangeBounds.Width || 0.0 == arrangeBounds.Height)
            size = new Size(arrangeBounds.Width, arrangeBounds.Height);
        else if (flag1 && flag2)
            size = new Size(double.PositiveInfinity, double.PositiveInfinity);
        else if (!LayoutTransformer.MatrixHasInverse(_transformation))
            size = new Size(0.0, 0.0);
        else if (0.0 == m12 || 0.0 == m21)
        {
            var num11 = flag2 ? double.PositiveInfinity : num4;
            var num12 = flag1 ? double.PositiveInfinity : num1;
            if (0.0 == m12 && 0.0 == m21)
                size = new Size(num12, num11);
            else if (0.0 == m12)
            {
                var height = Math.Min(num6, num11);
                size = new Size(num12 - Math.Abs(m21 * height / m11), height);
            }
            else if (0.0 == m21)
            {
                var width = Math.Min(num7, num12);
                size = new Size(width, num11 - Math.Abs(m12 * width / m22));
            }
        }
        else if (0.0 == m11 || 0.0 == m22)
        {
            var num11 = flag2 ? double.PositiveInfinity : num3;
            var num12 = flag1 ? double.PositiveInfinity : num2;
            if (0.0 == m11 && 0.0 == m22)
                size = new Size(num11, num12);
            else if (0.0 == m11)
            {
                var height = Math.Min(num8, num12);
                size = new Size(num11 - Math.Abs(m22 * height / m12), height);
            }
            else if (0.0 == m22)
            {
                var width = Math.Min(num5, num11);
                size = new Size(width, num12 - Math.Abs(m11 * width / m21));
            }
        }
        else if (num6 <= num10 * num5 + num4)
            size = new Size(num5, num6);
        else if (num8 <= num9 * num7 + num2)
        {
            size = new Size(num7, num8);
        }
        else
        {
            var width = (num4 - num2) / (num9 - num10);
            size = new Size(width, num9 * width + num2);
        }
        return size;
    }

    private static bool IsSizeSmaller(Size a, Size b)
    {
        return a.Width + AcceptableDelta < b.Width || a.Height + AcceptableDelta < b.Height;
    }

    private static Matrix RoundMatrix(Matrix matrix, int decimals)
    {
        return new Matrix(Math.Round(matrix.M11, decimals), Math.Round(matrix.M12, decimals), Math.Round(matrix.M21, decimals), Math.Round(matrix.M22, decimals), matrix.OffsetX, matrix.OffsetY);
    }

    private static Rect RectTransform(Rect rect, Matrix matrix)
    {
        var point1 = matrix.Transform(new Point(rect.Left, rect.Top));
        var point2 = matrix.Transform(new Point(rect.Right, rect.Top));
        var point3 = matrix.Transform(new Point(rect.Left, rect.Bottom));
        var point4 = matrix.Transform(new Point(rect.Right, rect.Bottom));
        var x = Math.Min(Math.Min(point1.X, point2.X), Math.Min(point3.X, point4.X));
        var y = Math.Min(Math.Min(point1.Y, point2.Y), Math.Min(point3.Y, point4.Y));
        var num1 = Math.Max(Math.Max(point1.X, point2.X), Math.Max(point3.X, point4.X));
        var num2 = Math.Max(Math.Max(point1.Y, point2.Y), Math.Max(point3.Y, point4.Y));
        return new Rect(x, y, num1 - x, num2 - y);
    }

    private static Matrix MatrixMultiply(Matrix matrix1, Matrix matrix2)
    {
        return new Matrix(matrix1.M11 * matrix2.M11 + matrix1.M12 * matrix2.M21, matrix1.M11 * matrix2.M12 + matrix1.M12 * matrix2.M22, matrix1.M21 * matrix2.M11 + matrix1.M22 * matrix2.M21, matrix1.M21 * matrix2.M12 + matrix1.M22 * matrix2.M22, matrix1.OffsetX * matrix2.M11 + matrix1.OffsetY * matrix2.M21 + matrix2.OffsetX, matrix1.OffsetX * matrix2.M12 + matrix1.OffsetY * matrix2.M22 + matrix2.OffsetY);
    }

    private static bool MatrixHasInverse(Matrix matrix)
    {
        return 0.0 != matrix.M11 * matrix.M22 - matrix.M12 * matrix.M21;
    }
}

<Style TargetType="local:LayoutTransformer">
    <Setter Property="Foreground" Value="#FF000000" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:LayoutTransformer">
                <Grid x:Name="TransformRoot" Background="{TemplateBinding Background}">
                    <ContentPresenter x:Name="Presenter" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

调解员:

    public class DoubleAnimationMediator : FrameworkElement
{
    private string _layoutTransformerName;

    public LayoutTransformer LayoutTransformer { get; set; }

    public string LayoutTransformerName
    {
        get => _layoutTransformerName;
        set
        {
            _layoutTransformerName = value;
            LayoutTransformer = null;
        }
    }

    public static readonly DependencyProperty AnimationValueProperty =
        DependencyProperty.Register(
            "AnimationValue",
            typeof(double),
            typeof(DoubleAnimationMediator),
            new PropertyMetadata(0, AnimationValuePropertyChanged));

    private static void AnimationValuePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        ((DoubleAnimationMediator)o).AnimationValuePropertyChanged();
    }

    public double AnimationValue
    {
        get => (double)GetValue(AnimationValueProperty);
        set => SetValue(AnimationValueProperty, value);
    }

    private void AnimationValuePropertyChanged()
    {
        if (null == LayoutTransformer)
        {
            LayoutTransformer = FindName(LayoutTransformerName) as LayoutTransformer;
            if (null == LayoutTransformer)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
                    "AnimationMediator was unable to find a LayoutTransformer named \"{0}\".",
                    LayoutTransformerName));
            }
        }
        CoreApplication.MainView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => LayoutTransformer.ApplyLayoutTransform()).AsTask().ConfigureAwait(true);
    }
}

测试控件:

    public class TestControl : Control
{
    public TestControl()
    {
        DefaultStyleKey = typeof(TestControl);

        PointerEntered += OnPointerEntered;
        PointerExited += OnPointerExited;
    }

    protected override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        VisualStateManager.GoToState(this, "Minimized", false);
    }

    private void OnPointerExited(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
    {
        VisualStateManager.GoToState(this, "Minimized", false);
    }

    private void OnPointerEntered(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
    {
        VisualStateManager.GoToState(this, "Maximized", false);
    }
}

    <Style TargetType="local:TestControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:TestControl">
                <Grid VerticalAlignment="{TemplateBinding VerticalAlignment}">
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="DisplayStates">
                            <VisualState x:Name="Minimized">
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="scaleYMediator" 
                                                     Storyboard.TargetProperty="AnimationValue" 
                                                     EnableDependentAnimation="True"
                                                     Duration="0:0:0.2" 
                                                     To="0"/>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Maximized">
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="scaleYMediator" 
                                                     Storyboard.TargetProperty="AnimationValue" 
                                                     EnableDependentAnimation="True"
                                                     Duration="0:0:0.2" 
                                                     To="1"/>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <local:DoubleAnimationMediator x:Name="scaleYMediator"
                                                   LayoutTransformerName="layoutTransform"
                                                   AnimationValue="{Binding ScaleY, ElementName=scaleTransform, Mode=TwoWay}"/>
                    <Grid Background="Red"
                          Grid.Row="0">
                        <TextBlock Text="Header"/>
                    </Grid>
                    <local:LayoutTransformer x:Name="layoutTransform" Grid.Row="1"
                                             Background="Orange">
                        <local:LayoutTransformer.LayoutTransform>
                            <TransformGroup>
                                <ScaleTransform x:Name="scaleTransform" ScaleY="0"/>
                            </TransformGroup>
                        </local:LayoutTransformer.LayoutTransform>
                        <local:LayoutTransformer.Content>
                            <Grid>
                                <TextBlock Text="Content"/>
                            </Grid>
                        </local:LayoutTransformer.Content>
                    </local:LayoutTransformer>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>