uwp 获取listviewitem里的控件_[UWP]为番茄钟应用设计一个平平无奇的状态按钮

e70e8b94be8dbe0562d3d38c7308b1f6.png

1. 为什么需要设计一个状态按钮

OnePomodoro应用里有个按钮用来控制计时器的启动/停止,本来这应该是一个包含“已启动”和“已停止”两种状态的按钮,但我以前在WPF和UWP上做过太多StateButton、ProgressButton之类的东西,已经厌倦了这种控件,所以我在OnePomodoro应用里只是简单地使用两个按钮来实现这个功能:

<Button Content="&#xE768;"
        Visibility="{x:Bind ViewModel.IsTimerInProgress,Converter={StaticResource NegationBoolToVisibilityConverter}}"
        Command="{Binding StartTimerCommand}" />
<Button Content="&#xE769;"
        Visibility="{x:Bind  ViewModel.IsTimerInProgress,Converter={StaticResource BoolToVisibilityConverter}}"
        Command="{Binding StopTimerCommand}" />

094eb7c1ff589c818de63fd48ef8d4b9.gif

颇有花花公子玩腻了找个良家结婚的意味。但两个按钮实际用起来很不顺手,手感也不好,尤其状态切换时会有种撕裂的感觉,越用越不爽,最后还是花时间又做了一个状态按钮PomodoroStateButton。这个按钮目标是要低调又炫丽,可以匹配OnePomodoro的多个主题。期间试玩了很多种技术,最后留下了这个成果:

7c496b65b9fd0726c2890f5f09fbed51.png

看起来简直就是平平无奇。

1d1b2c99c8b2e589436a65ef0fb6b2f4.png

下面说说实现细节。

2. 按钮状态

我做自定义控件一定会先写代码部分,然后再写XAML部分,功能和外观要做到解耦,写起来也不会乱。

PomodoroStateButton 继承自Button,除了Button本身的CommonStates,PomodoroStateButton还包含以下两组VisualState:

  • ProgressStates:Idle为番茄钟计时器正在计时,Busy为番茄钟停止的状态。
  • PromodoroStates:Inwork为正处于工作状态,Break为休息状态。

虽然是一个放飞自我的控件,但基本的规则还是要遵守的,VisualState对应的TemplateVisualState不能省:

[TemplateVisualState(GroupName = ProgressStatesName, Name = IdleStateName)]
[TemplateVisualState(GroupName = ProgressStatesName, Name = BusyStateName)]
[TemplateVisualState(GroupName = PromodoroStatesName, Name = InworkStateName)]
[TemplateVisualState(GroupName = PromodoroStatesName, Name = BreakStateName)]

public class PomodoroStateButton : Button
{
    private const string ProgressStatesName = "ProgressStates";
    private const string IdleStateName = "Idle";
    private const string BusyStateName = "Busy";

    private const string PromodoroStatesName = "PromodoroStates";
    private const string InworkStateName = "Inwork";
    private const string BreakStateName = "Break";

    protected virtual void UpdateVisualStates(bool useTransitions)
    {
        VisualStateManager.GoToState(this, IsInPomodoro ? InworkStateName : BreakStateName, useTransitions);
        VisualStateManager.GoToState(this, IsTimerInProgress ? BusyStateName : IdleStateName, useTransitions);
    }

有了这些按钮基本就满足番茄钟的需求了。

3. ICommand

需要支持Start和Stop两个Command。要实现ICommand支持,控件中要执行如下步骤:

  • 定义Command和CommandParameter属性。
  • 监视Command的CanExecuteChanged事件。 在CanExecuteChanged的事件处理函数及CommandParameter的PropertyChangedCallback中,根据Command.CanExecute(CommandParameter)的结果设置控件的IsEnabled属性。 在某个事件(Click或者ValueChanged)中执行Command。

这篇文章里有详细介绍:了解模板化控件(7):支持Command

因为从需求来说这个按钮不需要CommandParameter,也不需要监视CanExecuteChanged事件,所以实现得简单些:

public ICommand StartCommand
{
    get => (ICommand)GetValue(StartCommandProperty);
    set => SetValue(StartCommandProperty, value);
}

public ICommand StopCommand
{
    get => (ICommand)GetValue(StopCommandProperty);
    set => SetValue(StopCommandProperty, value);
}

private void OnClick(object sender, RoutedEventArgs e)
{
    if (IsTimerInProgress)
    {
        if (StopCommand != null && StopCommand.CanExecute(this))
            StopCommand.Execute(this);
    }
    else
    {
        if (StartCommand != null && StartCommand.CanExecute(this))
            StartCommand.Execute(this);
    }
}

4. 变形

写完代码部分才开始写XAML部分。

PomodoroStateButton的ControlTempalte中最核心的是一个Polygon,在计时器启动和停止之间按钮图标需要改变它的形状,本来是三角形,需要被用户变成正方形的形状。这部分的操纵在ProgressStates里做。如果只是简单地隐藏/显示或者更换Points会很无聊,这里我使用了以前介绍过的ProgressToPointCollectionBridge,具体可以见 用Shape做动画(2) 使用与扩展PointAnimation 这篇文章。为了让变形流畅些我让三角形先变成圆形再变形到正方形,还加入了旋转动画:

<VisualTransition From="Idle" To="Busy">
    <Storyboard >
        <DoubleAnimation Storyboard.TargetName="ProgressToPointCollectionBridge" Storyboard.TargetProperty="Progress" To="1" EnableDependentAnimation="True" Duration="0:0:0.3">
            <DoubleAnimation.EasingFunction>
                <CubicEase EasingMode="EaseOut"/>
            </DoubleAnimation.EasingFunction>
        </DoubleAnimation>
        <DoubleAnimation Storyboard.TargetName="ShapeCompositeTransform" Storyboard.TargetProperty="Rotation" To="180" EnableDependentAnimation="True" Duration="0:0:0.3">
            <DoubleAnimation.EasingFunction>
                <CubicEase EasingMode="EaseOut"/>
            </DoubleAnimation.EasingFunction>
        </DoubleAnimation>

    </Storyboard>
</VisualTransition>

<Border.Resources>
    <controls:ProgressToPointCollectionBridge x:Name="ProgressToPointCollectionBridge"
                   Progress="0">
        <PointCollection>三角形的点</PointCollection>
        <PointCollection>圆型的点</PointCollection>
        <PointCollection>正方形的点</PointCollection>
    </controls:ProgressToPointCollectionBridge>
</Border.Resources>

<Polygon Points="{Binding Source={StaticResource ProgressToPointCollectionBridge},Path=Points}"/>

e22327d07e5329bedf18d551a61b6c2a.gif

顺便提一下其它的变形方案。

HandyControl提供了GeometryAnimation,可以像使用其它线性动画那样使用变形动画:

<hc:GeometryAnimationUsingKeyFrames Storyboard.TargetProperty="Data" Storyboard.TargetName="PathDemo">
    <hc:DiscreteGeometryKeyFrame KeyTime="0:0:0.7" Value="{StaticResource FaceBookGeometry}"/>
    <hc:EasingGeometryKeyFrame KeyTime="0:0:1.2" Value="{StaticResource TwitterGeometry}">
        <hc:EasingGeometryKeyFrame.EasingFunction>
            <QuarticEase EasingMode="EaseInOut"/>
        </hc:EasingGeometryKeyFrame.EasingFunction>
    </hc:EasingGeometryKeyFrame>
</hc:GeometryAnimationUsingKeyFrames>

d2d412e9d3d04fae758bf3db01203f41.gif

也可以使用MorphSVG,或类似的SVG变形库:

642ebfda0987bd294ec5ef5ea18501d1.gif

5. 传递AlphaMask

我在使用GetAlphaMask制作阴影这篇文章里介绍了如何使用GetAlphaMask函数获取元素的AlphaMask,在 PomodoroStateButton里我也使用这个函数获取了ControlTemplate中的Polygon(就是上面变形的部分)的AlphaMask,并使用这个AlphaMask创建阴影、处理MouseEnter/MouseLeave的动画、Pressed的状态变换、还有Inwork/Break状态切换的动画。这还真是累坏它了,而要在一个元素上处理这个多动画我也会累,所以我没有使用DropShadowPanel那种ContentControl的方案,因为那样只能由ContentControl自己拥有Polygon的AlphaMask。而是创建了多个ButtonDecorator控件,让它们都用RelativeElement="{Binding ElementName=Shape}"的方式关联Polygon,然后再通过GetAlphaMask函数获取Polygon的AlphaMask,做到人手一份Polygon的AlphaMask,然后各自进行动画,这样避免了动画太过复杂。XML大致这样:

<controls:ButtonDecorator  x:Name="Shadow"
                           RelativeElement="{Binding ElementName=Shape}" 
                           Style="{StaticResource Shadow}"/>
<controls:ButtonDecorator RelativeElement="{Binding ElementName=Shape}"
                          x:Name="Outline"
                          Style="{StaticResource Outline}"/>
<controls:ButtonDecorator RelativeElement="{Binding ElementName=Shape}"
                          Style="{StaticResource Glow}"
                          IsInPomodoro="{TemplateBinding IsInPomodoro}"/>
<Polygon Points="{Binding Source={StaticResource ProgressToPointCollectionBridge},Path=Points}"
         StrokeThickness="4"
         Stretch="None" 
         StrokeEndLineCap="Round"
         x:Name="Shape"/>

6. 传递ButtonState

<VisualState x:Name="Pressed">
    <VisualState.Setters>
        <Setter Target="RootGrid.(RevealBrush.State)" Value="Pressed" />
        <Setter Target="RootGrid.Background" Value="{ThemeResource ButtonRevealBackgroundPressed}" />
        <Setter Target="ContentPresenter.BorderBrush" Value="{ThemeResource ButtonRevealBorderBrushPressed}" />
        <Setter Target="ContentPresenter.Foreground" Value="{ThemeResource ButtonForegroundPressed}" />
    </VisualState.Setters>

    <Storyboard>
        <PointerDownThemeAnimation Storyboard.TargetName="RootGrid" />
    </Storyboard>
</VisualState>

上面是是ButtonRevealStyle的部分XAML,应用了ButtonRevealStyle样式的按钮有很复杂的外观,但它的Style写得倒很简洁,这是因为它把状态传递给RevealBrush由它去处理动画(还有PointerDownThemeAnimation之类的),这样分解了复杂的XAML。我也为ButtonDecorator添加了State属性,它是一个ButtonState枚举类型的属性:

public enum ButtonState
{
    //
    // 摘要:
    //     元素处于其默认状态。
    Normal = 0,
    //
    // 摘要:
    //     指针在元素上。
    PointerOver = 1,
    //
    // 摘要:
    //     已按下元素。
    Pressed = 2
}

PomodoroStateButton在CommonStates的个状态间转变时会做轮廓的Outward和Inward动画,阴影也会变颜色,但因为通过传递ButtonState分离了复杂的XAML,所以CommonStates的XAML倒是写得很简单:

<VisualState x:Name="Normal" >
    <VisualState.Setters>
        <Setter Target="Outline.State" Value="Normal"/>
        <Setter Target="Shadow.State" Value="Normal"/>
    </VisualState.Setters>
</VisualState>
<VisualState x:Name="PointerOver">
    <VisualState.Setters>
        <Setter Target="Outline.State" Value="PointerOver"/>
        <Setter Target="Shadow.State" Value="PointerOver"/>
    </VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
    <VisualState.Setters>
        <Setter Target="Outline.State" Value="Pressed"/>
        <Setter Target="Shadow.State" Value="Pressed"/>
        <Setter Target="Shape.Opacity" Value="0.7"/>
    </VisualState.Setters>
</VisualState>

1502df1be303d4a4f5d9ee108dd0dd71.gif

7. 圆周动画

PomodoroStateButton在Inwork和Break之间切换的时候让左右两边的蓝色和红色阴影做半圈圆周运动交换位置,虽然也可以将就些,但当时太闲了就讲究起来了。

之前 介绍ProgressRing的文章 里说过怎么做圆周运动,简单来说就是把元素放到一个大的容器里,对整个容器做旋转。

<Page.Resources>
    <Storyboard RepeatBehavior="Forever" x:Key="Sb" >
        <DoubleAnimation Storyboard.TargetName="E1R" BeginTime="0" Storyboard.TargetProperty="Angle" Duration="0:0:4" To="360"/>
    </Storyboard>
</Page.Resources>
<Grid Background="White">
    <Canvas RenderTransformOrigin=".5,.5" Height="100" Width="100">
        <Canvas.RenderTransform>
            <RotateTransform x:Name="E1R" />
        </Canvas.RenderTransform>
        <Rectangle  Width="20"  Height="20"  Fill="MediumPurple"  />
    </Canvas>
</Grid>

6e52d41eefbb293d3e2e1341f566a141.gif

但是这样的话里面的元素也会跟着旋转,其中一种解决方法是里面的元素用同样的速度向着反方向做旋转,抵消外层的旋转。但那时我太闲用了另一种方法,也就是平移:

<Page.Resources>
    <Storyboard RepeatBehavior="Forever" x:Key="Sb" >
        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="Translate1" Storyboard.TargetProperty="X" EnableDependentAnimation="True">
            <EasingDoubleKeyFrame KeyTime="0:0:4" Value="120">
                <EasingDoubleKeyFrame.EasingFunction>
                    <QuadraticEase EasingMode="EaseInOut"/>
                </EasingDoubleKeyFrame.EasingFunction>
            </EasingDoubleKeyFrame>
            <EasingDoubleKeyFrame KeyTime="0:0:8" Value="0">
                <EasingDoubleKeyFrame.EasingFunction>
                    <QuadraticEase EasingMode="EaseInOut"/>
                </EasingDoubleKeyFrame.EasingFunction>
            </EasingDoubleKeyFrame>
        </DoubleAnimationUsingKeyFrames>

        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="Translate1" Storyboard.TargetProperty="Y" EnableDependentAnimation="True">
            <EasingDoubleKeyFrame KeyTime="0:0:2" Value="60">
                <EasingDoubleKeyFrame.EasingFunction>
                    <QuadraticEase EasingMode="EaseOut"/>
                </EasingDoubleKeyFrame.EasingFunction>
            </EasingDoubleKeyFrame>
            <EasingDoubleKeyFrame KeyTime="0:0:4" Value="0">
                <EasingDoubleKeyFrame.EasingFunction>
                    <QuadraticEase EasingMode="EaseIn"/>
                </EasingDoubleKeyFrame.EasingFunction>
            </EasingDoubleKeyFrame>
            <EasingDoubleKeyFrame KeyTime="0:0:6" Value="-60">
                <EasingDoubleKeyFrame.EasingFunction>
                    <QuadraticEase EasingMode="EaseOut"/>
                </EasingDoubleKeyFrame.EasingFunction>
            </EasingDoubleKeyFrame>
            <EasingDoubleKeyFrame KeyTime="0:0:8" Value="0">
                <EasingDoubleKeyFrame.EasingFunction>
                    <QuadraticEase EasingMode="EaseIn"/>
                </EasingDoubleKeyFrame.EasingFunction>
            </EasingDoubleKeyFrame>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</Page.Resources>
<Grid Background="White">
    <Grid Height="100" Width="100">
        <Rectangle Width="20" Height="20" Fill="MediumPurple"  RenderTransformOrigin=".5,.5"  HorizontalAlignment="Left" VerticalAlignment="Center">
            <Rectangle.RenderTransform>
                <TranslateTransform x:Name="Translate1" X="0" Y="0" />
            </Rectangle.RenderTransform>
        </Rectangle>
    </Grid>
</Grid>

选择QuadraticEase,搭配得宜的话可以做到漂亮的圆周运动,效果如下:

64fa18b75a6eaaae6877975e4bc260a9.gif

当然实际上我使用了CircleEase,效果更调皮些,PomodoroStateButton在Inwork和Break之间切换后的效果如下:

ad4327eb6734fe069c99739cf9fdfbab.gif

(虽然搞这么复杂也没什么意义。)

8. 结语

这样一个手感还不错,看上去很收敛实际上用了一大堆代码的状态按钮就完成了,使用了两个月下来感觉手感还算好,而且很容易和各种主题的番茄钟搭配。

可以安装我的番茄钟应用试玩一下,安装地址:

一个番茄钟

9. 源码

OnePomodoro_Controls at master

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
应用背景为变电站电力巡检,基于YOLO v4算法模型对常见电力巡检目标进行检测,并充分利用Ascend310提供的DVPP等硬件支持能力来完成流媒体的传输、处理等任务,并对系统性能做出一定的优化。.zip深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值