[MAUI]模仿哔哩哔哩的一键三连

文章目录

    • 创建弧形进度条
      • 绘制弧
    • 准备物料
    • 创建气泡
    • 创建手势
    • 创建交互与动效
    • 项目地址

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

在这里插入图片描述

“三连按钮”是一组按钮,轻击时当做普通状态按钮使用,当长按 2 秒钟后,转为三连模式,可以控制并显示进度,并在进度完成时弹出一些泡泡

一直想实现这个效果,但由于.NET MAUI对图形填充渲染问题直到.NET 8才修复。想要的效果直到最近才能实现。

两年前Dino老师用UWP实现过“一键三连”:

[UWP]模仿哔哩哔哩的一键三连

今天用MAUI实现。

在这里插入图片描述

使用.NET MAU实现跨平台支持,本项目可运行于Android、iOS平台。

创建弧形进度条

新建.NET MAUI项目,命名HoldDownButtonGroup

在项目中添加SkiaSharp绘制功能的引用Microsoft.Maui.Graphics.Skia以及SkiaSharp.Views.Maui.Controls

<ItemGroup>
    <PackageReference Include="Microsoft.Maui.Graphics.Skia" Version="7.0.59" />
    <PackageReference Include="SkiaSharp.Views.Maui.Controls" Version="2.88.3" />
</ItemGroup>

进度条(ProgressBar)用于展示任务的进度,告知用户当前状态和预期,本项目依赖弧形进度条组件(CircleProgressBar),此组件在本项目中用于展示“三连按钮”长按任务的进度

这里简单介绍弧形进度条组件的原理和实现,更多内容请阅读:[MAUI]弧形进度条与弧形滑块的交互实现。

控件将包含以下可绑定属性:

  • Maxiumum:最大值
  • Minimum:最小值
  • Progress:当前进度
  • AnimationLength:动画时长
  • BorderWidth:描边宽度
  • LabelContent:标签内容
  • ContainerColor:容器颜色,即进度条的背景色
  • ProgressColor:进度条颜色

创建CircleProgressBar.xaml,代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:forms="clr-namespace:SkiaSharp.Views.Maui.Controls;assembly=SkiaSharp.Views.Maui.Controls"
             x:Class="HoldDownButtonGroup.Controls.CircleProgressBar">
    <ContentView.Content>
        <Grid>
            <forms:SKCanvasView x:Name="canvasView"
                                PaintSurface="OnCanvasViewPaintSurface" />
            <ContentView x:Name="MainContent"></ContentView>
            <Label 
                FontSize="28"
                HorizontalOptions="Center"
                VerticalOptions="Center" 
                x:Name="labelView"></Label>
        </Grid>

    </ContentView.Content>
</ContentView>

SKCanvasView是SkiaSharp.Views.Maui.Controls封装的View控件。

绘制弧

Skia中,通过AddArc方法绘制弧,需要传入一个SKRect对象,其代表一个弧(或椭弧)的外接矩形。startAngle和sweepAngle分别代表顺时针起始角度和扫描角度。

通过startAngle和sweepAngle可以绘制出一个弧,如下图红色部分所示:

在这里插入图片描述

CircleProgressBar.xaml.cs的CodeBehind中,创建OnCanvasViewPaintSurface,通过给定起始角度为正上方,扫描角度为360对于100%进度,通过插值计算出当前进度对应的扫描角度,绘制出进度条。

private void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{

    var SumValue = Maximum - Minimum;


    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    SKRect rect = new SKRect(_mainRectPadding, _mainRectPadding, info.Width - _mainRectPadding, info.Height - _mainRectPadding);
    float startAngle = -90;
    float sweepAngle = (float)((_realtimeProgress / SumValue) * 360);

    canvas.DrawOval(rect, OutlinePaint);

    using (SKPath path = new SKPath())
    {
        path.AddArc(rect, startAngle, sweepAngle);
        
        canvas.DrawPath(path, ArcPaint);
    }
}

其中SumValue表明进度条的总进度,通过Maximum和Minimum计算得出。

public double SumValue => Maximum - Minimum;

创建进度条轨道背景画刷和进度条画刷,其中进度条画刷的StrokeCap属性设置为SKStrokeCap.Round,使得进度条两端为圆形。

protected SKPaint _outlinePaint;

public SKPaint OutlinePaint
{
    get
    {
        if (_outlinePaint == null)
        {
            RefreshMainRectPadding();
            SKPaint outlinePaint = new SKPaint
            {
                Color = this.ContainerColor.ToSKColor(),
                Style = SKPaintStyle.Stroke,
                StrokeWidth = (float)BorderWidth,
            };
            _outlinePaint = outlinePaint;
        }
        return _outlinePaint;
    }
}

protected SKPaint _arcPaint;

public SKPaint ArcPaint
{
    get
    {
        if (_arcPaint == null)
        {
            RefreshMainRectPadding();
            SKPaint arcPaint = new SKPaint
            {
                Color = this.ProgressColor.ToSKColor(),
                Style = SKPaintStyle.Stroke,
                StrokeWidth = (float)BorderWidth,
                StrokeCap = SKStrokeCap.Round,
            };
            _arcPaint = arcPaint;
        }

        return _arcPaint;
    }
}

在Progress值变更时,重新渲染进度条,并触发ValueChanged事件。


private void UpdateProgress()
{
    this._realtimeProgress = this.Progress;
    this.labelView.Text = this.Progress.ToString(LABEL_FORMATE);
    this.canvasView?.InvalidateSurface();
}

效果如下

在这里插入图片描述

准备物料

点赞、投币、收藏三个按钮的图片来自于哔哩哔哩(Bilibili)网站。这些按钮用svg格式在html中。

我们只需前往哔哩哔哩主站,要打开浏览器的开发者工具,用元素检查器,在找到按钮位置后查看其样式,拷贝path中的svg代码,即可得到这些矢量图片。

在这里插入图片描述

拷贝右侧红色区域选中的部分,我们只需要这些svg代码。

在Xaml中我们创建Path元素,并设置Data属性为svg代码。即可得到一个图形

<Path HeightRequest="65"
        WidthRequest="65"
        x:Name="Icon1"
        Fill="Transparent"
        Aspect="Uniform"
        Data="M 9.77234 30.8573 V 11.7471 H 7.54573 C 5.50932 11.7471 3.85742 13.3931 3.85742 15.425 V 27.1794 C 3.85742 29.2112 5.50932 30.8573 7.54573 30.8573 H 9.77234 Z M 11.9902 30.8573 V 11.7054 C 14.9897 10.627 16.6942 7.8853 17.1055 3.33591 C 17.2666 1.55463 18.9633 0.814421 20.5803 1.59505 C 22.1847 2.36964 23.243 4.32583 23.243 6.93947 C 23.243 8.50265 23.0478 10.1054 22.6582 11.7471 H 29.7324 C 31.7739 11.7471 33.4289 13.402 33.4289 15.4435 C 33.4289 15.7416 33.3928 16.0386 33.3215 16.328 L 30.9883 25.7957 C 30.2558 28.7683 27.5894 30.8573 24.528 30.8573 H 11.9911 H 11.9902 Z"
        VerticalOptions="Center"
        HorizontalOptions="Center" />

在这里插入图片描述

创建气泡

气泡实现分为两个步骤:

Bubble.xaml 创建单一气泡动画
Bubbles.xaml 包含气泡集群。随机生成气泡动画路径,创建气泡集群的动画

Bubbles控件将包含以下可绑定属性:

  • Brush:气泡颜色
  • BubbleCnt:气泡数量
  • BubbleSize:气泡大小

气泡动画算法参考于火火的 BubbleButton,这里只帖关键代码

单一气泡的动画:先变大后消失

public Animation GetAnimation()
{

    var scaleAnimation = new Animation();

    var scaleUpAnimation0 = new Animation(v => MainBox.Scale = v, 0, 1);
    var scaleUpAnimation1 = new Animation(v => MainBox.Scale = v, 1, 0);


    scaleAnimation.Add(0, 0.2, scaleUpAnimation0);
    scaleAnimation.Add(0.8, 1, scaleUpAnimation1);

    scaleAnimation.Finished = () =>
    {
        this.MainBox.Scale = 0;
    };

    return scaleAnimation;

}

生成气泡

public void SpawnBubbles()
{
    this.PitContentLayout.Clear();
    for (int i = 0; i < BubbleCnt; i++)
    {
        var currentBox = new Bubble();
        currentBox.FillColor = i % 2 == 0 ? this.Brush : SolidColorBrush.Transparent;
        currentBox.BorderColor = this.Brush;
        currentBox.HeightRequest = BubbleSize;
        currentBox.WidthRequest = BubbleSize;
        currentBox.HorizontalOptions = LayoutOptions.Start;
        currentBox.VerticalOptions = LayoutOptions.Start;
        this.PitContentLayout.Add(currentBox);
    }
}

计算单个气泡的动画路径:随机产生动画运动的随机坐标

private Animation InitAnimation(Bubble element, Size targetSize, bool isOnTop = true)
{


    var offsetAnimation = new Animation();

    if (targetSize == default)
    {
        targetSize = element.DesiredSize;

    }
    var easing = Easing.Linear;

    var originX = PitContentLayout.Width / 2;
    var originY = PitContentLayout.Height / 2;

    var targetX = rnd.Next(-(int)targetSize.Width, (int)targetSize.Width) + (int)targetSize.Width / 2 + originX;
    var targetY = isOnTop ? rnd.Next(-(int)(targetSize.Height * 1.5), 0) + (int)targetSize.Height / 2 + originY :
            rnd.Next(0, (int)(targetSize.Height * 1.5)) + (int)targetSize.Height / 2 + originY
        ;


    var offsetX = targetX - originX;
    var offsetY = targetY - originY;


    var offsetAnimation1 = new Animation(v => element.TranslationX = v, originX - targetSize.Width / 2, targetX - targetSize.Width / 2, easing);
    var offsetAnimation2 = new Animation(v => element.TranslationY = v, originY - targetSize.Height / 2, targetY - targetSize.Height / 2, easing);

    offsetAnimation.Add(0.2, 0.8, offsetAnimation1);
    offsetAnimation.Add(0.2, 0.8, offsetAnimation2);
    offsetAnimation.Add(0, 1, element.BoxAnimation);

    offsetAnimation.Finished = () =>
    {

        element.TranslationX = originX;
        element.TranslationY = originY;
        element.Rotation = 0;
    };

    return offsetAnimation;
}

开始气泡动画

public void StartAnimation()
{

    Content.AbortAnimation("ReshapeAnimations");
    var offsetAnimationGroup = new Animation();

    foreach (var item in this.PitContentLayout.Children)
    {
        if (item is Bubble)
        {
            var isOntop = this.PitContentLayout.Children.IndexOf(item) > this.PitContentLayout.Children.Count / 2;
            var currentAnimation = InitAnimation(item as Bubble, targetSize, isOntop);
            offsetAnimationGroup.Add(0, 1, currentAnimation);


        }
    }
    offsetAnimationGroup.Commit(this, "ReshapeAnimations", 16, 400);

}

创建手势

可喜可贺,在新发布的.NET 8 中, .NET MAUI 引入了指针手势识别器(PointerGestureRecognizer),使用方式如下,终于不用自己实现手势监听控件了。

Xaml:

<Image Source="dotnet_bot.png">
    <Image.GestureRecognizers>
        <PointerGestureRecognizer PointerEntered="OnPointerEntered"
                                  PointerExited="OnPointerExited"
                                  PointerMoved="OnPointerMoved" />
  </Image.GestureRecognizers>
</Image>

C#:

void OnPointerEntered(object sender, PointerEventArgs e)
{
    // Handle the pointer entered event
}

void OnPointerExited(object sender, PointerEventArgs e)
{
    // Handle the pointer exited event
}

void OnPointerMoved(object sender, PointerEventArgs e)
{
    // Handle the pointer moved event
}

具体请阅读官方文档:
.NET 8 中 .NET MAUI 的新增功能以及
识别指针手势

在本项目中,需要监听长按动作,当“三连按钮”长按2秒后,转为三连模式,此时需要监听手指释放情况,当时长不足时取消三连。

由于在之前的文章中实现过监听手势,这里仅简单介绍定义,其余内容不再重复,如需了解请阅读: [MAUI程序设计] 用Handler实现自定义跨平台控件

定义可以监听的手势类别,分别是按下、移动、抬起、取消、进入、退出


 public enum TouchActionType
    {
        Entered,
        Pressed,
        Moved,
        Released,
        Exited,
        Cancelled
    }

添加手势监听器TouchRecognizer,它将提供一个事件OnTouchActionInvoked,用触发手势动作。

public partial class TouchRecognizer: IDisposable
{
    public event EventHandler<TouchActionEventArgs> OnTouchActionInvoked;
    public partial void Dispose();
}

EventArg类TouchActionEventArgs,用于传递手势动作的参数

创建交互与动效

在页面创建完三个按钮后,在CodeBehind中编写交互逻辑

添加更新圆环进度的方法UpdateProgressWithAnimate,此方法根据圆环进度的百分比,计算圆环进度的动画时间,并开始动画


private void UpdateProgressWithAnimate(CircleProgressBar progressElement, double progressTarget = 100, double totalLenght = 5*1000, Action<double, bool> finished = null)
{
    Content.AbortAnimation("ReshapeAnimations");
    var scaleAnimation = new Animation();

    double progressOrigin = progressElement.Progress;

    var animateAction = (double r) =>
    {
        progressElement.Progress = r;
    };

    var scaleUpAnimation0 = new Animation(animateAction, progressOrigin, progressTarget);

    scaleAnimation.Add(0, 1, scaleUpAnimation0);
    var calcLenght = (double)(Math.Abs((progressOrigin - progressTarget) / 100)) * totalLenght;

    scaleAnimation.Commit(progressElement, "ReshapeAnimations", 16, (uint)calcLenght, finished: finished);

}

创建“三连成功”的动画方法StartCelebrationAnimate,在这里通过改变按钮的Scale和Fill属性,实现三连成功时“点亮”图标的动画效果。


private void StartCelebrationAnimate(Path progressElement, Action<double, bool> finished = null)
{
    var toColor = (Color)this.Resources["BrandColor"];
    var fromColor = (Color)this.Resources["DisabledColor"];

    var scaleAnimation = new Animation();

    var scaleUpAnimation0 = new Animation(v => progressElement.Scale=v, 0, 1.5);
    var scaleUpAnimation1 = new Animation(v => progressElement.Fill=GetColor(v, fromColor, toColor), 0, 1);
    var scaleUpAnimation2 = new Animation(v => progressElement.Scale=v, 1.5, 1);

    scaleAnimation.Add(0, 0.5, scaleUpAnimation0);
    scaleAnimation.Add(0, 1, scaleUpAnimation1);
    scaleAnimation.Add(0.5, 1, scaleUpAnimation2);

    scaleAnimation.Commit(progressElement, "CelebrationAnimate", 16, 400, finished: finished);

}

按钮触发TouchContentView_OnTouchActionInvoked事件:

_dispatcherTimer作用是延时1秒,如果按钮被点击,则开始执行后续操作。当按钮被点击时此Timer会开。

一秒后,开始进入“三连模式”,此时更新圆环进度,当进度完成后开始气泡动画和“三连成功”的动画。

若中途按钮被释放,则取消此Timer,并重置圆环进度。

若按钮未进入“三连模式”,则直接播放按钮的点击动画。

private void TouchContentView_OnTouchActionInvoked(object sender, TouchActionEventArgs e)
{
    var layout = ((Microsoft.Maui.Controls.Layout)(sender as TouchContentView).Content).Children;
    var bubbles = layout[0] as Bubbles;
    var circleProgressBar = layout[1] as CircleProgressBar;


    switch (e.Type)
    {
        case TouchActionType.Entered:
            break;
        case TouchActionType.Pressed:
            _dispatcherTimer =Dispatcher.CreateTimer();
            _dispatcherTimer.Interval=new TimeSpan(0, 0, 1);

            _dispatcherTimer.Tick+=   async (o, e) =>
            {
                _isInProgress=true;
                this.UpdateProgressWithAnimate(ProgressBar1, 100, 2*1000, (d, b) =>
                {
                    if (circleProgressBar.Progress==100)
                    {
                        this.bubbles1.StartAnimation();
                        StartCelebrationAnimate(this.Icon1);
                    }
                });
                this.UpdateProgressWithAnimate(ProgressBar2, 100, 2*1000, (d, b) =>
                {
                    if (circleProgressBar.Progress==100)
                    {
                        this.bubbles2.StartAnimation();
                        StartCelebrationAnimate(this.Icon2);
                    }
                });
                this.UpdateProgressWithAnimate(ProgressBar3, 100, 2*1000, (d, b) =>
                {
                    if (circleProgressBar.Progress==100)
                    {
                        this.bubbles3.StartAnimation();
                        StartCelebrationAnimate(this.Icon3);
                    }
                });

            };

            _dispatcherTimer.Start();


            break;
        case TouchActionType.Moved:
            break;
        case TouchActionType.Released:

            if (!_isInProgress)
            {
                var brandColor = (Color)this.Resources["BrandColor"];
                var disabledColor = (Color)this.Resources["DisabledColor"];

                if (circleProgressBar.Progress==100)
                {
                    this.UpdateProgressWithAnimate(ProgressBar1, 0, 1000);
                    this.UpdateProgressWithAnimate(ProgressBar2, 0, 1000);
                    this.UpdateProgressWithAnimate(ProgressBar3, 0, 1000);
                    (ProgressBar1.LabelContent as Path).Fill=disabledColor;
                    (ProgressBar2.LabelContent as Path).Fill=disabledColor;
                    (ProgressBar3.LabelContent as Path).Fill=disabledColor;


                }
                else
                {
                    if (((circleProgressBar.LabelContent as Path).Fill  as SolidColorBrush).Color==disabledColor)
                    {
                        StartCelebrationAnimate(circleProgressBar.LabelContent as Path);

                    }
                    else
                    {
                        (circleProgressBar.LabelContent as Path).Fill=disabledColor;
                    }
                }


            }
            if (_dispatcherTimer!=null)
            {
                if (_dispatcherTimer.IsRunning)
                {
                    _dispatcherTimer.Stop();
                    _isInProgress=false;

                }
                _dispatcherTimer=null;

                if (circleProgressBar.Progress==100)
                {
                    return;
                }

                this.UpdateProgressWithAnimate(ProgressBar1, 0, 1000);
                this.UpdateProgressWithAnimate(ProgressBar2, 0, 1000);
                this.UpdateProgressWithAnimate(ProgressBar3, 0, 1000);
            }



            break;
        case TouchActionType.Exited:
            break;
        case TouchActionType.Cancelled:
            break;
        default:
            break;
    }
}

进入三联模式动画效果如下:
在这里插入图片描述

未进入三联模式动画效果如下:

在这里插入图片描述

最终效果如下:

在这里插入图片描述

项目地址

Github:maui-samples

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/485088.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

力扣98---验证二叉搜索树

题目描述&#xff1a; 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左 子树 只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。 …

C++(类和对象)2

36 友元 1&#xff09;全局函数 全局函数做优元&#xff0c;就是把全局函数复制到类中&#xff0c;加个friend 同上&#xff0c;将class GoodGay前写个friend&#xff0c;就可以访问了 当然&#xff0c;还有成员函数做友元 39 运算符重载-加号 普通加号只知道两个整型撒的…

Rust 程序设计语言学习——结构体

结构体和元组类似&#xff0c;它们都包含多个相关的值。和元组一样&#xff0c;结构体的每一部分可以是不同类型。但不同于元组&#xff0c;结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字&#xff0c;结构体比元组更灵活&#xff1a;不需要依赖顺序来…

macOS访问samba文件夹的正确姿势,在哪里更改“macOS的连接身份“?还真不好找!

环境&#xff1a;路由器上需要身份认证的Mini NAS macOS Sonoma 14 这是一个非常简单的问题&#xff0c;但解决方法却藏得比较深&#xff0c;不够直观&#xff0c;GPT也没有给出明确的解决提示&#xff0c;特意记录一下。 macOS很多地方都很自动&#xff0c;有时候让人找不到设…

DevStack 部署 OpenStack 多节点

DevStack 部署 OpenStack 多节点 DevStack 支持OpenStack多节点部署&#xff0c;下面以一个控制节点和一个计算节点为例&#xff0c;介绍多节点多网卡部署流程。 官方文档&#xff1a; https://docs.openstack.org/devstack/latest/guides/multinode-lab.html https://docs…

202基于matlab的曲柄滑块机构的运动学仿真分析

基于matlab的曲柄滑块机构的运动学仿真分析&#xff0c;分析各个杆的速度、位移、加速度曲线&#xff0c;以及曲柄滑块机构的动画。程序已调通&#xff0c;可直接运行。 202 matlab 曲柄滑块机构 运动学仿真分析 - 小红书 (xiaohongshu.com)

枚举的详解

枚举的讲解 在C语言中&#xff0c;没有内置的枚举&#xff08;enum&#xff09;数据类型&#xff0c;但我们可以使用整数类型来模拟枚举的行为。C99标准之前&#xff0c;C语言使用#define指令来定义枚举&#xff0c;但这种方式并不安全&#xff0c;因为如果枚举值发生变化&…

某名麻将长连接数据解密

点击上方↑↑↑蓝字[协议分析与还原]关注我们 “ 分析金某麻将的长连接数据加密算法。” 近期&#xff0c;不小心碰到了一个棋牌游戏&#xff0c;叫xx麻将&#xff0c;使用长连接进行数据的传输&#xff0c;但三天两头换传输的内容格式&#xff0c;它的编码方式也很有意思&…

中间件设置静态资源目录

文章目录 为什么要设置静态资源目录设置静态资源代码示例 为什么要设置静态资源目录 服务器中的代码&#xff0c;对于外部来说都是不可见的&#xff0c; 所以我们写的html页面&#xff0c;浏览器无法直接访问 如果希望浏览器可以访问&#xff0c;则需要将页面所在的目录设置静…

洛谷day3

B2053 求一元二次方程 - 洛谷 掌握printf用法&#xff1b; #include <iostream> #include <cmath> using namespace std; double a,b,c; double delta; double x1,x2;int main() {cin>>a>>b>>c;delta b*b-4*a*c;if(delta>0){x1 (-bsqrt…

微服务(基础篇-003-Nacos集群搭建)

目录 Nacos集群搭建 1.集群结构图 2.搭建集群 2.1.初始化数据库 2.2.下载nacos 2.3.配置Nacos 2.4.启动 2.5.nginx反向代理 2.6.优化 视频地址&#xff1a; 06-Nacos配置管理-nacos集群搭建_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1LQ4y127n4?p29&…

包子凑数 蓝桥杯

关于这题的数学定理&#xff0c;如果 a,b 均是正整数且互质&#xff0c;那么由 axby&#xff0c;x≥0&#xff0c;y≥0 不能凑出的最大数是 &#xff1a;a*b-a-b 想不起来的时候&#xff0c;把能列出来的数据列出来找规律&#xff0c;不互质得数不符合题目所说 类似于力扣零钱…

Clion使用 - vcpkg

创建空白C项目 引入vcpkg插件 开启清单模式 引入某个库&#xff08;以cjson库为例&#xff09; 在代码中使用某个库&#xff08;以cjson库为例&#xff09;

数据结构和算法:树

二叉树 与链表类似&#xff0c;二叉树的基本单元是节点&#xff0c;每个节点包含值、左子节点引用和右子节点引用。 /* 二叉树节点结构体 */ struct TreeNode {int val; // 节点值TreeNode *left; // 左子节点指针TreeNode *right; // 右子节点指针TreeNode(int x) : val(x),…

uniapp页面嵌套其他页面的实现

功能: 类似于一个drawer&#xff0c;当主页面加载的时候会一并加载url对应的组件&#xff0c;当点击后以drawer形式显示组件里面的内容&#xff0c;可动画。 <navigator url"/pages/my/components/personalMessage" slot"right"><view><di…

阿里云4核8G云服务器优惠价格表,多配置报价

阿里云4核8G云服务器优惠价格955元一年&#xff0c;云服务器ECS通用算力型u1实例。2024年腾讯云服务器优惠价格表&#xff0c;一张表整理阿里云服务器最新报价&#xff0c;阿里云服务器网整理云服务器ECS和轻量应用服务器详细CPU内存、公网带宽和系统盘详细配置报价单&#xff…

【自然语言处理】统计中文分词技术(一):1、分词与频度统计

文章目录 一、词与分词1、词 vs 词素2、世界语言分类 二、分词的原因与基本原因1、为什么要分词2、分词规范3、分词的主要难点-切分歧义如何排除切分歧义利用词法信息利用句法信息利用语义信息利用语用、语境信息 4、分词的主要难点-未登录词未登录词如何识别未登录词 三、分词…

Spark spark-submit 提交应用程序

Spark spark-submit 提交应用程序 Spark支持三种集群管理方式 Standalone—Spark自带的一种集群管理方式&#xff0c;易于构建集群。Apache Mesos—通用的集群管理&#xff0c;可以在其上运行Hadoop MapReduce和一些服务应用。Hadoop YARN—Hadoop2中的资源管理器。 注意&…

力扣3. 无重复字符的最长子串

Problem: 3. 无重复字符的最长子串 文章目录 题目描述思路及解法复杂度Code 题目描述 思路及解法 1.川建一个set集合存储最长的无重复的字符&#xff1b; 2.创建双指针p、q&#xff0c;每次当q指针指向的字符不在set集合中时将其添加到set集合中让q指针后移&#xff0c;并且更新…

【Transformer】transformer注解

every blog every motto: You can do more than you think. 0. 前言 transformer注解 在过去的一年里&#xff0c;《Attention is all you need》中的transformer一直萦绕在很多人的脑海里。除了在翻译质量上产生重大改进之外&#xff0c;它还为许多其他NLP任务提供了一种新的…