
鸿蒙跨设备自定义环形进度条设计与实现 原创
鸿蒙跨设备自定义环形进度条设计与实现
一、系统架构设计
基于HarmonyOS的Canvas绘图能力和分布式技术,我们设计了一个跨设备同步的环形进度条组件,可用于展示任务进度、健康数据等多设备同步场景。
!https://example.com/circle-progress-arch.png
系统包含三个核心模块:
Canvas绘图模块 - 使用Canvas组件绘制环形进度条
动画控制模块 - 使用animateTo实现平滑过渡效果
分布式同步模块 - 通过@ohos.distributedData实现多设备进度同步
二、核心代码实现
环形进度条组件(ArkTS)
// CircleProgress.ets
@Component
export struct CircleProgress {
@State progress: number = 0; // 0-100
@State animatedProgress: number = 0;
@State color: Color = Color.Blue;
private settings: ProgressSettings = {
size: 200,
strokeWidth: 20,
backgroundColor: Color.Grey,
showText: true,
textSize: 24
};
build() {
Column() {
// 环形进度条
Canvas(this.onDraw)
.width(this.settings.size)
.height(this.settings.size)
// 控制面板
this.buildControls()
}
onDraw = (ctx: CanvasRenderingContext2D) => {
const center = this.settings.size / 2;
const radius = center - this.settings.strokeWidth / 2;
const startAngle = -Math.PI / 2;
const endAngle = startAngle + (this.animatedProgress / 100) Math.PI 2;
// 绘制背景圆
ctx.beginPath();
ctx.arc(center, center, radius, 0, Math.PI * 2);
ctx.lineWidth = this.settings.strokeWidth;
ctx.strokeStyle = this.settings.backgroundColor.toString();
ctx.stroke();
// 绘制进度圆
ctx.beginPath();
ctx.arc(center, center, radius, startAngle, endAngle);
ctx.lineWidth = this.settings.strokeWidth;
ctx.strokeStyle = this.color.toString();
ctx.stroke();
// 绘制进度文本
if (this.settings.showText) {
ctx.font = ${this.settings.textSize}px sans-serif;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = Color.Black.toString();
ctx.fillText(${Math.round(this.animatedProgress)}%, center, center);
}
@Builder
buildControls() {
Row() {
Slider({
value: this.progress,
min: 0,
max: 100,
step: 1
})
.onChange((value: number) => {
this.updateProgress(value);
})
Button('同步')
.onClick(() => {
this.syncProgress();
})
.margin({ top: 20 })
private updateProgress(value: number) {
this.progress = value;
// 使用动画过渡
animateTo({
duration: 500,
curve: Curve.EaseOut
}, () => {
this.animatedProgress = value;
});
private syncProgress() {
distributedData.sync('progress_data', {
progress: this.progress,
color: this.color.toString(),
timestamp: Date.now()
});
public setProgress(value: number): void {
this.updateProgress(value);
public setColor(color: Color): void {
this.color = color;
public setSettings(settings: Partial<ProgressSettings>): void {
this.settings = { ...this.settings, ...settings };
}
interface ProgressSettings {
size: number;
strokeWidth: number;
backgroundColor: Color;
showText: boolean;
textSize: number;
分布式进度同步服务(ArkTS)
// ProgressSyncService.ets
import distributedData from ‘@ohos.distributedData’;
class ProgressSyncService {
private static instance: ProgressSyncService = null;
private dataManager: distributedData.DataManager;
private listeners: ProgressListener[] = [];
private constructor() {
this.initDataManager();
public static getInstance(): ProgressSyncService {
if (!ProgressSyncService.instance) {
ProgressSyncService.instance = new ProgressSyncService();
return ProgressSyncService.instance;
private initDataManager() {
this.dataManager = distributedData.createDataManager({
bundleName: 'com.example.progressdemo',
area: distributedData.Area.GLOBAL
});
this.dataManager.registerDataListener('progress_data', (data) => {
this.handleSyncData(data);
});
public syncProgress(progress: number, color: Color): void {
this.dataManager.syncData('progress_data', {
progress: progress,
color: color.toString(),
timestamp: Date.now(),
deviceId: this.getLocalDeviceId()
});
public addListener(listener: ProgressListener): void {
if (!this.listeners.includes(listener)) {
this.listeners.push(listener);
}
public removeListener(listener: ProgressListener): void {
this.listeners = this.listeners.filter(l => l !== listener);
private handleSyncData(data: any): void {
if (data?.progress !== undefined && data.color) {
const progress = data.progress;
const color = Color.fromString(data.color);
const deviceId = data.deviceId;
this.listeners.forEach(listener => {
listener.onProgressChanged(progress, color, deviceId);
});
}
private getLocalDeviceId(): string {
// 实际实现需要获取设备ID
return ‘local_device’;
}
interface ProgressListener {
onProgressChanged(progress: number, color: Color, deviceId: string): void;
export const progressSyncService = ProgressSyncService.getInstance();
多设备进度显示页面(ArkUI)
// MultiDeviceProgress.ets
import { progressSyncService } from ‘./ProgressSyncService’;
@Entry
@Component
struct MultiDeviceProgress {
@State localProgress: number = 0;
@State localColor: Color = Color.Blue;
@State deviceProgress: Map<string, DeviceProgress> = new Map();
private progressListener: ProgressListener = {
onProgressChanged: (progress, color, deviceId) => {
this.updateDeviceProgress(deviceId, progress, color);
};
aboutToAppear() {
progressSyncService.addListener(this.progressListener);
aboutToDisappear() {
progressSyncService.removeListener(this.progressListener);
build() {
Column() {
// 本地进度条
Text('本机进度').fontSize(18)
CircleProgress({
progress: this.localProgress,
color: this.localColor
})
.settings({
size: 150,
strokeWidth: 15
})
// 控制面板
this.buildControls()
// 远程设备进度
Text('其他设备进度').fontSize(18).margin({ top: 30 })
this.buildDeviceProgress()
.padding(20)
@Builder
buildControls() {
Column() {
Slider({
value: this.localProgress,
min: 0,
max: 100,
step: 1
})
.onChange((value: number) => {
this.localProgress = value;
})
Row() {
Button('蓝色')
.onClick(() => {
this.localColor = Color.Blue;
})
Button('红色')
.margin({ left: 10 })
.onClick(() => {
this.localColor = Color.Red;
})
Button('绿色')
.margin({ left: 10 })
.onClick(() => {
this.localColor = Color.Green;
})
.margin({ top: 10 })
Button('同步到所有设备')
.margin({ top: 20 })
.onClick(() => {
progressSyncService.syncProgress(this.localProgress, this.localColor);
})
.margin({ top: 20 })
@Builder
buildDeviceProgress() {
if (this.deviceProgress.size === 0) {
Text(‘暂无其他设备数据’)
.fontSize(16)
.margin({ top: 20 })
else {
Grid() {
ForEach(Array.from(this.deviceProgress.entries()), ([deviceId, data]) => {
GridItem() {
Column() {
Text(data.deviceName)
CircleProgress({
progress: data.progress,
color: data.color
})
.settings({
size: 120,
strokeWidth: 12,
textSize: 18
})
}
})
.columnsTemplate(‘1fr 1fr’)
.rowsTemplate('1fr')
.columnsGap(20)
.rowsGap(20)
}
private updateDeviceProgress(deviceId: string, progress: number, color: Color) {
this.deviceProgress.set(deviceId, {
deviceId: deviceId,
deviceName: this.getDeviceName(deviceId),
progress: progress,
color: color
});
private getDeviceName(deviceId: string): string {
// 实际实现需要获取设备名称
return 设备 ${deviceId.slice(-4)};
}
interface DeviceProgress {
deviceId: string;
deviceName: string;
progress: number;
color: Color;
三、高级功能扩展
渐变颜色进度条
// 修改CircleProgress的onDraw方法
onDraw = (ctx: CanvasRenderingContext2D) => {
const center = this.settings.size / 2;
const radius = center - this.settings.strokeWidth / 2;
const startAngle = -Math.PI / 2;
const endAngle = startAngle + (this.animatedProgress / 100) Math.PI 2;
// 绘制背景圆
ctx.beginPath();
ctx.arc(center, center, radius, 0, Math.PI * 2);
ctx.lineWidth = this.settings.strokeWidth;
ctx.strokeStyle = this.settings.backgroundColor.toString();
ctx.stroke();
// 创建渐变
const gradient = ctx.createLinearGradient(0, 0, this.settings.size, 0);
gradient.addColorStop(0, ‘#FF5F6D’);
gradient.addColorStop(0.5, ‘#FFC371’);
gradient.addColorStop(1, ‘#4BC0C8’);
// 绘制进度圆
ctx.beginPath();
ctx.arc(center, center, radius, startAngle, endAngle);
ctx.lineWidth = this.settings.strokeWidth;
ctx.strokeStyle = gradient;
ctx.stroke();
// …文本绘制代码不变
动画效果增强
// 在CircleProgress组件中添加动画效果
private updateProgress(value: number) {
this.progress = value;
// 使用弹簧动画效果
animateTo({
duration: 800,
curve: Curve.Spring
}, () => {
this.animatedProgress = value;
});
// 添加脉冲效果
if (value > 0) {
animateTo({
duration: 200,
iterations: 2,
playMode: PlayMode.Alternate
}, () => {
this.settings.strokeWidth = this.settings.strokeWidth * 1.2;
});
}
多段进度显示
// 修改CircleProgress的onDraw方法支持多段进度
onDraw = (ctx: CanvasRenderingContext2D) => {
const center = this.settings.size / 2;
const radius = center - this.settings.strokeWidth / 2;
// 绘制背景圆
ctx.beginPath();
ctx.arc(center, center, radius, 0, Math.PI * 2);
ctx.lineWidth = this.settings.strokeWidth;
ctx.strokeStyle = this.settings.backgroundColor.toString();
ctx.stroke();
// 绘制多段进度
const segments = [
value: 30, color: Color.Red },
value: 50, color: Color.Yellow },
value: 20, color: Color.Green }
];
let startAngle = -Math.PI / 2;
const total = segments.reduce((sum, seg) => sum + seg.value, 0);
segments.forEach(segment => {
const segmentAngle = (segment.value / total) Math.PI 2;
const endAngle = startAngle + segmentAngle;
ctx.beginPath();
ctx.arc(center, center, radius, startAngle, endAngle);
ctx.lineWidth = this.settings.strokeWidth;
ctx.strokeStyle = segment.color.toString();
ctx.stroke();
startAngle = endAngle;
});
// …文本绘制代码
四、项目配置
权限配置
// module.json5
“module”: {
"requestPermissions": [
“name”: “ohos.permission.DISTRIBUTED_DATASYNC”,
"reason": "跨设备同步进度数据"
],
"abilities": [
“name”: “MainAbility”,
"type": "page",
"visible": true
],
"distributedNotification": {
"scenarios": [
“name”: “progress_sync”,
"value": "circle_progress"
]
}
资源文件
<!-- resources/base/element/string.json -->
“string”: [
“name”: “app_name”,
"value": "环形进度条"
},
“name”: “local_progress”,
"value": "本机进度"
},
“name”: “remote_progress”,
"value": "其他设备进度"
},
“name”: “no_devices”,
"value": "暂无其他设备数据"
},
“name”: “sync_button”,
"value": "同步到所有设备"
]
五、使用示例
基本使用
// 简单使用示例
@Entry
@Component
struct SimpleProgress {
@State progress: number = 30;
build() {
Column() {
CircleProgress({
progress: this.progress,
color: Color.Blue
})
Slider({
value: this.progress,
min: 0,
max: 100
})
.onChange((value: number) => {
this.progress = value;
})
}
多设备同步示例
// 多设备同步示例
@Entry
@Component
struct SyncProgressDemo {
@State localProgress: number = 0;
@State colorIndex: number = 0;
private colors: Color[] = [Color.Blue, Color.Red, Color.Green, Color.Orange];
build() {
Column() {
CircleProgress({
progress: this.localProgress,
color: this.colors[this.colorIndex]
})
.settings({
size: 180,
strokeWidth: 18
})
Row() {
Button('增加进度')
.onClick(() => {
this.localProgress = Math.min(100, this.localProgress + 10);
this.syncProgress();
})
Button('切换颜色')
.margin({ left: 10 })
.onClick(() => {
this.colorIndex = (this.colorIndex + 1) % this.colors.length;
this.syncProgress();
})
.margin({ top: 20 })
}
private syncProgress() {
progressSyncService.syncProgress(
this.localProgress,
this.colors[this.colorIndex]
);
}
六、总结
通过这个自定义环形进度条组件的实现,我们学习了:
使用Canvas绘制复杂图形
实现平滑的动画过渡效果
利用HarmonyOS分布式能力同步UI状态
构建可复用的自定义组件
处理多设备数据同步与显示
这个组件可以广泛应用于各种需要展示进度的场景,如:
健康应用中的运动目标完成度
任务管理中的任务完成进度
下载或上传文件的进度显示
多设备协同任务的进度同步
组件具有良好的扩展性,可以轻松添加更多功能如渐变色、多段显示、动画效果等。
