未验证 提交 bb61ced0 编写于 作者: O openharmony_ci 提交者: Gitee

!18499 新增案例【不需要翻译】

Merge pull request !18499 from duangavin123/master
......@@ -17,6 +17,9 @@
- [如何实现抽屉式导航](navigation-drawer.md)
- [如何实现内容下拉变化](content-changing-with-pulldown.md)
- [如何实现圆形进度条](circle-progress-bar.md)
- [如何实现列表的二级联动](interact-lists.md)
- [时钟开发示例](time-styles-shift.md)
- [弹簧动画开发](how-to-develop-spring-animation.md)
### 网络管理
- [如何请求并加载网络图片](how-to-load-images-from-internet.md)
......
# 如何使用弹簧动画曲线
## 场景说明
在动画开发场景中,经常用到弹性效果,尤其在拖拽某个对象时经常伴随弹性动效。OpenHarmony提供了三种弹簧动画曲线用来实现弹性效果,本例将为大家介绍这三种曲线的用法。
## 效果呈现
本例最终效果如下:
![springanimation](figures/springanimation.gif)
## 运行环境
本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发:
- IDE: DevEco Studio 3.1 Beta2
- SDK: Ohos_sdk_public 3.2.11.9(API Version 9 Release)
## 实现思路
本例主要用到以下三种弹簧动画曲线:
- [curves.springCurve](../application-dev/reference/apis/js-apis-curve.md#curvesspringcurve9):通过设置弹簧的初始速度、质量、刚度和阻尼来控制弹簧动画的效果。对应本例中springCurve按钮触发的动画。
- [curves.springMotion](../application-dev/reference/apis/js-apis-curve.md#curvesspringmotion9):通过设置弹簧震动时间和阻尼来控制弹簧动画的效果。对应本例中springMotion按钮触发的动画。
- [curves.responsiveSpringMotion](../application-dev/reference/apis/js-apis-curve.md#curvesresponsivespringmotion9):构造弹性跟手动画曲线对象,是springMotion的一种特例,仅默认参数不同,可与springMotion混合使用。用来实现拖拽动画。
## 开发步骤
1. 搭建UI框架。
样例中有两个按钮,一个图片。内容整体纵向分布,两个按钮横向分布。纵向布局可以采用Column组件,横向布局可以采用Row组件。代码如下:
```ts
@Entry
@Component
struct ImageComponent {
build() {
Column() {
Row() {
Button('springCurve')
.margin({right:10})
.fontSize(20)
.backgroundColor('#18183C')
Button('springMotion')
.fontSize(20)
.backgroundColor('#18183C')
}
.margin({top:30})
Image($r("app.media.contact2"))
.width(100)
.height(100)
}.width("100%").height("100%").backgroundColor('#A4AE77')
}
}
```
2. 为springCurve按钮添加curves.springCurve的曲线动画。
```ts
...
// 定义状态变量translateY,用来控制笑脸图像的位移
@State translateY: number = 0
...
Button('springCurve')
.margin({right:10})
.fontSize(20)
.backgroundColor('#18183C')
// 绑定点击事件
.onClick(() => {
// 在点击事件中添加显示动画
animateTo({
duration: 2000,
// 设定curves.springCurve为动画曲线
curve: curves.springCurve(100, 10, 80, 10)
},
() => {
// 改变translateY的值,使笑脸图像发生位移
this.translateY = -20
})
this.translateY = 0
})
...
Image($r("app.media.contact2"))
.width(100)
.height(100)
// 为笑脸图像添加位移属性,以translateY为参数
.translate({ y: this.translateY })
...
```
效果如下:
![springCurve](figures/springCurve.gif)
3. 为springMotion按钮添加curves.springMotion曲线动画。
这里通过position属性控制springMotion按钮的移动,当然开发者也可以继续选择使用translate属性。
```ts
...
// 定义状态变量translateY,用来控制笑脸图像的位置变化
@State imgPos: {
x: number,
y: number
} = { x: 125, y: 400 }
...
Button('springMotion')
.fontSize(20)
.backgroundColor('#18183C')
// 绑定点击事件
.onClick(() => {
// 在点击事件中添加显示动画
animateTo({
duration: 15,
//设定curves.springMotion为动画曲线
curve: curves.springMotion(0.5, 0.5),
onFinish: () => {
animateTo({ duration: 500,
curve: curves.springMotion(0.5, 0.5), }, () => {
// 动画结束时笑脸图像位置还原
this.imgPos = { x: 125, y: 400 }
})
}
}, () => {
// 改变笑脸图像位置,y轴位置由400,变为150
this.imgPos = { x: 125, y: 150 }
})
})
...
Image($r("app.media.contact2"))
.width(100)
.height(100)
.translate({ y: this.translateY })
// 为笑脸图像添加位置属性,以imgPos为参数
.position(this.imgPos)
...
```
效果如下:
![springmotion](figures/springmotion.gif)
4. 使用curves.responsiveSpringMotion为笑脸图像添加拖拽动画。
```ts
...
Image($r("app.media.contact2"))
.width(100)
.height(100)
.translate({ y: this.translateY })
.position(this.imgPos)
// 绑定触摸事件
.onTouch((event: TouchEvent) => {
// 当触摸放开时,笑脸图像位置还原
if (event.type == TouchType.Up) {
animateTo({
duration: 50,
delay: 0,
curve: curves.springMotion(),
onFinish: () => {
}
}, () => {
this.imgPos = { x: 125, y: 400 }
})
} else {
// 触摸过程中触发跟手动画
animateTo({
duration: 50,
delay: 0,
//设定跟手动画曲线
curve: curves.responsiveSpringMotion(),
onFinish: () => {
}
}, () => {
// 根据触点位置改变笑脸图像位置,从而实现跟手动画
this.imgPos = {
x: event.touches[0].screenX - 100 / 2,
y: event.touches[0].screenY - 100 / 2
}
})
}
})
...
```
效果如下:
![responsivemotion](figures/responsivemotion.gif)
## 完整代码
本例完整代码如下:
```ts
import curves from '@ohos.curves';
@Entry
@Component
struct ImageComponent {
// 定义状态变量translateY,用来控制笑脸图像的位移
@State translateY: number = 0
// 定义状态变量translateY,用来控制笑脸图像的位置变化
@State imgPos: {
x: number,
y: number
} = { x: 125, y: 400 }
build() {
Column() {
Row() {
Button('springCurve')
.margin({right:10})
.fontSize(20)
.backgroundColor('#18183C')
// 绑定点击事件
.onClick(() => {
// 在点击事件中添加显示动画
animateTo({
duration: 2000,
// 设定curves.springCurve为动画曲线
curve: curves.springCurve(100, 10, 80, 10)
},
() => {
// 改变translateY的值,使笑脸图像发生位移
this.translateY = -20
})
this.translateY = 0
})
Button('springMotion')
.fontSize(20)
.backgroundColor('#18183C')
// 绑定点击事件
.onClick(() => {
// 在点击事件中添加显示动画
animateTo({
duration: 15,
//设定curves.springMotion为动画曲线
curve: curves.springMotion(0.5, 0.5),
onFinish: () => {
animateTo({ duration: 500,
curve: curves.springMotion(0.5, 0.5), }, () => {
// 动画结束时笑脸图像位置还原
this.imgPos = { x: 125, y: 400 }
})
}
}, () => {
// 改变笑脸图像位置,y轴位置由400,变为150
this.imgPos = { x: 125, y: 150 }
})
})
}
.margin({top:30})
Image($r("app.media.contact2"))
.width(100)
.height(100)
// 为笑脸图像添加位移属性,以translateY为参数
.translate({ y: this.translateY })
// 为笑脸图像添加位置属性,以imgPos为参数
.position(this.imgPos)
// 绑定触摸事件
.onTouch((event: TouchEvent) => {
// 当触摸放开时,笑脸图像位置还原
if (event.type == TouchType.Up) {
animateTo({
duration: 50,
delay: 0,
curve: curves.springMotion(),
onFinish: () => {
}
}, () => {
this.imgPos = { x: 125, y: 400 }
})
} else {
// 触摸过程中触发跟手动画,同样通过animateTo实现动画效果
animateTo({
duration: 50,
delay: 0,
//设定跟手动画曲线
curve: curves.responsiveSpringMotion(),
onFinish: () => {
}
}, () => {
// 根据触点位置改变笑脸图像位置,从而实现跟手动画
this.imgPos = {
x: event.touches[0].screenX - 100 / 2,
y: event.touches[0].screenY - 100 / 2
}
})
}
})
}.width("100%").height("100%").backgroundColor('#A4AE77')
}
}
```
## 参考
- [显示动画](../application-dev/reference/arkui-ts/ts-explicit-animation.md)
- [插值计算](../application-dev/reference/apis/js-apis-curve.md)
\ No newline at end of file
# 二级联动
## 场景介绍
列表的二级联动(Cascading List)是指根据一个列表(一级列表)的选择结果,来更新另一个列表(二级列表)的选项。这种联动可以使用户根据实际需求,快速定位到想要的选项,提高交互体验。例如,短视频中拍摄风格的选择、照片编辑时的场景的选择,本文即为大家介绍如何开发二级联动。
## 效果呈现
本例最终效果如下:
![](figures/secondarylinkage.gif)
## 运行环境
本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发:
- IDE: DevEco Studio 3.1 Beta2
- SDK: Ohos_sdk_public 3.2.11.9 (API Version 9 Release)
## 实现思路
- 数字标题(titles)以及下方的数字列表(contents)分组展示:通过两个List组件分别承载数字标题和数字项。
- 滚动数字列表,上方数字标题也随之变动:通过List组件的onScrollIndex事件获取到当前滚动数字的索引,根据该索引计算出对应标题数字的索引,然后通过Scroller的scrollToIndex方法跳转到对应的数字标题,且通过Line组件为选中的标题添加下划线。
- 点击数字标题,下方的数字列表也随之变化:首先获取到点击数字标题的索引,通过该索引计算出下方对应数字的起始项索引,然后通过scroller的scrollToIndex方法跳转到对应索引的数字项。
## 开发步骤
根据实现思路,具体实现步骤如下:
1. 首先构建列表数据,在records中记录数字列表中各个数字的首项索引值,具体代码块如下:
```ts
...
@State typeIndex: number = 0
private tmp: number = 0
private titles: Array<string> = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
private contents: Array<string> = ["1", "1", "1", "1", "1", "1", "1", "1", "1", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "3"
, "3", "3", "3", "3", "4", "4", "4", "5", "5", "5", "5", "5", "6", "7", "7", "7", "7", "7", "7", "7", "7", "7", "7", "7", "7",
"8", "8", "8", "8", "8", "9", "9", "9", "9", "9", "9", "9", "9", "9", "9", "9"]
private records: Array<number> = [0, 9, 21, 26, 29, 34, 35, 47, 52, 63]
private classifyScroller: Scroller = new Scroller();
private scroller: Scroller = new Scroller();
...
```
数字标题列表:具体代码块如下:
```ts
...
build() {
Column({ space: 0 }) {
List ({ space: 50, scroller: this.classifyScroller, initialIndex: 0 }) {
ForEach(this.titles, (item, index) => {
ListItem() {
Column() {
Text(item)
.fontSize(14)
...
}
}
}
...
}
.listDirection(Axis.Horizontal)
.height(50)
}
}
```
数字列表,具体代码块如下:
```ts
List({ space: 20, scroller: this.scroller }) {
ForEach(this.contents, (item, index) => {
ListItem() {
Column({ space: 5 }) {
Image($r("app.media.app_icon"))
.width(40)
.height(40)
Text(item)
.fontSize(12)
}
...
}
}
}
.listDirection(Axis.Horizontal) //列表排列方向水平
.edgeEffect(EdgeEffect.None) //不支持滑动效果
```
2. 数字标题的索引值判断,根据当前滚动数字的首项索引值计算出数字标题的索引,具体代码块如下:
```ts
...
findClassIndex(ind: number) { // 当前界面最左边图的索引值ind
let ans = 0
// 定义一个i 并进行遍历 this.records.length = 10
for (let i = 0; i < this.records.length; i++) {
// 判断ind在this.records中那两个临近索引值之间
if (ind >= this.records[i] && ind < this.records[i + 1]) {
ans = i
break
}
}
return ans
}
findItemIndex(ind: number) {
// 将ind重新赋值给类型标题列表的索引值
return this.records[ind]
}
...
```
通过Line组件构成标题下滑线,具体代码块如下:
```ts
...
if (this.typeIndex == index) {
Line()
//根据长短判断下划线
.width(item.length === 2 ? 25 : item.length === 3 ? 35 : 50)
.height(3)
.strokeWidth(20)
.strokeLineCap(LineCapStyle.Round)
.backgroundColor('#ffcf9861')
}
...
```
3. 点击数字标题,数字列表随之滑动:首先获取到点击数字标题的索引,通过该索引计算出下方对应数字的起始项索引,然后通过scroller的scrollToIndex方法跳转到对应索引的数字项,具体代码块如下:
```ts
...
.onClick(() => {
this.typeIndex = index
this.classifyScroller.scrollToIndex(index)
let itemIndex = this.findItemIndex(index)
console.log("移动元素:" + itemIndex)
this.scroller.scrollToIndex(itemIndex)
})
...
```
4. 数字列表的滑动或点击导致数字标题的变动:通过List组件中onScrollIndex事件获取的到屏幕中最左边数字的索引值start,然后通过该索引值计算出对应的数字标题的索引currentClassIndex,然后通过scrollToIndex控制数字标题跳转到对应索引处,具体代码块如下:
```ts
...
.onScrollIndex((start) => {
let currentClassIndex = this.findClassIndex(start)
console.log("找到的类索引为: " + currentClassIndex)
if (currentClassIndex != this.tmp) {
this.tmp = currentClassIndex
console.log("类别移动到索引: " + currentClassIndex)
this.typeIndex = currentClassIndex
this.classifyScroller.scrollToIndex(currentClassIndex)
}
})
...
```
## 完整代码
完整示例代码如下:
```ts
@Entry
@Component
struct TwoLevelLink {
@State typeIndex: number = 0
private tmp: number = 0
private titles: Array<string> = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
private contents: Array<string> = ["1", "1", "1", "1", "1", "1", "1", "1", "1", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "3"
, "3", "3", "3", "3", "4", "4", "4", "5", "5", "5", "5", "5", "6", "7", "7", "7", "7", "7", "7", "7", "7", "7", "7", "7", "7",
"8", "8", "8", "8", "8", "9", "9", "9", "9", "9", "9", "9", "9", "9", "9", "9"]
private colors: Array<string> = ["#18183C", "#E8A027", "#D4C3B3", "#A4AE77", "#A55D51", "#1F3B54", "#002EA6", "#FFE78F", "#FF770F"]
private records: Array<number> = [0, 9, 21, 26, 29, 34, 35, 47, 52, 63]
private classifyScroller: Scroller = new Scroller();
private scroller: Scroller = new Scroller();
// 根据数字列表索引计算对应数字标题的索引
findClassIndex(ind: number) {
let ans = 0
for (let i = 0; i < this.records.length; i++) {
if (ind >= this.records[i] && ind < this.records[i + 1]) {
ans = i
break
}
}
return ans
}
// 根据数字标题索引计算对应数字列表的索引
findItemIndex(ind: number) {
return this.records[ind]
}
build() {
Column({ space: 0 }) {
List ({ space: 50, scroller: this.classifyScroller, initialIndex: 0 }) {
ForEach(this.titles, (item, index) => {
ListItem() {
Column() {
Text(item)
.fontSize(24)
if (this.typeIndex == index) {
Line()
.width(item.length === 2 ? 25 : item.length === 3 ? 35 : 50)
.height(3)
.strokeWidth(20)
.strokeLineCap(LineCapStyle.Round)
.backgroundColor('#ffcf9861')
}
}
.onClick(() => {
this.typeIndex = index
this.classifyScroller.scrollToIndex(index)
let itemIndex = this.findItemIndex(index)
console.log("移动元素:" + itemIndex)
this.scroller.scrollToIndex(itemIndex)
})
}
})
}
.listDirection(Axis.Horizontal)
.height(50)
List({ space: 20, scroller: this.scroller }) {
ForEach(this.contents, (item, index) => {
ListItem() {
Column({ space: 5 }) {
Text(item)
.fontSize(30)
.fontColor(Color.White)
}
.width(60)
.height(60)
.backgroundColor(this.colors[item-1])
.justifyContent(FlexAlign.Center)
.onClick(() => {
this.scroller.scrollToIndex(index)
})
}
})
}
.listDirection(Axis.Horizontal) //列表排列方向水平
.edgeEffect(EdgeEffect.None) //不支持滑动效果
.onScrollIndex((start) => {
let currentClassIndex = this.findClassIndex(start)
console.log("找到的类索引为: " + currentClassIndex)
if (currentClassIndex != this.tmp) {
this.tmp = currentClassIndex
console.log("类别移动到索引: " + currentClassIndex)
this.typeIndex = currentClassIndex
this.classifyScroller.scrollToIndex(currentClassIndex)
}
})
}.width('100%').height('100%').backgroundColor(0xDCDCDC).padding({ top: 5 })
}
}
```
## 参考
[List](../application-dev/reference/arkui-ts/ts-container-list.md)
[Line](../application-dev/reference/arkui-ts/ts-drawing-components-line.md)
[Scroll](../application-dev/reference/arkui-ts/ts-container-scroll.md)
\ No newline at end of file
# 时钟开发
## 场景介绍
常见的时钟呈现方式有两种,一种是表盘方式,一种是数字方式。用户可根据个人喜好在两种形式间进行切换。本例即为大家讲解如何开发上述两种钟表样式,以供参考。
## 效果呈现
本例最终效果如下:
![](figures/clock.gif)
## 运行环境
本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发:
- IDE: DevEco Studio 3.1 Beta2
- SDK: Ohos_sdk_public 3.2.11.9 (API Version 9 Release)
## 实现思路
- 表盘方式的展示:通过Canvas组件提供画布;在画布上,通过CanvasRenderingContext2D对象使用RenderingContext在Canvas组件上进行绘制,绘制表盘上的数字、时针、分针、秒针。表盘上数字的分布使用fillText绘制填充类文本并确定其在画布上位置;表盘上时针的运动通过theta的角度决定时针的移动;分针和秒针同上。
- 数字时间方式的展示:使用TextClock组件通过文本将系统时间显示在设备上。
## 开发步骤
根据上述思路,具体实现步骤如下:
1. 表盘方式:通过CanvasRenderingContext2D对象使用RenderingContext在Canvas组件上进行绘制,绘制表盘上的数字、时针、分针、秒针。
首先,创建画布,具体代码如下:
```ts
// clock ets
clear() { // clear canvas function
this.ctx.clearRect(0, 0, 360, 500);
}
drawScene() { // main drawScene function 绘制场景
this.clear(); // clear canvas
...
build() {
Column({ space: 5 }) {
Canvas(this.ctx)
.width(360)
.height(500)
.border({ width: 1, color: '#ffff00'})
.onReady(() => {
setInterval(() => {
this.drawScene()
}, 1000)
})
...
}
}
```
声明相关变量,具体代码如下:
```ts
// clock ets
let date = new Date();
let hours = date.getHours();
let minutes = date.getMinutes();
let seconds = date.getSeconds();
hours = hours > 12 ? hours - 12 : hours;
let hour = hours + minutes / 60;
let minute = minutes + seconds / 60;
```
使用fillText方法绘制表盘数字并确定其位置
```ts
// clock ets
...
// draw numbers
this.ctx.font = '36px Arial'; //文本尺寸
this.ctx.fillStyle = '#000'; //指定绘制的填充色
this.ctx.textAlign = 'center'; // 文本对齐
this.ctx.textBaseline = 'middle'; //文本基线
for (let n = 0; n < 12; n++) {
let theta = (n - 2) * (Math.PI * 2) / 12;
let x = clockRadius * 0.7 * Math.cos(theta);
let y = clockRadius * 0.7 * Math.sin(theta);
this.ctx.fillText(`${n + 1}`, x, y); // 表盘数字所在的位置
...
```
时针的移动路径,具体代码如下:
```ts
// clock ets
...
// draw hour
this.ctx.save(); //将当前状态放入栈中,保存canvas的全部状态,通常在需要保存绘制状态时调用
let theta = (hour - 3) * 2 * Math.PI / 12;
this.ctx.rotate(theta); //顺时针旋转
this.ctx.beginPath(); //创建一个新的绘制路径
this.ctx.moveTo(-15, -5); //绘制时针组件 起始点
this.ctx.lineTo(-15, 5);
this.ctx.lineTo(clockRadius * 0.3, 1);
this.ctx.lineTo(clockRadius * 0.3, -1); //绘制时针组件 终点
this.ctx.fillStyle = 'green';
this.ctx.fill();
this.ctx.restore(); //对保存的绘图上下文进行恢复
...
```
分针的移动路径,具体代码如下:
```ts
// clock ets
...
// draw minute
this.ctx.save();
theta = (minute - 15) * 2 * Math.PI / 60;
this.ctx.rotate(theta); //顺时针旋转
this.ctx.beginPath(); //创建一个新的绘制路径
this.ctx.moveTo(-15, -4);//绘制分针组件 起始点
this.ctx.lineTo(-15, 4);
this.ctx.lineTo(clockRadius * 0.45, 1);
this.ctx.lineTo(clockRadius * 0.45, -1);//绘制分针组件 终点
this.ctx.fillStyle = 'red';
this.ctx.fill();
this.ctx.restore(); //对保存的绘图上下文进行恢复
...
```
秒针的移动路径,具体代码如下:
```ts
// clock ets
...
// draw second
this.ctx.save();
theta = (seconds - 15) * 2 * Math.PI / 60;
this.ctx.rotate(theta); //顺时针旋转
this.ctx.beginPath(); //创建一个新的绘制路径
this.ctx.moveTo(-15, -3);//绘制秒针组件 起始点
this.ctx.lineTo(-15, 3);
this.ctx.lineTo(clockRadius * 0.6, 1);
this.ctx.lineTo(clockRadius * 0.6, -1);//绘制秒针组件 终点
this.ctx.fillStyle = 'black';
this.ctx.fill();
this.ctx.restore(); //对保存的绘图上下文进行恢复
...
```
2. 时钟方式的转换:通过Button组件中的onClick事件进行切换页面。
从表盘方式往数字方式转换,具体代码如下:
```ts
// clock.ets
...
Button(){
Text("切换")
.fontSize(30)
.fontWeight(FontWeight.Regular)
}
.type(ButtonType.Capsule)
.margin({top:20
})
.backgroundColor("red")
.width('40%')
.height('5%')
.onClick(()=>{
router.pushUrl({url:'pages/Index1'})
})
...
```
从数字时间方式往表盘方式转换,具体代码如下:
```ts
// TextClock.ets
...
Button() {
Text("切换")
.fontSize(30)
.fontWeight(FontWeight.Regular)
}
.type(ButtonType.Capsule)
.margin({ top: 20
})
.backgroundColor("red")
.width('40%')
.height('5%')
.onClick(() => {
router.back()
})
...
```
3. 数字时间方式:使用TextClock组件通过文本将当前系统时间显示在设备上。
具体代码如下:
```ts
// TextClock.ets
import router from '@ohos.router'
@Entry
@Component
struct Second {
@State accumulateTime: number = 0
// 导入对象
controller: TextClockController = new TextClockController()
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
TextClock({ timeZoneOffset: -8, controller: this.controller })
.format('hms') //数字时间格式
.onDateChange((value: number) => {
this.accumulateTime = value
})
.margin(20)
.fontSize(30)
...
}
}
}
```
## 完整代码
完整示例代码如下:
表盘时钟代码页
```ts
// clock.ets
import router from '@ohos.router';
const clockRadius = 180;
@Entry
@Component
struct Test10 {
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
// 绘制函数
clear() {
this.ctx.clearRect(0, 0, 360, 500);
}
drawScene() { // 绘制场景
this.clear(); // 清空画布
// 获取当前时间
let date = new Date();
let hours = date.getHours();
let minutes = date.getMinutes();
let seconds = date.getSeconds();
hours = hours > 12 ? hours - 12 : hours;
let hour = hours + minutes / 60;
let minute = minutes + seconds / 60;
this.ctx.save();
this.ctx.translate(360 / 2, 500 / 2);
this.ctx.beginPath(); //创建一个新的绘制路径
// 绘制表盘数字
this.ctx.font = '45px Arial'; //文本尺寸
this.ctx.fillStyle = '#000'; //指定绘制的填充色
this.ctx.textAlign = 'center'; // 文本对齐
this.ctx.textBaseline = 'middle'; //文本基线
for (let n = 0; n < 12; n++) {
let theta = (n - 2) * (Math.PI * 2) / 12;
let x = clockRadius * 0.7 * Math.cos(theta);
let y = clockRadius * 0.7 * Math.sin(theta);
this.ctx.fillText(`${n + 1}`, x, y); // 表盘数字所在的位置
}
// 绘制时针
this.ctx.save(); //将当前状态放入栈中,保存canvas的全部状态,通常在需要保存绘制状态时调用
let theta = (hour - 3) * 2 * Math.PI / 12;
this.ctx.rotate(theta); //顺时针旋转
this.ctx.beginPath(); //创建一个新的绘制路径
this.ctx.moveTo(-15, -5); //绘制时针组件 起始点
this.ctx.lineTo(-15, 5);
this.ctx.lineTo(clockRadius * 0.3, 1);
this.ctx.lineTo(clockRadius * 0.3, -1);
this.ctx.fillStyle = 'green';
this.ctx.fill();
this.ctx.restore(); //对保存的绘图上下文进行恢复
// 绘制分针
this.ctx.save();
theta = (minute - 15) * 2 * Math.PI / 60;
this.ctx.rotate(theta); //顺时针旋转
this.ctx.beginPath(); //创建一个新的绘制路径
this.ctx.moveTo(-15, -4); //绘制分针组件 起始点
this.ctx.lineTo(-15, 4);
this.ctx.lineTo(clockRadius * 0.45, 1);
this.ctx.lineTo(clockRadius * 0.45, -1);
this.ctx.fillStyle = 'red';
this.ctx.fill();
this.ctx.restore(); //对保存的绘图上下文进行恢复
// 绘制秒针
this.ctx.save();
theta = (seconds - 15) * 2 * Math.PI / 60;
this.ctx.rotate(theta); //顺时针旋转
this.ctx.beginPath(); //创建一个新的绘制路径
this.ctx.moveTo(-15, -3); //绘制秒针组件 起始点
this.ctx.lineTo(-15, 3);
this.ctx.lineTo(clockRadius * 0.6, 1);
this.ctx.lineTo(clockRadius * 0.6, -1);
this.ctx.fillStyle = 'black';
this.ctx.fill();
this.ctx.restore(); //对保存的绘图上下文进行恢复
this.ctx.restore(); //对保存的绘图上下文进行恢复
}
build() {
Column({ space: 5 }) {
Canvas(this.ctx)
.width(360)
.height(500)
.onReady(() => {
setInterval(() => {
this.drawScene()
}, 1000)
})
Button(){
Text("切换")
.fontSize(30)
.fontWeight(FontWeight.Regular)
}
.type(ButtonType.Capsule)
.margin({top:20
})
.backgroundColor('#E8A027')
.width('40%')
.height('5%')
.onClick(()=>{
router.pushUrl({url:'pages/TextClock'})
})
}.width('100%')
.height('100%')
.backgroundColor('#A4AE75')
}
}
```
数字时间代码页:
```ts
//TextClock.ets
import router from '@ohos.router'
@Entry
@Component
struct Second {
@State accumulateTime: number = 0
// 导入对象
controller: TextClockController = new TextClockController()
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
TextClock({ timeZoneOffset: -8, controller: this.controller }) //timeZoneOffset 时区偏移ian
.format('hms')
.onDateChange((value: number) => {
this.accumulateTime = value
})
.margin(20)
.fontSize(30)
Button() {
Text("切换")
.fontSize(30)
.fontWeight(FontWeight.Regular)
}
.type(ButtonType.Capsule)
.margin({ top: 20
})
.backgroundColor('#E8A027')
.width('40%')
.height('5%')
.onClick(() => {
router.back()
})
}
.width('100%')
.height('100%')
.backgroundColor('#D4C3B3')
}
}
```
## 参考
[Canvas](../application-dev/reference/arkui-ts/ts-components-canvas-canvas.md)
[CanvasRenderingContext2D对象](../application-dev/reference/arkui-ts/ts-canvasrenderingcontext2d.md)
[TextClock](../application-dev/reference/arkui-ts/ts-basic-components-textclock.md)
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册