Как сделать заднюю часть Windows Composition UI ElementVisual отличной от передней при повороте?
У меня есть приложение UWP XAML с серией кнопок в пользовательском интерфейсе.
Я использую API визуального слоя композиции Windows для анимации RotationAngleInDegrees одного из визуальных элементов Button.
Содержимое кнопки видно на лицевой стороне, которую я ожидал, но оно также видно (хотя и перевернуто), когда задняя сторона визуального элемента поворачивается в поле зрения.
Желаемый результат заключается в том, что кнопка выглядит как перевернутая игральная карта, где содержимое видно спереди, но скрыто при переворачивании карты.
Я пробовал несколько вещей, в том числе:
изменение шаблона кнопки для вложения предъявителя контента в родительские элементы управления границами, считая, что ограничение может закрывать переднюю часть при просмотре сзади
установка свойства BackfaceVisibility для Visual в CompositionBackfaceVisibility.Visible
поиграл с настройками непрозрачности как цвета фона кнопки, так и визуального
Однако элемент Visual по-прежнему выглядит прозрачным, и содержимое отображается на обеих сторонах повернутого визуала.
Это ограничение или ошибка в API Windows Composition? Есть ли простой обходной путь, чтобы заставить заднюю сторону визуальных элементов отображать нечто иное, чем содержимое, отображаемое спереди?
Вот основы кода поворота, который производит анимацию:
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>
Это помогло мне подтвердить, что размещение содержимого кнопки внутри пограничных контейнеров не мешало визуальному отображению при повороте.
1 ответ
Я нашел обходной путь, но все равно приветствовал бы любые решения, позволяющие визуальным элементам композиции соответствовать их содержанию, когда речь идет о трехмерной видимости предметов спереди, которые не видны сквозь заднюю часть родителей.
Работа, которую я придумал, заключалась в том, чтобы достать и захватить визуальный контент, а затем анимировать его непрозрачность так, чтобы он становился видимым только на половине пути поворота. Я также добавил линейное смягчение, которое, похоже, помогает определить время появления визуального изображения ребенка в нужное время.
//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, чтобы получить contentpresenter, чтобы я мог получить его визуальный элемент.