WPF 自定义页面动画(出)的应用——室内监控可视化

需求

接上篇。前面我们实现了页面切换时,以动画的形式加载,虽然我们在设计之初我们添加了页面卸载动画功能块,但实际上卸载的动画从没有出现过。本文主要针对自定义动画为主题,继续完成页面卸载时的动画效果。

首先上效果:

 

原理

定义两个容器(F1和F2),F1用于存放新的页面,F2用户保存旧的页面。利用页面加载时执行动画,实现动画效果。当页面切换时,根据两个容器的特性,加载对应的动画,实现页面出现或者消失动画效果。即:F1容器上的页面执行加载(出现)动画,F2容器上的页面执行卸载(消失)动画。

 

环境

Windows 10

Visual Studio 2019

.Net Framework 4.7.2

 

设计

UI设计:

上篇文章相同。

功能设计:

1.页面进入加载时,使用淡入或滑入的动画效果。

2.页面退出卸载时,使用淡出或滑出的动画效果。

 

实现

1.自定义一个用户控件,用于创建和记录页面的容器

(1)HostView控件

<UserControl x:Class="Deamon.View.ViewHost"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Deamon.View"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Frame x:Name="OldPage" NavigationUIVisibility="Hidden"/>
        <Frame x:Name="NewPage" NavigationUIVisibility="Hidden"/>
    </Grid>
</UserControl>

(2)使用依赖属性显示当前页面

using System.Windows;
using System.Windows.Controls;

namespace Deamon.View
{
    /// <summary>
    /// ViewHost.xaml 的交互逻辑
    /// </summary>
    public partial class ViewHost : UserControl
    {
        public ViewHost()
        {
            InitializeComponent();
        }

        #region 依赖属性

        /// <summary>
        /// 在 ViewHost 中显示的当前页面
        /// </summary>
        public AnimationPageBaseView CurrentView
        {
            get => (AnimationPageBaseView)GetValue(CurrentPageProperty);
            set => SetValue(CurrentPageProperty, value);
        }

        /// <summary>
        /// 注册依赖属性 <see cref="CurrentView"/> 
        /// </summary>
        public static readonly DependencyProperty CurrentPageProperty =
            DependencyProperty.Register(nameof(CurrentView), typeof(AnimationPageBaseView), typeof(ViewHost), new UIPropertyMetadata(CurrentPagePropertyChanged));

        #region 属性更改事件

        /// <summary>
        /// 当 <see cref="CurrentView"/> 值发生变化时调用
        /// </summary>
        /// <param name="d">依赖对象</param>
        /// <param name="e">依赖属性值发生变化的参数</param>
        private static void CurrentPagePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            // 获取Frame
            var newPageFrame = (d as ViewHost).NewPage;
            var oldPageFrame = (d as ViewHost).OldPage;

            // 将当前页内容存储为旧页
            var oldPageContent = newPageFrame.Content;

            // 删除当前页
            newPageFrame.Content = null;

            // 将上一页(旧页)移到 oldPageFrame 里面
            oldPageFrame.Content = oldPageContent;

            // 以动画的方式移除上一页
            if (oldPageContent is AnimationPageBaseView oldPage)
            { oldPage.ShouldAnimationOut = true; }

            // 设置当前页的内容
            newPageFrame.Content = e.NewValue;
        }

        #endregion

        #endregion

    }

}

 

2.完善具备动画能力页面(Page)基类,该类实现页面加载时以动画的方式呈现,并根据定义确定页面的动画方式(呈现还是消失)。

using Deamon.Util.Animation;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace Deamon.View
{
    public class AnimationPageBaseView :Page
    {
        #region 公共属性

        /// <summary>
        /// 加载页面动画方向 默认值为None表示只有淡出效果,没有滑动效果
        /// </summary>
        public AnimationSlideInDirection PageLoadAnimationDirection { get; set; } = AnimationSlideInDirection.None;

        /// <summary>
        /// 卸载页面动画方向 默认值为None表示只有淡出效果,没有滑动效果
        /// </summary>
        public AnimationSlideInDirection PageUnloadAnimationDirection { get; set; } = AnimationSlideInDirection.None;

        /// <summary>
        /// 标识在加载时,是否需要使用动画退出
        /// 用于将页面移动到另外的一个Frame容器上
        /// </summary>
        public bool ShouldAnimationOut { get; set; } = false;

        /// <summary>
        /// 滑动时间
        /// </summary>
        public float SlideSeconds { get; set; } = 0.5f;

        #endregion

        #region 构造函数

        /// <summary>
        /// 默认构造函数
        /// </summary>
        public AnimationPageBaseView()
        {
            // 让设计时,不去制作动画(不添加这个,设计界面会弹出“NullReferenceException”)
            if (DesignerProperties.GetIsInDesignMode(this))
            { return; }

            // 如果需要以动画的方式进入,首先将该页影藏
            if (PageLoadAnimationDirection != AnimationSlideInDirection.None)
            { Visibility = Visibility.Collapsed; }

            // 监听页面加载
            Loaded += BasePage_LoadedAsync;
        }


        #endregion

        #region 根据页面属性,实现动画加载和卸载

        /// <summary>
        /// 一旦页面加载,执行必要的动画
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void BasePage_LoadedAsync(object sender, System.Windows.RoutedEventArgs e)
        {
            if (ShouldAnimationOut)
            {
                // Animation out the page
                await AnimateOutAsync();
            }
            // Otherwise...
            else
            {
                // Animate the page in
                await AnimateInAsync();
            }
        }

        /// <summary>
        /// 以动画的方式进入
        /// </summary>
        /// <returns></returns>
        public async Task AnimateInAsync()
        {
            // 开始动画
            await this.SlideAndFadeInAsync(PageLoadAnimationDirection, false, SlideSeconds, size: (int)Application.Current.MainWindow.Width);
        }

        /// <summary>
        /// 以动画的方式退出
        /// </summary>
        /// <returns></returns>
        public async Task AnimateOutAsync()
        {
            // 开始动画
            await this.SlideAndFadeOutAsync(PageUnloadAnimationDirection, SlideSeconds);
        }

        #endregion
    }
}

 

3.在MainWindow中应用ViewHost控件

<Window x:Class="Deamon.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Deamon"
        xmlns:view="clr-namespace:Deamon.View"
        xmlns:converter="clr-namespace:Deamon.Util.ValueConverter"
        xmlns:vm="clr-namespace:Deamon.ViewModel"
        mc:Ignorable="d" Background="#FF031704"
        DataContext="{Binding ApplicationVM, Source={x:Static vm:ViewModelLocator.Locator}}"
        Title="Chamber Information Runtime Monitor System" Height="450" Width="800">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <Grid Grid.Row="0" Margin="5 0 5 5" Height="44"
              Visibility="{Binding HasNavigationBar,Converter={converter:BooleanToVisibilityCollapsedConverter},ConverterParameter=True}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <WrapPanel Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0 0 50 0">
                <RadioButton Command="{Binding HomeCommand}"  Content="Deamon" Style="{StaticResource MainNavigation}"/>
                <RadioButton Command="{Binding ChamberCommand}"  Content="Chamber" Style="{StaticResource MainNavigation}" />
                <RadioButton Command="{Binding RecordCommand}"  Content="Record" Style="{StaticResource MainNavigation}"/>
                <RadioButton Command="{Binding SettingsCommand}"  Content="Settings" Style="{StaticResource MainNavigation}"/>
            </WrapPanel>

            <Border Grid.Column="1">
                <WrapPanel VerticalAlignment="Bottom">

                    <Button Style="{StaticResource CoverGeometryPathBackgroundButton}" Width="23">
                        <PathGeometry Figures="M 101.520,0.000 L 21.240,29.699 L 0.000,16.199 L 0.000,61.919 L 21.240,49.859 L 101.520,79.380 L 101.520,0.000 Z"/>
                    </Button>

                    <Button Style="{StaticResource CoverGeometryPathBackgroundButton}" Width="23">
                        <PathGeometry Figures="M 30.300,278.620 L 30.300,49.417 L 224.530,177.874 L 224.575,177.806 L 224.640,177.905 L 420.180,49.512 L 420.180,278.620 L 30.300,278.620 Z M 224.318,135.392 L 65.295,30.220 L 384.493,30.220 L 224.318,135.392 Z M 450.120,0.000 L 420.180,0.000 L 0.120,0.000 L 0.120,0.190 L 0.000,0.190 L 0.000,308.860 L 0.120,308.860 L 30.300,308.860 L 420.180,308.860 L 450.120,308.860 L 450.579,308.860 L 450.579,0.000 L 450.120,0.000 Z"/>
                    </Button>

                </WrapPanel>
            </Border>

        </Grid>

        <!-- 主容器 -->
        <view:ViewHost Grid.Row="1" Grid.RowSpan="1"
                       Grid.Column="0" Grid.ColumnSpan="1"
                       CurrentView="{Binding}" 
                       DataContext="{Binding Path=CurrentView,Converter={converter:ApplicationViewValueConverter}}"/>

    </Grid>
</Window>

 

4.应用具备动画能力的页面(AnimationPageBaseView),在派生类中设置页面卸载的动画属性(PageUnloadAnimationDirection )

    /// <summary>
    /// LoginView.xaml 的交互逻辑
    /// </summary>
    public partial class LoginView : AnimationPageBaseView
    {
        public LoginView()
        {
            InitializeComponent();

            PageLoadAnimationDirection = Util.Animation.AnimationSlideInDirection.None;
            PageUnloadAnimationDirection = Util.Animation.AnimationSlideInDirection.Left;
        }
    }
    /// <summary>
    /// HomeView.xaml 的交互逻辑
    /// </summary>
    public partial class HomeView : AnimationPageBaseView
    {
        public HomeView()
        {
            InitializeComponent();

            PageLoadAnimationDirection = Util.Animation.AnimationSlideInDirection.Right;
            PageUnloadAnimationDirection = Util.Animation.AnimationSlideInDirection.Left;
        }
    }
    /// <summary>
    /// ChamberView.xaml 的交互逻辑
    /// </summary>
    public partial class ChamberView : AnimationPageBaseView
    {
        public ChamberView()
        {
            InitializeComponent();

            PageLoadAnimationDirection = Util.Animation.AnimationSlideInDirection.Bottom;
            PageUnloadAnimationDirection = Util.Animation.AnimationSlideInDirection.Top;
        }
    }
    /// <summary>
    /// RecordView.xaml 的交互逻辑
    /// </summary>
    public partial class RecordView : AnimationPageBaseView
    {
        public RecordView()
        {
            InitializeComponent();

            PageLoadAnimationDirection = Util.Animation.AnimationSlideInDirection.Top;
            PageUnloadAnimationDirection = Util.Animation.AnimationSlideInDirection.Bottom;
        }
    }
    /// <summary>
    /// SettingsView.xaml 的交互逻辑
    /// </summary>
    public partial class SettingsView : AnimationPageBaseView
    {
        public SettingsView()
        {
            InitializeComponent();

            PageLoadAnimationDirection = Util.Animation.AnimationSlideInDirection.Left;
            PageUnloadAnimationDirection = Util.Animation.AnimationSlideInDirection.Right;
        }
    }

 

Over

每次记录一小步...点点滴滴人生路...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值