[MAUI]用纯C#代码写两个漂亮的时钟

news2024/9/21 5:38:04

文章目录

  • 时钟1
    • 绘制锯齿表盘
    • 绘制指针
    • 绘制沿路径文本
  • 时钟2
    • 绘制表盘
    • 绘制指针
    • 项目地址

谷歌在2021年5月份推出的Android 12给我们带来了新的UI设计规范Material You,你是否已经体验到了Material You设计的魅力了呢?

在原生主屏幕启动器中,有一个时钟小部件。这个小部件可以选择表盘风格。

在这里插入图片描述

图:Android 12的时钟小部件

今天挑战在.NET MAU中实现这个Material You风格时钟。

最终效果如下:

在这里插入图片描述

时钟1

绘制锯齿表盘

锯齿表盘是正玄波曲线闭合成一个圆形。
创建Clock1,打开Xaml文件

在页面布局中创建一个Path对象,设置Stroke和Fill属性。

<Path Grid.Row="0"
        Grid.Column="1"
        Stroke="white"
        Fill="#FFEED9"
        IsVisible="true"
        x:Name="ModulatedPath">
    <Path.Data>
        <PathGeometry>
            <PathGeometry.Figures>
                <PathFigureCollection>
                    <PathFigure IsClosed="True" x:Name="MainPathFigure">
                        <PathFigure.Segments>
                            <PathSegmentCollection x:Name="MainPathSegmentCollection">

                            </PathSegmentCollection>
                        </PathFigure.Segments>
                    </PathFigure>

                </PathFigureCollection>
            </PathGeometry.Figures>
        </PathGeometry>
    </Path.Data>
</Path>

在codebehind中,订阅SizeChanged事件,当控件大小发生变化时,重新绘制表盘。

public Clock1()
{
    InitializeComponent();
    this.SizeChanged+=ContentView_SizeChanged;
}

正弦曲线绘制原理

单位圆中的正弦函数在平面直角坐标系中可以映射为一个波形曲线,下图所示在0-2π范围中y=sin(x)的图像。

在这里插入图片描述

其中最低点和最高点决定了波形的振幅,他们与平衡点的距离即单位圆的半径

设置变量 r为平衡点,r2为最高点,r3为最低点。

centerX和centerY为圆心坐标。
segemts为绘制的线段数,越大锯齿越密集。

private void ContentView_SizeChanged(object sender, EventArgs e)
{

     var length = (float)Math.Min(this.Width, this.Height) * 0.95;
    var centerX = (float)this.Width / 2;
    var centerY = (float)this.Height / 2;
    var points = new List<Point>();
    var r = length / 2;
    var r2 = r * 1.1;
    var r3 = r * 0.9;
    var index = 0;
    var segments = 40;
}

首先计算各平衡点在圆周上的离散分布坐标(x,y)

根据index的奇偶性,给与当前点最高点或最低点的半径。

根据各分配的半径计算调整后的离散点坐标

在这里插入图片描述

代码如下:

...
for (var i = 0; i < segments; i += 2)
{
    var x = r * Math.Cos(i * 2 * Math.PI / segments) + centerX;
    var y = r * Math.Sin(i * 2 * Math.PI / segments) + centerY;

    points.Add(new Point((float)x, (float)y));
    var currentR = index++ % 2 == 0 ? r2 : r3;
    x = currentR * Math.Cos((i + 1) * 2 * Math.PI / segments) + centerX;
    y = currentR * Math.Sin((i + 1) * 2 * Math.PI / segments) + centerY;

    points.Add(new Point((float)x, (float)y));
}

如此,我们得到一个闭合的锯齿圆形表盘

在这里插入图片描述

绘制指针

在表盘上绘制时钟指针,需要计算时针、分针、秒针的角度,然后根据角度旋转画布,绘制指针。

秒针每秒钟转动6度,

分针每分钟转动6度,并叠加每秒0.1度。

时针每小时转动30度。并叠加每分钟0.5度。

其中时针和分针由宽度为15的实心填充圆角线条构成

DateTime dateTime = DateTime.Now;

// Hour hand
strokePaint.Color = SKColor.Parse("#5E4000");
strokePaint.StrokeWidth = 15;
canvas.Save();
canvas.RotateDegrees(30 * dateTime.Hour + dateTime.Minute / 2f);
canvas.DrawLine(0, 0, 0, -r*(float)0.4, strokePaint);
canvas.Restore();

// Minute hand
strokePaint.Color = SKColor.Parse("#9C6D00");
canvas.Save();
canvas.RotateDegrees(6 * dateTime.Minute + dateTime.Second / 10f);
canvas.DrawLine(0, 0, 0, -r*(float)0.8, strokePaint);
canvas.Restore();

// Second hand
strokePaint.Color = SKColor.Parse("#657E3F");
canvas.Save();
canvas.RotateDegrees(6 * dateTime.Second);
strokePaint.StrokeWidth *=(float)0.5;
strokePaint.Style=SKPaintStyle.Fill;
canvas.DrawCircle(0, -r*(float)0.8, strokePaint.StrokeWidth, strokePaint);
canvas.Restore();

指针效果如下:

在这里插入图片描述

其中秒针需要绘制一个点,在其以圆心为中心的对侧绘制一个带有日期的文本

绘制沿路径文本

首先绘制文本路径,它是一个圆弧,在初始状态圆弧的角度为20度,圆弧的起始角度为70度,终止角度为110度。

var pathAngle = 20;
var startAngle = 90-pathAngle;
var sweepAngle = pathAngle*2;
var rect = new SKRect(-r*(float)0.8, -r*(float)0.8, r*(float)0.8, r*(float)0.8);

使用SkiaSharp的DrawTextOnPath方法绘制沿路径的文本,详情请查看官方文档

using (SKPath path = new SKPath())
{
    path.AddArc(rect, startAngle, sweepAngle);
    canvas.DrawTextOnPath(dateStr, path, new SKPoint(), strokePaint);
}

指针效果如下:

在这里插入图片描述

时钟1的完整代码如下:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    using (SKPaint strokePaint = new SKPaint())
    using (SKPaint fillPaint = new SKPaint())
    {
        strokePaint.Style = SKPaintStyle.Stroke;
        strokePaint.StrokeCap = SKStrokeCap.Round;

        fillPaint.Style = SKPaintStyle.Fill;
        fillPaint.Color = SKColors.Transparent;

        // Transform for 100-radius circle centered at origin
        var r = 100f;
        canvas.Translate(info.Width / 2f, info.Height / 2f);
        canvas.Scale(Math.Min(info.Width / 200f, info.Height / 200f));


        DateTime dateTime = DateTime.Now;

        // Hour hand
        strokePaint.Color = SKColor.Parse("#5E4000");
        strokePaint.StrokeWidth = 15;
        canvas.Save();
        canvas.RotateDegrees(30 * dateTime.Hour + dateTime.Minute / 2f);
        canvas.DrawLine(0, 0, 0, -r*(float)0.4, strokePaint);
        canvas.Restore();

        // Minute hand
        strokePaint.Color = SKColor.Parse("#9C6D00");
        canvas.Save();
        canvas.RotateDegrees(6 * dateTime.Minute + dateTime.Second / 10f);
        canvas.DrawLine(0, 0, 0, -r*(float)0.62, strokePaint);
        canvas.Restore();

        // Second hand
        strokePaint.Color = SKColor.Parse("#657E3F");
        canvas.Save();
        canvas.RotateDegrees(6 * dateTime.Second);
        strokePaint.StrokeWidth *=(float)0.5;
        strokePaint.Style=SKPaintStyle.Fill;

        canvas.DrawCircle(0, -r*(float)0.8, strokePaint.StrokeWidth, strokePaint);

        strokePaint.Color = SKColors.Black;
        strokePaint.StrokeWidth = 1;
        var dateStr = dateTime.ToString("M");
        var pathAngle = 20;
        var startAngle = 90-pathAngle;
        var sweepAngle = pathAngle*2;
        var rect = new SKRect(-r*(float)0.8, -r*(float)0.8, r*(float)0.8, r*(float)0.8);

        using (SKPath path = new SKPath())
        {
            path.AddArc(rect, startAngle, sweepAngle);

            //canvas.DrawPath(path, strokePaint);

            canvas.DrawTextOnPath(dateStr, path, new SKPoint(), strokePaint);
        }


        canvas.Restore();
    }
}


配置一个定时器,每秒刷新各指针位置

IDispatcherTimer _timer;

public Clock1()
{
   
    ...
    this.SizeChanged+=ContentView_SizeChanged;
    _timer=  Dispatcher.CreateTimer();
    _timer.Interval=TimeSpan.FromSeconds(1);
    _timer.Tick+=_timer_Tick;
    _timer.Start();
}

private void _timer_Tick(object sender, EventArgs e)
{
    this.canvasView?.InvalidateSurface();
}

时钟1 效果如下:

在这里插入图片描述

时钟2

URWGeometricBlack字体文件放到Fonts目录下

在这里插入图片描述

在MauiProgram.cs中注册字体

.ConfigureFonts(fonts =>
{
    ...
    fonts.AddFont("URWGeometricBlack.otf", "URWGeometricBlack");

});

绘制表盘

时钟2的表盘相对简单,是一个简单的圆配简洁抽象的数字刻度组成
创建Clock2,打开Xaml文件,代码如下:

<Grid>

    <Ellipse Grid.Row="0"
                Grid.Column="1"
                Stroke="white"
                Fill="#FFEED9"
                IsVisible="true"
                HeightRequest="200"
                WidthRequest="200"
                x:Name="ModulatedPath">
    </Ellipse>
    <Grid TranslationY="-15">
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Label   Grid.Row="0"
                    Grid.ColumnSpan="2"
                    Text="12"
                    Style="{StaticResource ClockPlateNumberLabelStyle}"></Label>

        <Label   Grid.RowSpan="2"
                    Grid.Column="1"
                    Text="3"
                    Style="{StaticResource ClockPlateNumberLabelStyle}"></Label>

        <Label   Grid.Row="1"
                    Grid.ColumnSpan="2"
                    Text="6"
                    Style="{StaticResource ClockPlateNumberLabelStyle}"></Label>

        <Label   Grid.RowSpan="2"
                    Grid.Column="0"
                    Text="9"
                    Style="{StaticResource ClockPlateNumberLabelStyle}"></Label>

    </Grid>



    <forms:SKCanvasView x:Name="canvasView"
                        PaintSurface="OnCanvasViewPaintSurface" />

    <Label FontSize="28"
            HorizontalOptions="Center"
            VerticalOptions="Center"
            x:Name="labelView"></Label>
</Grid>

同样我们需要放置SKCanvasView对象用于绘制指针

其中ClockPlateNumberLabelStyle定义如下:

<Style TargetType="Label"
        x:Key="ClockPlateNumberLabelStyle">
    <Setter Property="HorizontalTextAlignment"
            Value="Center"></Setter>
    <Setter Property="VerticalTextAlignment"
            Value="Center"></Setter>
    <Setter Property="VerticalOptions"
            Value="Center"></Setter>
    <Setter Property="FontAttributes"
            Value="Bold"></Setter>
    <Setter Property="FontSize"
            Value="120"></Setter>
    <Setter Property="TextColor"
            Value="#F9BC49"></Setter>
    <Setter Property="FontFamily"
            Value="URWGeometricBlack"></Setter>
</Style>

效果如下:

在这里插入图片描述

绘制指针

时钟2的指针绘制原理与时钟1类似,此处将不赘述,完整代码如下:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    using (SKPaint strokePaint = new SKPaint())
    using (SKPaint fillPaint = new SKPaint())
    {
        strokePaint.Style = SKPaintStyle.Stroke;
        strokePaint.StrokeCap = SKStrokeCap.Round;

        fillPaint.Style = SKPaintStyle.Fill;
        fillPaint.Color = SKColors.Transparent;

        // Transform for 100-radius circle centered at origin
        var r = 100f;
        canvas.Translate(info.Width / 2f, info.Height / 2f);
        canvas.Scale(Math.Min(info.Width / 200f, info.Height / 200f));


        DateTime dateTime = DateTime.Now;

        // Hour hand
        strokePaint.Color = SKColor.Parse("#5E4000");
        strokePaint.StrokeWidth = 15;
        canvas.Save();
        canvas.RotateDegrees(30 * dateTime.Hour + dateTime.Minute / 2f);
        canvas.DrawLine(0, 0, 0, -r*(float)0.4, strokePaint);
        canvas.Restore();

        // Minute hand
        strokePaint.Color = SKColor.Parse("#9C6D00");
        strokePaint.StrokeWidth = 5;
        canvas.Save();
        canvas.RotateDegrees(6 * dateTime.Minute + dateTime.Second / 10f);
        canvas.DrawLine(0, 0, 0, -r*(float)0.8, strokePaint);
        canvas.Restore();

        // Second hand
        strokePaint.Color = SKColor.Parse("#657E3F");
        strokePaint.StrokeWidth = 2;
        canvas.Save();
        canvas.RotateDegrees(6 * dateTime.Second);
        canvas.DrawLine(0, r*(float)0.1, 0, -r*(float)0.8, strokePaint);
        strokePaint.Style=SKPaintStyle.Fill;
        canvas.DrawCircle(0, 0, 5, strokePaint);

        canvas.Restore();
    }
}

时钟2 效果如下

在这里插入图片描述

项目地址

Github:maui-samples

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【杂谈理解】STM32F103引脚反转速度时间

前言 事情的起因是最近我在使用F103编写WS2812彩灯的时序时遇到了一些小问题。具体来说&#xff0c;我发现WS2812的1码的低电平部分的时序无法达到要求的220ns~380ns&#xff0c;而只能到大约580ns左右。因此&#xff0c;我开始对引脚的反转速度进行了一些测试。下面数据仅是我…

Docker一键安装个人基础开发环境-MySQL、Redis、MongoDB

Docker Compose 在个人开发环境中&#xff0c;使用 Docker Compose 可以极大地简化和优化应用程序的部署和管理过程。 Docker Compose 的配置文件通常采用 YAML 格式&#xff0c;使其易于阅读和编写。我们可以在配置文件中指定所需的容器镜像、端口映射、环境变量、数据卷挂载…

张量分解中Tucker分解和CP分解的区别与联系

CP分解图示&#xff1a; Tucker分解图示&#xff1a; 两者的区别如下&#xff1a; 主要区别&#xff1a;核张量&#xff08;core tensor&#xff09; Tucker分解的结果会形成一个核张量&#xff0c;即PCA中的主成分因子&#xff0c;来表示原张量的主要性质&#xff0c;而CP分解…

【添加anaconda环境变量】

Win11操作系统&#xff0c;首先进入到编辑环境变量界面 分别添加以下这三个&#xff0c;保存。 最后关闭pycharm,重启&#xff0c;打开Terminal&#xff0c;输入conda --version&#xff0c;成功&#xff01;

Linux删除文件夹时,报错“Error: EBUSY:resource busy or locked, unlink……”该如何解决?

目录 问题描述&#xff1a; 问题解决&#xff1a; 问题描述&#xff1a; 在linux上&#xff0c;想要删除一个文件夹&#xff0c;总是报错如下图&#xff0c;一直删除不掉。明明感觉自己并没有执行这个文件夹下的内容&#xff0c;却一直删除失败。 问题解决&#xff1a; 不得不…

如何在小程序中引入使用vant框架

一、vant框架 vantUI框架常用于移动端页面组件的基础库构建&#xff0c;为了让用户获得更趋向于原生的体验&#xff0c;它是一种相当不错的方案选择。 关于这个框架&#xff0c;它不仅有适用于移动端vue脚手架的版本&#xff0c;同时还存在可以兼容小程序开发的webapp版本。在…

业务逻辑一定要紧密执行,希望大家引以为戒!——记一次惨痛的教训【伸手党福利】【又是贷款上班的一天】

切记一定要先执行业务逻辑再进行其他操作&#xff01; 程序员的教训 问题现象 后台传来报警&#xff0c;充值平台余额不足&#xff0c;发现大量充值记录。 分析 先看一下代码&#xff1a; 发起http的post请求&#xff0c;然后将返回体显示&#xff0c;并用logs记入日志&a…

STM32F4_nRF24L01无线通讯

目录 前言&#xff1a; 1. nRF24L01无线模块简介 2. nRF24L01状态机 3. nRF24L01模式 4. nRF24L01的SPI配置 4.1 nRF24L01 Rx 和 Tx 的初始化配置 4.2 nRF24L01相关寄存器 5. 硬件连接 6. 实验程序 6.1 main.c 6.2 NRF24L01.c 6.3 NRF24L01.h 前言&#xff1a; S…

10-切片是什么?【视频版】

目录 问题视频解答 问题 视频解答 点击观看&#xff1a; 10-切片是什么&#xff1f;

主成分分析系列(二)为何特征向量是主成分

在主成分分析系列&#xff08;一&#xff09;概览及数据为何要中心化这篇文章中介绍了PCA算法的大概想法及数据为何要中心化&#xff0c;在这篇文章具体推导PCA算法的过程。 1. 首先 PCA 最原始的想法是&#xff1a; 设 V \mathbf{V} V 为 d {d} d 维 线性空间&#xff08;即…

单表-DQL

注意&#xff1a;这张图还包含了对于的顺序&#xff0c;先分组再排序&#xff0c;再分页&#xff0c;顺序不能乱 基本查询 # 1.基本查询 # 查询全部行 select * from tb_emp; select id, user_name, password, name, gender, image, job, entry_date, create_time, update_ti…

成本降低60%至70%?中国展现顶级电池技术,锂电就是下一个铅酸

在3月份&#xff0c;宁德时代宣布加速推进钠离子电池产业化&#xff0c;以降低成本并提供差异化产品和技术&#xff0c;帮助客户提升产品竞争力和占据更大市场份额。孚能科技已在上半年开始批量生产钠离子电池&#xff0c;而拓邦股份也在最近的国际电池技术展上发布了自家的钠离…

二、逻辑回归

二、逻辑回归1.线性回归2.分类问题1&#xff09;二分类2&#xff09;多分类 3.逻辑回归模型简介1&#xff09;逻辑回归背景2&#xff09;逻辑回归主要基于以下三个目的3&#xff09;优缺点 4.逻辑回归原理1&#xff09;构造函数&#xff08;Sigmoid函数&#xff09;2&#xff0…

阿里远程仓库环境安装

自己是做前端开发的&#xff0c;自己薅的阿里的三个月的免费的服务器&#xff0c;练练手。在安装环境的时候遇到一些坑&#xff0c;记录下来 获取到服务器之后我是通过Royal TSX工具远程连接的&#xff0c;作为一个前端可能想到先装一些基本的命令 介绍一些基本的命令&#xff…

Git学习笔记(二)

导航小助手 四、分支管理 4.1 管理分支 4.2 创建分支、切换分支、合并分支 4.3 删除分支 4.4 合并冲突 4.5 分支管理策略 4.5.1 分支策略 4.6 bug分支 4.7 删除临时分支 四、分支管理 4.1 管理分支 现在介绍一下Git的杀手级别功能之一&#xff1a;分支~ 理解分支&a…

解决:前端请求跨域问题

解决&#xff1a;前端请求跨域问题 问题一&#xff1a;Access to XMLHttpRequest at https://xxx/ICN40310000075_1687926884828.pdf from origin http://localhost:63342 has been blocked by CORS policy: No Access-Control-Allow-Origin header is present on the requeste…

valgrind检测内存泄漏、越界访问、野指针访问实验

前言 本次测试包括&#xff0c;检测无误的代码&#xff0c;检测内存泄漏&#xff0c;检测访问越界&#xff0c;检测野指针&#xff0c;检测访问已经释放(已经被free)的内存。 一 安装valgrind sudo apt install valgrind 二 无错误 #include <stdio.h> #include <…

leetcode:836. 矩形重叠(python3解法)

难度&#xff1a;简单 矩形以列表 [x1, y1, x2, y2] 的形式表示&#xff0c;其中 (x1, y1) 为左下角的坐标&#xff0c;(x2, y2) 是右上角的坐标。矩形的上下边平行于 x 轴&#xff0c;左右边平行于 y 轴。 如果相交的面积为 正 &#xff0c;则称两矩形重叠。需要明确的是&…

在图像上绘制图形

1.绘制直线 cv.line(img, (0, 0), (511, 511), (255, 0, 0), 5)2.绘制圆形 cv.circle(img, (256, 256), 60, (0, 0, 255), -1) 3.绘制矩形 cv.rectangle(img, (100, 100), (400, 400), (0, 255, 0), 5)4.向图片中添加文字 cv.putText(img, "hello", (100, 150), cv.F…

google浏览器如何把 develop tools 弹窗设置为一个单独界面

更改默认的弹出式窗口和重定向设置 在计算机上打开 Chrome。在右上角&#xff0c;依次点击“更多”图标 设置。依次点击隐私设置和安全性 网站设置 弹出式窗口和重定向。选择要设为默认设置的选项。 管理特定网站的弹出式窗口和重定向 https://support.google.com/ch…