1. 一键三连

什么是一键三连?

哔哩哔哩弹幕网中用户可以通过长按点赞键同时完成点赞、投币、收藏对UP主表示支持,后UP主多用“一键三连”向视频浏览者请求对其作品同时进行点赞、投币、收藏。

去年在云之幻大佬的 哔哩 项目里看到一键三连的 UWP 实现,觉得挺有趣的,这次参考它的代码重新实现一次,最终成果如下:

下面这些是一键三连的核心功能:

  • 可以控制并显示进度
  • 有普通状态和完成状态
  • 可以点击或长按
  • 当切换到完成状态时弹出写泡泡
  • 点击切换状态
  • 长按 2 秒钟切换状态,期间有进度显示

这篇文章将介绍如何使用自定义控件实现上面的功能。写简单的自定义控件的时候,我推荐先写完代码,然后再写控件模板,但这个控件也适合一步步增加功能,所以这篇文章用逐步增加功能的方式介绍如何写这个控件。

2. ProgressButton

万事起头难,做控件最难的是决定控件名称。不过反正也是玩玩的 Demo,就随便些用 ProgressButton 吧,因为有进度又可以点击。

第二件事就是决定这个按钮继承自哪个控件,可以选择继承 Button 或 RangeBase 以减少需要自己实现的功能。因为长按这个需求破坏了点击这个行为,所以还是放弃 Button 选择 RangeBase 比较好。然后再加上 Content 属性,控件的基础代码如下:

[ContentProperty(Name = nameof(Content))]
public partial class ProgressButton : RangeBase
{public ProgressButton(){DefaultStyleKey = typeof(ProgressButton);}public object Content{get => (object)GetValue(ContentProperty);set => SetValue(ContentProperty, value);}
}

在控件模板中用一个 CornerRadius 很大的 Border 模仿圆形边框,ContentControl 显示 Content,RadialProgressBar 显示进度,控件模板的大致结构如下:

<ControlTemplate TargetType="local:ProgressButton"><Grid x:Name="RootGrid"><Border x:Name="RootBorder"Margin="{TemplateBinding Padding}"Background="{TemplateBinding Background}"BorderBrush="{TemplateBinding BorderBrush}"BorderThickness="1"CornerRadius="100"><ContentControl x:Name="ContentControl"HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"VerticalAlignment="{TemplateBinding VerticalContentAlignment}"Content="{TemplateBinding Content}"Foreground="{TemplateBinding Foreground}" /></Border><control:RadialProgressBar x:Name="PressProgressBar"Background="Transparent"Foreground="{StaticResource PrimaryColor}"Maximum="{TemplateBinding Maximum}"Minimum="{TemplateBinding Minimum}"Outline="Transparent"Value="{TemplateBinding Value}" /></Grid>
</ControlTemplate>

这时候的调用方式及效果如下所示:

<lab:ProgressButton x:Name="LikeButton" Content="" />
<lab:ProgressButton x:Name="CoinButton" Content="" Value="0.5" />
<lab:ProgressButton x:Name="FavoriteButton" Content="" Value="1" />

3. 状态

有了上面的代码,后面的功能只需要按部就班地一个个添加上去。我从以前的代码里抄来状态相关的代码。虽然定义了这么多状态备用,其实我也只用到 Idle 和 Completed,其它要用到的话可以修改 ControlTemplate。

public enum ProgressState
{Idle,InProgress,Completed,Faulted,
}
  • Idle,空闲的状态。
  • InProgress,开始的状态,暂时不作处理。
  • Completed,完成的状态。
  • Faulted,出错的状态,暂时不作处理。

在控件模板中添加一个粉红色的带一个同色阴影的圆形背景,其它状态下隐藏,在切换到 Completed 状态时显示。为了好看,还添加了 ImplictAnimation 控制淡入淡出。

<ContentControl x:Name="CompletedElement"Template="{StaticResource CompletedTemplate}"Visibility="Collapsed"><animations:Implicit.HideAnimations><animations:OpacityAnimation SetInitialValueBeforeDelay="True"From="1"To="0"Duration="0:0:0.3" /></animations:Implicit.HideAnimations><animations:Implicit.ShowAnimations><animations:OpacityAnimation SetInitialValueBeforeDelay="True"From="0"To="1"Duration="0:0:0.6" /></animations:Implicit.ShowAnimations>
</ContentControl>

在 VisualStateManager 中加入 ProgressStates 这组状态,只需要控制 Completed 状态的 Setters,显示粉红色的背景,隐藏边框,文字变白色。

<VisualStateGroup x:Name="ProgressStates"><VisualState x:Name="Idle" /><VisualState x:Name="InProgress" /><VisualState x:Name="Completed"><VisualState.Setters><Setter Target="RootBorder.BorderBrush" Value="Transparent" /><Setter Target="ContentControl.Foreground" Value="White" /><Setter Target="CompletedElement.Visibility" Value="Visible" /></VisualState.Setters></VisualState><VisualState x:Name="Faulted" />
</VisualStateGroup>

4. Button 的 CommonStates

作为一个 Button,按钮的 PointOver 和 Pressed 状态当然必不可少,这些逻辑我参考了 真篇文章 最后一部分代码(不过我没有加入 Click 事件)。在控件模板中也制作了最简单的处理:

<VisualStateGroup x:Name="CommonStates"><VisualState x:Name="PointerOver"><VisualState.Setters><Setter Target="ContentControl.Opacity" Value="0.8" /></VisualState.Setters></VisualState><VisualState x:Name="Pressed"><VisualState.Setters><Setter Target="ContentControl.Opacity" Value="0.6" /></VisualState.Setters></VisualState>
</VisualStateGroup>

5. 气泡

气泡动画来源于火火的 BubbleButton,它封装得很优秀,ProgressButton 只需要在 Completed 状态下设置 BubbleView.IsBubbing = true 即可触发气泡动画,这大大减轻了 XAML 的工作:

<Setter Target="BubbleView.IsBubbing" Value="True" /><bubblebutton:BubbleView x:Name="BubbleView"HorizontalAlignment="Stretch"VerticalAlignment="Stretch"Foreground="{StaticResource PrimaryColor}" />

6. Tapped 和 Holding

因为要实现长按功能,所以我没有实现 Button 的 Click,而是使用了 GestureRecognizer 的 Tapped 和 Holding,订阅这两个事件,触发后重新抛出。

private GestureRecognizer _gestureRecognizer = new GestureRecognizer();public ProgressButton()
{_gestureRecognizer.GestureSettings = GestureSettings.HoldWithMouse | GestureSettings.Tap | GestureSettings.Hold;_gestureRecognizer.Holding += OnGestureRecognizerHolding;_gestureRecognizer.Tapped += OnGestureRecognizerTapped;
}public event EventHandler<HoldingEventArgs> GestureRecognizerHolding;
public event EventHandler<TappedEventArgs> GestureRecognizerTapped;protected override void OnPointerPressed(PointerRoutedEventArgs e)
{// SOME CODEvar points = e.GetIntermediatePoints(null);if (points != null && points.Count > 0){_gestureRecognizer.ProcessDownEvent(points[0]);e.Handled = true;}
}protected override void OnPointerReleased(PointerRoutedEventArgs e)
{// SOME CODEvar points = e.GetIntermediatePoints(null);if (points != null && points.Count > 0){_gestureRecognizer.ProcessUpEvent(points[0]);e.Handled = true;_gestureRecognizer.CompleteGesture();}
}protected override void OnPointerMoved(PointerRoutedEventArgs e)
{// SOME CODE_gestureRecognizer.ProcessMoveEvents(e.GetIntermediatePoints(null));
}private void OnGestureRecognizerTapped(GestureRecognizer sender, TappedEventArgs args)
{GestureRecognizerTapped?.Invoke(this, args);
}private void OnGestureRecognizerHolding(GestureRecognizer sender, HoldingEventArgs args)
{GestureRecognizerHolding?.Invoke(this, args);
}

由于一键三连属于业务方面的功能(要联网、检查状态、还可能回退),不属于控件应该提供的功能,所以 ProgressButton 只需要实现到这一步就完成了。

7. 实现一键三连

终于要实现一键三连啦。首先创建三个 ProgressButton, 然后互相双向绑定 Value 的值并订阅事件:

<lab:ProgressButton x:Name="LikeButton"Content=""GestureRecognizerHolding="OnGestureRecognizerHolding"GestureRecognizerTapped="OnGestureRecognizerTapped" />
<lab:ProgressButton x:Name="CoinButton"Content=""GestureRecognizerHolding="OnGestureRecognizerHolding"GestureRecognizerTapped="OnGestureRecognizerTapped"Value="{Binding ElementName=LikeButton, Path=Value}" />
<lab:ProgressButton x:Name="FavoriteButton"Content=""GestureRecognizerHolding="OnGestureRecognizerHolding"GestureRecognizerTapped="OnGestureRecognizerTapped"Value="{Binding ElementName=LikeButton, Path=Value}" />

处理 Tapped 的代码很简单,就是反转一下状态:

private void OnGestureRecognizerTapped(object sender, Windows.UI.Input.TappedEventArgs e)
{var progressButton = sender as ProgressButton;if (progressButton.State == ProgressState.Idle)progressButton.State = ProgressState.Completed;elseprogressButton.State = ProgressState.Idle;
}

Holding 的代码就复杂一些,设置一个动画的 Taget 然后启动动画,动画完成后把所有 ProgressButton 的状态改为 Completed,最后效果可以参考文章开头的 gif:

private void OnGestureRecognizerHolding(object sender, Windows.UI.Input.HoldingEventArgs e)
{var progressButton = sender as ProgressButton;if (e.HoldingState == HoldingState.Started){if (!_isAnimateBegin){_isAnimateBegin = true;(_progressStoryboard.Children[0] as DoubleAnimation).From = progressButton.Minimum;(_progressStoryboard.Children[0] as DoubleAnimation).To = progressButton.Maximum;Storyboard.SetTarget(_progressStoryboard.Children[0] as DoubleAnimation, progressButton);_progressStoryboard.Begin();}}else{_isAnimateBegin = false;_progressStoryboard.Stop();}
}private void OnProgressStoryboardCompleted(object sender, object e)
{LikeButton.State = ProgressState.Completed;CoinButton.State = ProgressState.Completed;FavoriteButton.State = ProgressState.Completed;
}

8. 最后

很久没有认真写 UWP 的博客了,我突然有了个大胆的想法,在这个时间点,会不会就算我胡说八道都不会有人认真去验证我写的内容?毕竟现在写 UWP 的人又不多。不过放心,我对 UWP 是认真的,我保证我是个诚实的男人。

不过这个一键三连功能做出来后,又好像,完全没机会用到嘛。难得都做出来了,就用来皮一下。

9. 源码

uwp_design_and_animation_lab

[UWP] 模仿哔哩哔哩的一键三连相关推荐

  1. 云之幻哔哩哔哩uwp_云之幻哔哩哔哩uwp

    云之幻哔哩哔哩uwp是云之幻版本的B站PC客户端,能够帮助用户以插件的形式在桌面上直接观看B站内容,使用起来比网页体验感更加好,感兴趣的用户不要错过了,欢迎下载使用! 软件特色 这个UWP客户端当然有 ...

  2. 超详细!模仿哔哩哔哩搭建静态网页

    Web静态页面开发实战学习总结 一.构思与前期准备 灵感来源于csdn博客上看到的其他人开发的模仿官方网站的开发文档,同时鉴于自己喜欢二次元的风格.决定自主设计出一个模仿哔哩哔哩·的低配版哔哩哔哩网页 ...

  3. Flutter 2进阶(三):模仿哔哩哔哩登录注册

    app 做什么都离不开强大的 B 站,模仿哔哩哔哩登录注册,效果图: 一个简单的效果,输入密码完成图片闭眼,登录按钮可以点击并且高亮的效果: 这里请求接口网络用的免费的 Bmob ,Toast 用的  ...

  4. 云之幻哔哩哔哩uwp_[UWP]推荐一款很Fluent Design的bilibili UWP客户端 : 哔哩

    UWP已经有好几个Bilibili的客户端,最近又多了一个: 作者云之幻是一位很擅长设计的UWP开发者,我也从他那里学到了很多设计方面的技巧.它还是一位Bilibili的Up主,主打PowerPoin ...

  5. css+ html 模仿哔哩哔哩页面

    效果图 ** 建议就是 样式一定要学好学扎实,不然开发过程当中挺浪费时间 ** html页面 <!DOCTYPE html> <html lang="en"> ...

  6. 哔哩哔哩注册--表单练习

    哔哩哔哩注册–表单练习 HTML代码 可能代码有些不足或者用词不当等 希望大家能够见谅 这是一次bilibili的注册表单练习 里面都有详细注释 当然一个网页有许多种 这只是其中一种 是为了相互交流 ...

  7. Python3 爬虫实战 — 模拟登陆哔哩哔哩【滑动验证码对抗】

    登陆时间:2019-10-21 实现难度:★★★☆☆☆ 请求链接:https://passport.bilibili.com/login 实现目标:模拟登陆哔哩哔哩,攻克滑动验证码 涉及知识:滑动验证 ...

  8. ant-design 本地web版本下载_bilibili 哔哩哔哩视频如何下载到电脑的 3 种方法

    bilibili(哔哩哔哩)是目前国内知名的视频弹幕网站,被粉丝们亲切的称为"B站". 现为国内领先的年轻人文化社区,非常受年轻用户的欢迎. 其操作界面简洁.速度快而且广告也不多, ...

  9. b站电脑测试用什么软件,使用BiliBili访问诊断工具检测哔哩哔哩网络的方法

    BiliBili是一个人气超高的网站,在这个网站中我们可以搜索到很多的动漫信息,对于二次元爱好者来说一定不陌生吧?不过在使用BiliBili的时候经常会因为电脑或是网速的原因导致无法打开网站,有的网站 ...

最新文章

  1. Template Method(模板方法)模式
  2. 企业Shell实战-MySQL分库分表备份脚本
  3. 【数组】Find Peak Element
  4. Oracle-Listener log解读
  5. java后台验证不能为空_java validation 后台参数验证的使用详解
  6. mysql 删除重复数据_日常答疑|MySQL删除重复数据踩过得坑
  7. android继承父类的界面,Android调用父类方法,进行子界面刷新
  8. java写顾客购买的商品总价格_成交总金额=商品价格×商品件数-总优惠额。 如果一个顾客,购买的商品一口价为5元,购买的商品件数为4,总物流运费4元,满20送3,请问成交的金额是()。...
  9. IOS9 微信支付报 prepayid 获取失败 ErrorDomainSSL, -9802
  10. 机器学习——异常值检测
  11. django开发_七牛云图片管理
  12. Pascal基础(四)-常用函数和标准库
  13. 创灵原始与鸿蒙,上古启示录
  14. 自己动手写代码生成器
  15. 痞子衡嵌入式:语音处理工具pzh-speech诞生记(6)- 文语合成实现(pyttsx3, eSpeak1.48.04)...
  16. Stream Collectors - joining
  17. 9个免费的 CSS 生成器网站
  18. callback 回调函数
  19. MapSet哈希桶(基础+常用方法总结)
  20. mysql数据库分区

热门文章

  1. 小白笔记【一】|实验室服务器使用
  2. matplotlib显示中文和负号
  3. kali基本命令(建议先学习Linux的基础)
  4. Redis daemonize 介绍
  5. 宝塔linux面板 h5ai,宝塔面板丨Nginx环境下H5ai(Dplayer)完整安装使用教程及注意事项...
  6. HTML5全球普及加速 预计将终结iOS与Android界限 转载
  7. Fast and Accurate Ground Plane Detection for the Visually Impaired from 3D Organized Point Clouds
  8. html上传图片到数据库的两种方式form方式和Ajax方式
  9. day 17:二叉树 补卡!
  10. windows小技巧_1 设置图片查看器默认自适应缩放