Geometry几何图形集合+3D变换
实现效果:
- 展示玻璃效果的Radiobutton
- 选择页面时页面的出现及退出呈现动画旋转、缩放效果
有关具体类的含义见下章解析。
文件结构:
主窗口xaml:
主窗口DockPanel承载左侧玻璃Radiobutton按钮区域Border及右侧屏幕的区域Grid,Grid下加载黑色方格+径向渐变色的背景方框、加载页面Page的Frame的ScrollView区域、显示3D动画ViewBox的区域Border。
- Viewbox的长宽绑定ScrollView的实际长宽,其中的3D材质的VisualBrush的Visual源绑定到ScrollView元素,同时进行旋转、缩放动画。
- 当选择按钮时,在右侧Viewbox执行当前页面的退出动画3D效果。同时Checked事件设置新页面索引,执行完成退出动画后及,再执行新页面的进入动画3D效果。
Radiobutton代码:
<StackPanel VerticalAlignment="Stretch" Margin="10">
<RadioButton x:Name="Example1RadioButton" Checked="SampleSelected" Margin="0,0,0,10">Geometry Usage</RadioButton>
<RadioButton Checked="SampleSelected" Margin="0,0,0,10">Shape Geometries</RadioButton>
<RadioButton Checked="SampleSelected" Margin="0,0,0,10">PathGeometry</RadioButton>
<RadioButton Checked="SampleSelected" Margin="0,0,0,10">Geometry Attribute Syntax Example</RadioButton>
<RadioButton Checked="SampleSelected">Combining Geometries Example</RadioButton>
</StackPanel>
右侧展示页面动画及内容的xaml:
其中设有Frame的缩放对象ScaleTransform(取消无影响),
<Border Name="scrollViewerBorder">
<ScrollViewer Name="myScrollViewer" Background="White"
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Frame Name="mainFrame" Background="White" ContentRendered="FrameContentRendered"
NavigationUIVisibility="Hidden">
<Frame.RenderTransform>
<ScaleTransform />
</Frame.RenderTransform>
</Frame>
</ScrollViewer>
</Border>
3D动画容器代码:
内容装饰器ViewBox长宽绑定页面的容器ScrollView实际长宽,且其内容Viewport3D二维布局范围内包含的三维内容。
- 设置Viewport3D.Camera摄像机对象:PerspectiveCamera透视投影摄像机
- 设置Viewport3D.ModelVisual3D 3D模型视觉对象,其中包含3D模型环境光源对象AmbientLight
-
3D模型还包含使用指定的 Material 呈现 Geometry3D对象。
- 其GeometryModel3D.Geometry中包含三维三角基元类MeshGeometry3D 3D网状几何对象
- 其中GeometryModel3D.Material 3D素材对象设为光线漫射素材DiffuseMaterial ,其画刷DiffuseMaterial.Brush设置为VisualBrush,继承Visual的ScrollView对象作为画刷的Visual绑定源。
- 至此3D模型构建完成。
-
ModelVisual3D.Transform 3D模型视觉对象的变换:
- RotateTransform3D.Rotation 3D旋转对象设置为AxisAngleRotation3D 3D绕轴角度旋转对象。
- 设置ScaleTransform3D缩放3D变换。
<Border>
<Border ClipToBounds="True">
<Viewbox
Stretch="Fill"
Width="{Binding ElementName=myScrollViewer, Path=ActualWidth}"
Height="{Binding ElementName=myScrollViewer, Path=ActualHeight}">
<Viewport3D
Name="myViewport3D"
Opacity="1" IsHitTestVisible="False">
<Viewport3D.Camera>
<PerspectiveCamera
LookDirection="0,0,-2"
UpDirection="0,1,0"
Position="0,0,5"
FieldOfView="90"/>
</Viewport3D.Camera>
<ModelVisual3D>
<ModelVisual3D.Children>
<ModelVisual3D>
<ModelVisual3D.Content>
<AmbientLight Color="#FFFFFFFF" />
</ModelVisual3D.Content>
</ModelVisual3D>
<ModelVisual3D>
<ModelVisual3D.Children>
<ModelVisual3D>
<ModelVisual3D.Content>
<GeometryModel3D x:Name="myPlane">
<GeometryModel3D.Geometry>
<MeshGeometry3D
x:Name="myGeometry"
TriangleIndices="0,1,2 3,4,5, 11,10,9,8,7,6"
TextureCoordinates="0,0 0,-1 -1,-1 -1,-1 -1,0 0,0 -1,-1 -1,0 0,0 0,0 0,-1 -1,-1 "
Positions="12,-9,0 12,9,0 -12,9,0 -12,9,0 -12,-9,0 12,-9,0" />
</GeometryModel3D.Geometry>
<GeometryModel3D.Material>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<VisualBrush
RenderOptions.CacheInvalidationThresholdMinimum="0"
RenderOptions.CachingHint="Cache"
Visual="{Binding ElementName=myScrollViewer}" />
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</GeometryModel3D.Material>
</GeometryModel3D>
</ModelVisual3D.Content>
<ModelVisual3D.Transform>
<Transform3DGroup>
<RotateTransform3D x:Name="myHorizontalRotation"
CenterX="0" CenterY="0" CenterZ="0">
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Axis="0,1,0" Angle="0"
x:Name="MyHorizontalAxisAngleRotation3D" />
</RotateTransform3D.Rotation>
</RotateTransform3D>
<ScaleTransform3D x:Name="MyScaleTransform3D"
ScaleX="1" ScaleY="1" ScaleZ="1" />
</Transform3DGroup>
</ModelVisual3D.Transform>
</ModelVisual3D>
</ModelVisual3D.Children>
</ModelVisual3D>
</ModelVisual3D.Children>
</ModelVisual3D>
</Viewport3D>
</Viewbox>
</Border>
</Border>
选定按钮后页面触发器执行退出3D动画:
- 对上面的3D模型的水平轴、缩放x/y/z的值进行动画
- 同理设置页面进入时的3D模型动画效果,作为动画资源,被设置为退出动画后Completed》完成进入动画的执行。
<Page.Triggers>
<EventTrigger RoutedEvent="RadioButton.Checked">
<BeginStoryboard>
<Storyboard Completed="ZoomOutStoryboardCompleted">
<DoubleAnimation Storyboard.TargetName="MyHorizontalAxisAngleRotation3D"
Storyboard.TargetProperty="Angle"
From="0" To="360" Duration="0:0:1" AutoReverse="False" FillBehavior="Stop"
BeginTime="0:0:1" AccelerationRatio="1"
/>
<DoubleAnimation Storyboard.TargetName="MyScaleTransform3D"
Storyboard.TargetProperty="ScaleX"
From="1" To="0" Duration="0:0:2" AutoReverse="False" AccelerationRatio="1"
/>
<DoubleAnimation Storyboard.TargetName="MyScaleTransform3D"
Storyboard.TargetProperty="ScaleY"
From="1" To="0" Duration="0:0:2" AutoReverse="False" AccelerationRatio="1"
/>
<DoubleAnimation Storyboard.TargetName="MyScaleTransform3D"
Storyboard.TargetProperty="ScaleZ"
From="1" To="0.1" Duration="0:0:2" AutoReverse="False" AccelerationRatio="1"
/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Page.Triggers>
后台代码:
主页面初始化时就储存各页面初始化对象:
ps:可设置动态加载页面?
public SampleViewer()
{
InitializeComponent();
_examples = new Page[5];
_examples[0] = new GeometryUsageExample();
_examples[1] = new ShapeGeometriesExample();
_examples[2] = new PathGeometryExample();
_examples[3] = new CombiningGeometriesExample();
_examples[4] = new GeometryAttributeSyntaxExample();
}
主页面加载后执行选定一个Radiobutton,同时触发执行之前画面的退场动画。
private void PageLoaded(object sender, RoutedEventArgs args)
{
Example1RadioButton.IsChecked = true;
}
动画退场后执行Frame导航到新选定的页面并显示内容。
private void ZoomOutStoryboardCompleted(object sender, EventArgs args)
{
mainFrame.Navigate(_examples[_sampleIndex]);
}
新页面加载呈现内容后执行进场动画资源启动:
private void FrameContentRendered(object sender, EventArgs args)
{
var s = (Storyboard)Resources["ZoomInStoryboard"];
s.Begin(this);
}
进场动画完成后执行页面内容的显示,遮住动画画面并提供操作:
private void ZoomInStoryboardCompleted(object sender, EventArgs e)
{
scrollViewerBorder.Visibility = Visibility.Visible;
}
当选定一个按钮后发生:
- 设置3D三角基元的开始位置
- scrollViewerBorder内容隐藏,只显示动画界面
- 设置选定的页面对应索引值,作为退场动画后加载新页面的索引值。
private void SampleSelected(object sender, RoutedEventArgs args)
{
var points = new Point3DCollection();
var ratio = myScrollViewer.ActualWidth/myScrollViewer.ActualHeight;
points.Add(new Point3D(5, -5*ratio, 0));
points.Add(new Point3D(5, 5*ratio, 0));
points.Add(new Point3D(-5, 5*ratio, 0));
points.Add(new Point3D(-5, 5*ratio, 0));
points.Add(new Point3D(-5, -5*ratio, 0));
points.Add(new Point3D(5, -5*ratio, 0));
points.Add(new Point3D(-5, 5*ratio, 0));
points.Add(new Point3D(-5, -5*ratio, 0));
points.Add(new Point3D(5, -5*ratio, 0));
points.Add(new Point3D(5, -5*ratio, 0));
points.Add(new Point3D(5, 5*ratio, 0));
points.Add(new Point3D(-5, 5*ratio, 0));
myGeometry.Positions = points;
myViewport3D.Width = 100;
myViewport3D.Height = 100*ratio;
scrollViewerBorder.Visibility = Visibility.Hidden;
var button = sender as RadioButton;
if (button != null)
{
if (button.Content.ToString() == "Geometry Usage")
_sampleIndex = 0;
else if (button.Content.ToString() == "Shape Geometries")
_sampleIndex = 1;
else if (button.Content.ToString() == "PathGeometry")
_sampleIndex = 2;
else if (button.Content.ToString() == "Combining Geometries Example")
_sampleIndex = 3;
else if (button.Content.ToString() == "Geometry Attribute Syntax Example")
_sampleIndex = 4;
}
}
扩展:
- 以上按钮点击在某种情况下有bug:当正在显示第一个页面退场时点击第二个按钮,随机马上再点击之前的第一个页面按钮,那么当第一个页面退出动画完成后,不会出现第一个页面的进场动画及内容显示.
- 分析原因:同一个页面退场动画执行后,再次触发同样的动画时,会覆盖之前的效果。其中包括完成动画要执行的mainFrame.Navigate方法并没有执行。
- 当新的退出动画完成后发现mainFrame.Navigate导航的内容还是原来的第一个页面,那么就不会再重新程序内容及进场动画,此时页面内容是隐藏的。
- 临时解决方法:记录当前页面索引值,比较确定是否最后选择的页面还是之前第一个,那么直接显示内容画面。
private void ZoomOutStoryboardCompleted(object sender, EventArgs args)
{
mainFrame.Navigate(_examples[_sampleIndex]);
if (_currentIndex == _sampleIndex)
{
scrollViewerBorder.Visibility = Visibility.Visible;
}
else
{
_currentIndex = _sampleIndex;
}
}
玻璃按钮请看下一章。