旋转时如何使 Windows Composition UI ElementVisual 的背面与正面不同?

How to make backside of Windows Composition UI ElementVisual different from front when rotated?

我有一个 uwp xaml 应用程序,在 UI 中有一系列按钮。

我正在使用 Windows 合成视觉层 API 来为按钮视觉效果之一的 RotationAngleInDegrees 设置动画。

按钮的内容如我所料在正面可见,但当视觉对象的背面旋转到视图中时,它也可见(尽管反转)。

期望的结果是按钮看起来类似于翻转的扑克牌,其中内容从正面可见,但在翻牌时隐藏。

我尝试了很多方法,包括:

但是,视觉元素仍然看起来是透明的,并且内容显示在旋转的视觉元素的两个面上。

这是 Windows 组合 api 中的限制或错误吗? 是否有一种简单的解决方法可以使视觉效果的背面显示与正面显示的内容不同的东西?

以下是生成动画的旋转代码的基础知识:

var btnVisual = ElementCompositionPreview.GetElementVisual(btn);            
var compositor = btnVisual.Compositor;

ScalarKeyFrameAnimation flipAnimation = compositor.CreateScalarKeyFrameAnimation();
flipAnimation.InsertKeyFrame(0.0f, 0);
flipAnimation.InsertKeyFrame(0.0001f, 180);
flipAnimation.InsertKeyFrame(1f, 0);
flipAnimation.Duration = TimeSpan.FromMilliseconds(800);
flipAnimation.IterationBehavior = AnimationIterationBehavior.Count;
flipAnimation.IterationCount = 1;
btnVisual.CenterPoint = new Vector3((float)(0.5 * btn.ActualWidth),(float) (0.5f * btn.ActualHeight), (float)(btn.ActualWidth/4));
btnVisual.RotationAxis = new Vector3(0.0f, 1f, 0f);

btnVisual.StartAnimation(nameof(btnVisual.RotationAngleInDegrees), flipAnimation);

正在翻转的按钮是默认的 xaml 按钮,最初的样式非常简单:

<Style x:Key="ButtonStyle" TargetType="Button">
    <Setter Property="HorizontalAlignment" Value="Stretch" />
    <Setter Property="VerticalAlignment" Value="Stretch" />
    <Setter Property="Margin" Value="10" />
    <Setter Property="FontSize" Value="80" />
    <Setter Property="FontFamily" Value="Webdings" />
</Style>

我的一个实验涉及修改默认按钮模板以将内容呈现器嵌套在几个边框容器中:

<Style x:Key="ButtonStyle" TargetType="Button">
        <Setter Property="HorizontalAlignment" Value="Stretch" />
        <Setter Property="VerticalAlignment" Value="Stretch" />
        <Setter Property="Margin" Value="10" />
        <Setter Property="FontSize" Value="80" />
        <Setter Property="FontFamily" Value="Webdings" />
        <Setter Property="Background" Value="Silver" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Grid x:Name="RootGrid" Background="{TemplateBinding Background}">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal">
                                    <Storyboard>
                                        <PointerUpThemeAnimation Storyboard.TargetName="RootGrid" />
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="PointerOver">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                               Storyboard.TargetProperty="BorderBrush">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightBaseMediumLowBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                               Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightBaseHighBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <PointerUpThemeAnimation Storyboard.TargetName="RootGrid" />
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Pressed">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid"
                                               Storyboard.TargetProperty="Background">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlBackgroundBaseMediumLowBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                               Storyboard.TargetProperty="BorderBrush">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightTransparentBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                               Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightBaseHighBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <PointerDownThemeAnimation Storyboard.TargetName="RootGrid" />
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Disabled">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid"
                                               Storyboard.TargetProperty="Background">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlBackgroundBaseLowBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                               Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseMediumLowBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                               Storyboard.TargetProperty="BorderBrush">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledTransparentBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Border Background="Red">
                            <Border Background="#FF62FB0A" Margin="10,10,10,10">
                                <ContentPresenter x:Name="ContentPresenter"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                Content="{TemplateBinding Content}"
                                ContentTransitions="{TemplateBinding ContentTransitions}"
                                ContentTemplate="{TemplateBinding ContentTemplate}"
                                Padding="{TemplateBinding Padding}"
                                HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                                VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                                AutomationProperties.AccessibilityView="Raw"/>
                            </Border>
                        </Border>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>

    </Style>

具体修改的部分是

<Border Background="Red">
    <Border Background="#FF62FB0A" Margin="10,10,10,10">
        <ContentPresenter x:Name="ContentPresenter"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                Content="{TemplateBinding Content}"
                                ContentTransitions="{TemplateBinding ContentTransitions}"
                                ContentTemplate="{TemplateBinding ContentTemplate}"
                                Padding="{TemplateBinding Padding}"
                                HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                                VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                                AutomationProperties.AccessibilityView="Raw"/>
    </Border>
</Border>

这帮助我确认将按钮内容放在边框容器内并不会阻止视觉在旋转时被透视。

我找到了一个变通办法,但仍然欢迎任何解决方案,当涉及到正面的项目的 3D 可见性而不是通过 parents 的背面显示时,使合成视觉效果尊重它们的包含。 =11=]

我想出的解决方法是伸手抓住视觉内容,然后为其不透明度设置动画,使其仅在旋转的一半标记处可见。我还添加了一个线性缓动,这似乎有助于 child 视觉效果在正确的时间出现。

//Get a visual for the content
var btnContent = VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(btn,0),0);
var btnContentVisual = ElementCompositionPreview.GetElementVisual(btnContent as FrameworkElement);  

var easing = compositor.CreateLinearEasingFunction();  

ScalarKeyFrameAnimation appearAnimation = compositor.CreateScalarKeyFrameAnimation();
appearAnimation.InsertKeyFrame(0.0f, 0);
appearAnimation.InsertKeyFrame(0.499999f, 0);
appearAnimation.InsertKeyFrame(0.5f, 1);
appearAnimation.InsertKeyFrame(1f, 1);
appearAnimation.Duration = TimeSpan.FromMilliseconds(800);

btnContentVisual.StartAnimation(nameof(btnContentVisual.Opacity), appearAnimation);

注意:我删除了带有额外边框的自定义模板,这就是为什么上面的解决方法只需要使用 VisualTreeHelper 深入挖掘两层以获得内容展示器,以便我可以检索它的视觉效果。