14. 利用Canvas自制时钟组件

news2024/9/27 12:00:32

1. 说明

在自定义时钟组件时,使用到的基本控件主要是Canvas,在绘制相关元素时有两种方式:一种时在同一个canvas中绘制所有的部件元素,这样需要不断的对画笔和画布的属性进行保存和恢复,容易混乱;另一种就是创建多个canvas组件,每一部分的元素绘制都在各自的画布进行绘制,逻辑比较清晰,但是canvas组件会相对较多,本文使用的是第二种方式。
效果展示:
在这里插入图片描述

2. 整体代码

import QtQuick 2.15
import QtQuick.Controls 2.15

Item{
    id:root
    implicitWidth: 400
    implicitHeight: implicitWidth

    // 尺寸属性
    property real outerCircleRadius:root.width / 2.05
    property real innerCircleRadius:root.width / 2.05

    // 颜色属性
    property color bgColor:"white"
    property color outerColor:"black"
    property color innerColor:"black"
    property color innerRootColor:"lightSlateGray"
    property color innerLineColorL:"#484D58"
    property color innerLineColorS:"#63677A"
    property color textColor:"black"
    property color hourLineColor:"#484D58"
    property color minuteLineColor:"#484D58"
    property color secondLineColor:"red"
    property color timeTxtColor:"black"

    // 时间属性
    property var hours
    property var minutes
    property var seconds
    property var currentTime
    property alias hoursAngle:hourLine.angle
    property alias minutesAngle:minuteLine.angle
    property alias secondsAngle:secondLine.angle

    // 组件加载完成后先初始化当前时间
    Component.onCompleted: {
        calculateAngle()
    }

    // 时间计算
    function calculateAngle(){
        var date = new Date()
        hours = date.getHours()
        // 模除得到12小时制的小时数
        hours = hours % 12
        minutes = date.getMinutes()
        seconds = date.getUTCSeconds()
        currentTime = hours + ":" + minutes + ":" + seconds
        hoursAngle = Math.PI*2/12*hours+Math.PI*2*minutes/12/60-Math.PI
        minutesAngle = Math.PI*2*minutes/60 + Math.PI*2*seconds/60/60 -Math.PI
        secondsAngle = Math.PI*2*seconds/60-Math.PI
    }

    // 绘制背景
    Canvas{
        id:bgCircle
        width: root.width
        height: root.height
        anchors.centerIn: parent
        onPaint: {
            // 绘制背景
            var ctx = getContext("2d")  
            ctx.save()
            ctx.lineWidth = root.width/50   
            ctx.fillStyle = bgColor
            ctx.beginPath()
            ctx.arc(root.width/2,root.height/2,outerCircleRadius,0,Math.PI * (12/6))
            ctx.fill()
            ctx.restore()
        }
    }
    // 绘制圆环轮廓
    Canvas{
        id:outerCircle
        width: root.width
        height: root.height
        anchors.centerIn: parent
        onPaint: {
            var ctx = getContext("2d")  //创建画师
            //为画师创建画笔并设置画笔属性
            ctx.lineWidth = root.width/50   //设置画笔粗细
            ctx.strokeStyle = outerColor    //设置画笔颜色
            ctx.beginPath()     //每次绘制调用此函数,重新设置一个路径
            // 按照钟表刻度进行划分,一圈是Math.PI * 2,分成12个刻度,每个刻度占用 1/6
            // canvas绘制圆弧,是按照顺时针绘制,起点默认在三点钟方向
            ctx.arc(root.width/2,root.height/2,outerCircleRadius,0,Math.PI * (12/6))
            ctx.stroke()    //根据strokeStyle对边框进行描绘
        }
    }
    // 绘制圆环内衬
    Canvas{
        id:innerCircle
        width: root.width
        height: root.height
        anchors.centerIn: parent
        property real endAngle:Math.PI * (12/6)
        onPaint: {
            var ctx = getContext("2d")  
            ctx.save()
            ctx.lineWidth = root.width/50   
            ctx.strokeStyle = innerColor     
            ctx.beginPath()     
            ctx.arc(root.width/2,root.height/2,innerCircleRadius,0,endAngle)
            ctx.stroke()    
            ctx.restore()

            // 绘制指针根部圆圈
            ctx.save()
            ctx.lineWidth = root.width/50   
            ctx.fillStyle = innerRootColor
            ctx.beginPath()
            ctx.arc(root.width/2,root.height/2,innerCircleRadius/16,0,endAngle)
            ctx.fill()
            ctx.restore()
        }
    }
    // 绘制刻度线
    Canvas{
        id:innerLine
        width: root.width
        height: root.height
        anchors.centerIn: parent
        property real lineNums:60
        onPaint: {
            var ctx = getContext("2d")  
            for (var i = 0; i <= lineNums; ++i){
                ctx.beginPath();
                var angle = 2 * Math.PI / 60 * i;
                var dx = Math.cos(angle)*(outerCircleRadius-15);
                var dy = Math.sin(angle)*(outerCircleRadius-15);
                var dx2 = Math.cos(angle)*(outerCircleRadius-7);
                var dy2 = Math.sin(angle)*(outerCircleRadius-7);
                if (i % 5 === 0){
                    ctx.lineWidth = root.width/100
                    ctx.strokeStyle = innerLineColorL
                }else{
                    ctx.lineWidth = root.width/200
                    ctx.strokeStyle = innerLineColorS
                }
                ctx.moveTo(root.width/2+dx,root.height/2+dy);
                ctx.lineTo(root.width/2+dx2,root.height/2+dy2);
                ctx.stroke();
            }
        }
    }
    // 绘制数字
    Canvas{
        id:drawText
        width: root.width
        height: root.height
        anchors.centerIn: parent
        property var numbers : [1,2,3,4,5,6,7,8,9,10,11,12]
        onPaint: {
            var ctx = getContext("2d")  
            ctx.font = "18px Arial";
            ctx.textAlign = "center";
            ctx.textBaseline = "middle";
            for(var i = 0; i < 12; ++i)
            {
                ctx.fillStyle = textColor
                var angle = 2 * Math.PI / 12 * numbers[i] - 3.14 / 2;
                var dx = Math.cos(angle)*(outerCircleRadius-30);
                var dy = Math.sin(angle)*(outerCircleRadius-30);
                ctx.fillText(numbers[i],root.width/2 + dx,root.height / 2 + dy);
                ctx.fill()
            }
        }
    }
    // 绘制时针线
    Canvas{
        id:hourLine
        width: root.width
        height: root.height
        anchors.centerIn: parent
        property real angle
        onPaint: {
            var ctx = getContext("2d")  
            // 先清空画布上之前的内容
            ctx.clearRect(0,0,width,height)
            ctx.save()
            ctx.beginPath()
            ctx.lineWidth = root.width/100
            ctx.strokeStyle=hourLineColor
            // 平移坐标点(注意:坐标系原点先平移,再旋转)
            ctx.translate(root.width/2,root.height/2)
            // 旋转坐标系
            ctx.rotate(angle)
            // 坐标原点变化之后再进行实际的绘图
            ctx.moveTo(0,-20);
            ctx.lineTo(0,outerCircleRadius / 2 - 15);
            ctx.stroke()
            ctx.restore()
        }
    }
    // 绘制分针线
    Canvas{
        id:minuteLine
        width: root.width
        height: root.height
        anchors.centerIn: parent
        property real angle
        onPaint: {
            var ctx = getContext("2d")  
            ctx.clearRect(0,0,width,height)
            ctx.save()
            ctx.beginPath()
            ctx.lineWidth = root.width/100
            ctx.strokeStyle=minuteLineColor
            // 平移坐标点(注意:坐标系原点先平移,再旋转)
            ctx.translate(root.width/2,root.height/2)
            // 旋转坐标系
            ctx.rotate(angle)
            // 坐标原点变化之后再进行实际的绘图
            ctx.moveTo(0,-25);
            ctx.lineTo(0,outerCircleRadius / 2 - 5);
            ctx.stroke()
            ctx.restore()
        }
    }
    // 绘制秒针线
    Canvas{
        id:secondLine
        width: root.width
        height: root.height
        anchors.centerIn: parent
        property real angle
        onPaint: {
            var ctx = getContext("2d")  
            ctx.clearRect(0,0,width,height)
            ctx.save()
            ctx.beginPath()
            ctx.lineWidth = root.width/100
            ctx.strokeStyle=secondLineColor
            // 平移坐标点(注意:坐标系原点先平移,再旋转)
            ctx.translate(root.width/2,root.height/2)
            // 旋转坐标系
            ctx.rotate(angle)
            // 坐标原点变化之后再进行实际的绘图
            ctx.moveTo(0,-30);
            ctx.lineTo(0,outerCircleRadius / 1.5);
            ctx.stroke()
            ctx.restore()
        }
    }
    // 当前时间显示
    Text{
        id:timeTxt
        y:root.height * 0.75
        anchors.horizontalCenter: root.horizontalCenter
        text: currentTime
        font.pixelSize: root.width/12
        color: timeTxtColor
    }

    // 使用定时器(每秒钟进行计算)
    Timer{
        id:angleCal
        interval: 1000
        repeat: true
        running: true
        onTriggered: {
            calculateAngle()
            hourLine.requestPaint()
            minuteLine.requestPaint()
            secondLine.requestPaint()
        }
    }
}

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

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

相关文章

【C++】做一个飞机空战小游戏(十二)——游戏源代码、音效、图标等资源文件

[导读]本系列博文内容链接如下&#xff1a; 【C】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值 【C】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动【C】做一个飞机空战小游戏(三)——getch()函数控制任意造型飞机图标移动 【C】做一个飞…

基于微信小程序的短文作文写作竞赛管理系统

随着世界经济信息化、全球网络化的到来推动信息线上管理的飞速发展&#xff0c;为短文写作竞赛行业的改革起到关键作用。若想达到安全&#xff0c;快捷的目的&#xff0c;就需要拥有信息化的组织和管理模式&#xff0c;建立一套合理、畅通、高效的短文写作竞赛管理系统小程序。…

js中的正则表达式(一)

目录 1.什么是正则表达式 2.正则表达式在JavaScript中的使用场景: 3.正则表达式的语法&#xff1a; 1.什么是正则表达式 正则表达式(Regular Expression&#xff09;是用于匹配字符串中字符组合的模式。在JavaScript中&#xff0c;正则表达式也是对象通常用来查找、替换那些符…

亚马逊搜索关键词下单怎么操作

亚马逊鲲鹏系统可以根据产品关键词搜索后进行下单购买&#xff0c;多个亚马逊买家号搜索关键词下单可以帮助关键词上首页&#xff0c;具体操作如下&#xff1a; 首先需要先准备好一批能下单的买家账号及代理ip&#xff0c;准备好之后就可以设置需要下单的关键词及asin进行货比…

由于找不到concrt140.dll,无法继续执行代码怎么办

今天我想和大家分享一下我在修复concrt140.dll文件过程中的经验。作为一名程序员&#xff0c;我们经常会遇到各种各样的问题&#xff0c;而这次我所遇到的这个问题对我来说是一个挑战。在经过一番努力之后&#xff0c;我终于找到了解决方法&#xff0c;并且成功地修复了concrt1…

Markdown 基本语法

风无痕 August 21,2023 总览 几乎所有 Markdown 应用程序都支持 John Gruber 原始设计文档中列出的 Markdown 基本语法。但是&#xff0c;Markdown 处理程序之间存在着细微的变化和差异&#xff0c;我们都会尽可能标记出来。 标题&#xff08;Headings&#xff09; 要创建标…

信息安全/网络安全专业好不好?

​本人信息安全专业毕业&#xff0c;在甲方互联网大厂安全部与安全乙方大厂都工作过&#xff0c;有一些经验可以供对安全行业感兴趣的人参考。 或许是因为韩商言让更多人知道了CTF&#xff0c;也或许是因为网络安全越来越受国家重视&#xff0c;安全最近愈加火爆了。当然&…

司徒理财:8.21黄金空头呈阶梯下移!今日操作策略

黄金走势分析 盘面裸k分析&#xff1a;1小时周期的行情局部于1896附近即下行通道上轨附近录得一系列的K线呈震荡下行并筑圆顶&#xff0c;上轨压制有效&#xff0c;下行通道并未突破&#xff0c;后市建议延续看下行。4小时周期局部录得一系列的纺锤线呈震荡&#xff0c;但行情整…

ppt怎么做流程图?一文详解如何在ppt上绘制流程图!

ppt怎么做流程图&#xff1f;ppt制作流程图的方法是什么&#xff1f;本文将介绍2个在PPT中做流程图的方法&#xff0c;一起往下看吧。 方法一&#xff1a;利用PowerPoint自带功能 &#xff08;1&#xff09;使用SmartArt功能制作流程图 打开Microsoft PowerPoint并新建一…

测试Qt是否安装成功

1.打开Qt软件 选择新建项目,new 选择基类时&#xff0c;一次使用选择QWigget空白窗口&#xff0c;还有2种&#xff0c;带菜单栏的窗口和对话框窗口。选中Generate form会生成图形界面&#xff0c;组件直接拖拉&#xff0c;不需要自己编写。 运行结果&#xff1a;

【ICCV2023】Adaptive Frequency Filters As Efficient Global Token Mixers

Adaptive Frequency Filters As Efficient Global Token Mixers 论文&#xff1a;https://arxiv.org/abs/2307.14008 代码&#xff1a;暂未开源 解读&#xff1a;ICCV23&#xff5c;轻量级视觉主干网络AFFNet&#xff1a;频域自适应频段过滤空域全局动态大卷积核 - 知乎 (zh…

热烈祝贺贵州董程酿酒成功入选航天系统采购供应商库

经过航天系统采购平台的严审&#xff0c;贵州董程酿酒有限公司成功入选中国航天系统采购供应商库。航天系统采购平台是航天系统内企业采购专用平台&#xff0c;服务航天全球范围千亿采购需求&#xff0c;目前&#xff0c;已有华为、三一重工、格力电器、科大讯飞等企业、机构加…

基于讯飞开放平台API服务构建集成大模型能力

在前面的一篇文章中实测体验了讯飞开放平台发布的V2.0版本大模型的能力&#xff0c;感兴趣的话可以自行移步阅读即可&#xff1a; 《科大讯飞发布星火认知大模型2.0版——体验实测》 这里一并发布的还有API接口&#xff0c;如下所示&#xff1a; 点击【API测试申请】即可自动…

PHP服饰文化网站系统Dreamweaver开发mysql数据库web结构php编程计算机网页项目

一、源码特点 PHP 服饰文化网站系统是一套完善的web设计系统&#xff0c;对理解php编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 源码下载 https://download.csdn.net/download/qq_41221322/88236778 PHP服饰文化网站…

【哈希表】HashSet HashMap LeetCode习题

目录 136.只出现一次的数字 137.只出现一次的数字 || 217.存在重复元素 219.存在重复元素 || 771.宝石与石头 旧键盘(牛客) 首先需要导包 import java.utli.*; 表中常用的是前两个&#xff0c;时间复杂度低。O(1) Set<E> set new HashSet<>(); set.conta…

使用VSCode配置简单的vue项目

由于最近要使用的项目框架为前后端分离的&#xff0c;采用的是vue.jswebAPI的形式进行开发的。因为之前我没有接触过vue.js&#xff0c;也只是通过视频文档做了一些简单的练习。今天技术主管说让大家熟悉下VSCode开发vue&#xff0c;所以自己摸索了好久&#xff0c;才算是把简单…

2023年7月京东扫地机器人行业品牌销售排行榜(京东数据产品)

伴随消费者解放双手的消费需求&#xff0c;我国清洁电器行业快速发展。且随着消费回暖&#xff0c;我国扫地机器人市场也开始慢慢走出寒冬&#xff0c;止跌回升。 根据鲸参谋电商数据分析平台的相关数据显示&#xff0c;今年7月份&#xff0c;京东平台扫地机器人的销量为16万&…

正中优配:港股迎来估值“黄金坑” 哪个点位介入胜率高?

近两周&#xff0c;港股向下突破了一切技能短线支撑信号。在新的技能信号构成前&#xff0c;港股有望迎来估值压力的开释&#xff0c;带来一个估值“黄金坑”。当估值回到“轻视”区间时&#xff0c;咱们信任港股对全球资本的吸引力将再次凸显。详细而言&#xff0c;咱们用两种…

eDP接口的PCB布局布线要求

eDP接口是一种基于DisplayPort架构和协议的一种全数字化接口&#xff0c;传递高分辨率信号只需要较简单的连接器以及较少的引脚就可以实现&#xff0c;同时还能够实现多数据同时传输。 图1 EDP接口 eDP接口的PCB设计布局布线注意事项&#xff1a; 1、远离干扰源&#xff0c;防…

让eslint的错误信息显示在项目界面上

1.需求描述 效果如下 让eslint中的错误&#xff0c;显示在项目界面上 2.问题解决 1.安装 vite-plugin-eslint 插件 npm install vite-plugin-eslint --save-dev2.配置插件 // vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue import e…