鸿蒙跨设备自定义环形进度条设计与实现 原创

进修的泡芙
发布于 2025-6-18 21:12
浏览
0收藏

鸿蒙跨设备自定义环形进度条设计与实现

一、系统架构设计

基于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状态

构建可复用的自定义组件

处理多设备数据同步与显示

这个组件可以广泛应用于各种需要展示进度的场景,如:
健康应用中的运动目标完成度

任务管理中的任务完成进度

下载或上传文件的进度显示

多设备协同任务的进度同步

组件具有良好的扩展性,可以轻松添加更多功能如渐变色、多段显示、动画效果等。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
收藏
回复
举报
回复
    相关推荐