圆形进度条:基于HarmonyOS Canvas的自定义组件实现与跨设备同步
自定义Canvas绘制圆形进度条支持百分比文本显示多设备间进度状态同步平滑的进度变化动画丰富的样式定制选项健身应用中训练进度的多设备展示文件传输进度的跨设备监控多设备协同任务的进度同步IoT设备状态的多端展示多段颜色进度(如红黄绿渐变)内环图标显示更丰富的动画效果同步过程中的冲突解决策略优化这个组件展示了HarmonyOS自定义组件开发的能力,以及如何将游戏中的同步技术应用于UI组件的开发中。
·
圆形进度条:基于HarmonyOS Canvas的自定义组件实现与跨设备同步
一、项目概述
本文实现一个基于HarmonyOS的自定义圆形进度条组件,该组件通过Canvas绘制动态进度环并支持百分比显示。同时借鉴《鸿蒙跨端U同步》中的状态同步机制,使进度条状态能够在多设备间实时同步,适用于健身应用、文件传输进度显示等多设备协同场景。
二、架构设计
+---------------------+ +---------------------+
| 圆形进度条组件 |<----->| 状态同步服务 |
| (CircleProgress) | | (StateSyncService) |
+----------+----------+ +----------+----------+
| |
+----------v----------+ +----------v----------+
| Canvas绘制引擎 | | 分布式数据管理 |
| (CanvasRenderer) | | (DistributedData) |
+----------+----------+ +----------+----------+
| |
+----------v-----------------------------v----------+
| HarmonyOS图形系统 |
+---------------------------------------------------+
三、核心代码实现
1. 圆形进度条组件实现
public class CircleProgress extends Component implements Component.DrawTask {
private static final String TAG = "CircleProgress";
// 默认样式参数
private static final int DEFAULT_WIDTH = 200;
private static final int DEFAULT_HEIGHT = 200;
private static final int DEFAULT_STROKE_WIDTH = 20;
private static final Color DEFAULT_BACKGROUND_COLOR = Color.GRAY;
private static final Color DEFAULT_PROGRESS_COLOR = Color.BLUE;
private static final Color DEFAULT_TEXT_COLOR = Color.BLACK;
// 组件状态
private int progress = 0;
private int maxProgress = 100;
private int strokeWidth = DEFAULT_STROKE_WIDTH;
private Color backgroundColor = DEFAULT_BACKGROUND_COLOR;
private Color progressColor = DEFAULT_PROGRESS_COLOR;
private Color textColor = DEFAULT_TEXT_COLOR;
private boolean showText = true;
private String syncId; // 用于跨设备同步的标识
// 同步服务引用
private StateSyncService stateSync;
public CircleProgress(Context context) {
super(context);
init();
}
public CircleProgress(Context context, AttrSet attrSet) {
super(context, attrSet);
initAttributes(attrSet);
init();
}
private void init() {
// 设置绘制委托
addDrawTask(this);
// 初始化同步服务
stateSync = StateSyncService.getInstance(getContext());
// 设置默认尺寸
setWidth(DEFAULT_WIDTH);
setHeight(DEFAULT_HEIGHT);
}
private void initAttributes(AttrSet attrSet) {
// 从XML属性初始化样式
strokeWidth = attrSet.getAttr("stroke_width").isPresent() ?
attrSet.getAttr("stroke_width").get().getIntegerValue() : DEFAULT_STROKE_WIDTH;
backgroundColor = attrSet.getAttr("bg_color").isPresent() ?
attrSet.getAttr("bg_color").get().getColorValue() : DEFAULT_BACKGROUND_COLOR;
progressColor = attrSet.getAttr("progress_color").isPresent() ?
attrSet.getAttr("progress_color").get().getColorValue() : DEFAULT_PROGRESS_COLOR;
textColor = attrSet.getAttr("text_color").isPresent() ?
attrSet.getAttr("text_color").get().getColorValue() : DEFAULT_TEXT_COLOR;
showText = attrSet.getAttr("show_text").isPresent() ?
attrSet.getAttr("show_text").get().getBoolValue() : true;
syncId = attrSet.getAttr("sync_id").isPresent() ?
attrSet.getAttr("sync_id").get().getStringValue() : null;
}
@Override
public void onDraw(Component component, Canvas canvas) {
// 获取组件尺寸
int width = getWidth();
int height = getHeight();
int radius = Math.min(width, height) / 2 - strokeWidth / 2;
int centerX = width / 2;
int centerY = height / 2;
// 绘制背景圆
Paint bgPaint = new Paint();
bgPaint.setStyle(Paint.Style.STROKE);
bgPaint.setStrokeWidth(strokeWidth);
bgPaint.setColor(backgroundColor);
canvas.drawCircle(centerX, centerY, radius, bgPaint);
// 绘制进度弧
Paint progressPaint = new Paint();
progressPaint.setStyle(Paint.Style.STROKE);
progressPaint.setStrokeWidth(strokeWidth);
progressPaint.setColor(progressColor);
progressPaint.setStrokeCap(Paint.StrokeCap.ROUND);
RectFloat oval = new RectFloat(
centerX - radius,
centerY - radius,
centerX + radius,
centerY + radius
);
float sweepAngle = 360 * progress / (float) maxProgress;
canvas.drawArc(oval, -90, sweepAngle, false, progressPaint);
// 绘制进度文本
if (showText) {
Paint textPaint = new Paint();
textPaint.setColor(textColor);
textPaint.setTextSize(radius * 0.5f);
textPaint.setTextAlign(Paint.Align.CENTER);
String text = progress + "%";
canvas.drawText(textPaint, text, centerX, centerY + radius * 0.1f);
}
}
// 设置当前进度
public void setProgress(int progress) {
this.progress = Math.min(Math.max(progress, 0), maxProgress);
// 同步状态到其他设备
if (syncId != null) {
stateSync.syncProgress(syncId, this.progress);
}
// 请求重绘
invalidate();
}
// 设置最大值
public void setMaxProgress(int max) {
this.maxProgress = max > 0 ? max : 100;
invalidate();
}
// 其他样式设置方法...
// 注册同步监听器
public void registerSyncListener() {
if (syncId != null) {
stateSync.registerProgressListener(syncId, new StateSyncService.ProgressListener() {
@Override
public void onProgressChanged(String id, int newProgress) {
getUITaskDispatcher().asyncDispatch(() -> {
setProgress(newProgress);
});
}
});
}
}
}
2. 状态同步服务实现
public class StateSyncService {
private static final String TAG = "StateSyncService";
private static final String PROGRESS_SYNC_CHANNEL = "progress_sync";
private static StateSyncService instance;
private DistributedDataManager dataManager;
private Map<String, ProgressListener> progressListeners = new HashMap<>();
private StateSyncService(Context context) {
this.dataManager = DistributedDataManagerFactory.getInstance()
.createDistributedDataManager(context);
initDataListener();
}
public static synchronized StateSyncService getInstance(Context context) {
if (instance == null) {
instance = new StateSyncService(context);
}
return instance;
}
private void initDataListener() {
// 监听进度变化
dataManager.registerDataChangeListener(PROGRESS_SYNC_CHANNEL, new DataChangeListener() {
@Override
public void onDataChanged(String deviceId, String key, String value) {
try {
JSONObject progressJson = new JSONObject(value);
String syncId = progressJson.getString("sync_id");
int progress = progressJson.getInt("progress");
ProgressListener listener = progressListeners.get(syncId);
if (listener != null) {
listener.onProgressChanged(syncId, progress);
}
} catch (JSONException e) {
HiLog.error(TAG, "Failed to parse progress data");
}
}
});
}
// 同步进度状态
public void syncProgress(String syncId, int progress) {
JSONObject progressJson = new JSONObject();
try {
progressJson.put("sync_id", syncId);
progressJson.put("progress", progress);
progressJson.put("timestamp", System.currentTimeMillis());
progressJson.put("device_id", DistributedDeviceInfo.getLocalDeviceId());
dataManager.putString(PROGRESS_SYNC_CHANNEL, progressJson.toString());
} catch (JSONException e) {
HiLog.error(TAG, "Failed to serialize progress data");
}
}
// 注册进度监听器
public void registerProgressListener(String syncId, ProgressListener listener) {
progressListeners.put(syncId, listener);
}
// 取消注册进度监听器
public void unregisterProgressListener(String syncId) {
progressListeners.remove(syncId);
}
public interface ProgressListener {
void onProgressChanged(String syncId, int progress);
}
}
3. 组件XML属性定义 (resources/attrs.xml)
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<attr name="stroke_width" format="integer"/>
<attr name="bg_color" format="color"/>
<attr name="progress_color" format="color"/>
<attr name="text_color" format="color"/>
<attr name="show_text" format="boolean"/>
<attr name="sync_id" format="string"/>
<declare-styleable name="CircleProgress">
<attr name="stroke_width"/>
<attr name="bg_color"/>
<attr name="progress_color"/>
<attr name="text_color"/>
<attr name="show_text"/>
<attr name="sync_id"/>
</declare-styleable>
</resources>
4. 使用示例 (XML布局)
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
xmlns:app="http://schemas.huawei.com/res/ohos-auto"
ohos:width="match_parent"
ohos:height="match_parent"
ohos:orientation="vertical"
ohos:padding="24vp">
<com.example.circleprogress.CircleProgress
ohos:id="$+id:progress1"
ohos:width="200vp"
ohos:height="200vp"
app:stroke_width="15"
app:bg_color="#EEEEEE"
app:progress_color="#4285F4"
app:text_color="#000000"
app:show_text="true"
app:sync_id="workout_progress"/>
<Slider
ohos:id="$+id:progress_control"
ohos:width="match_parent"
ohos:height="wrap_content"
ohos:margin_top="24vp"
ohos:min_value="0"
ohos:max_value="100"
ohos:progress="0"/>
</DirectionalLayout>
5. 使用示例 (Java代码)
public class ProgressDemoSlice extends AbilitySlice {
private CircleProgress circleProgress;
private Slider progressControl;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
setUIContent(ResourceTable.Layout_progress_demo_layout);
// 获取组件
circleProgress = (CircleProgress) findComponentById(ResourceTable.Id_progress1);
progressControl = (Slider) findComponentById(ResourceTable.Id_progress_control);
// 注册同步监听器
circleProgress.registerSyncListener();
// 设置滑动条监听
progressControl.setValueChangedListener((slider, value, fromUser) -> {
circleProgress.setProgress(value);
});
// 模拟进度变化
getUITaskDispatcher().delayDispatch(() -> {
animateProgress(0, 75, 2000);
}, 1000);
}
private void animateProgress(int from, int to, long duration) {
ValueAnimator animator = ValueAnimator.ofInt(from, to);
animator.setDuration(duration);
animator.setCurveType(IAnimator.CurveType.LINEAR);
animator.setValueUpdateListener((animator, value) -> {
circleProgress.setProgress((int) value);
});
animator.start();
}
}
四、与《鸿蒙跨端U同步》的技术关联
本项目的跨设备同步机制借鉴了游戏中的多设备同步方案:
- 状态同步模型:类似游戏中玩家状态的同步,进度条状态通过JSON格式广播
- 冲突解决:基于时间戳的更新策略,确保最终一致性
- 设备过滤:避免本地设备处理自己发送的状态更新
- 轻量级协议:使用最小必要数据实现同步,减少网络开销
增强的同步逻辑(借鉴游戏同步机制):
// 增强的进度同步方法
public void syncProgress(String syncId, int progress) {
JSONObject progressJson = new JSONObject();
try {
progressJson.put("sync_id", syncId);
progressJson.put("progress", progress);
progressJson.put("timestamp", System.currentTimeMillis());
progressJson.put("device_id", DistributedDeviceInfo.getLocalDeviceId());
progressJson.put("version", getNextVersion());
// 增加可靠性选项
DistributedOptions options = new DistributedOptions();
options.setPriority(DistributedOptions.Priority.HIGH);
options.setTimeToLive(5000); // 5秒有效期
// 使用可靠传输
dataManager.putString(PROGRESS_SYNC_CHANNEL,
progressJson.toString(),
DistributedDataManager.PUT_MODE_RELIABLE,
options);
} catch (JSONException e) {
HiLog.error(TAG, "Failed to serialize progress data");
}
}
// 增强的数据监听器
private void initDataListener() {
dataManager.registerDataChangeListener(PROGRESS_SYNC_CHANNEL, new DataChangeListener() {
@Override
public void onDataChanged(String deviceId, String key, String value) {
try {
JSONObject progressJson = new JSONObject(value);
String syncId = progressJson.getString("sync_id");
int progress = progressJson.getInt("progress");
long timestamp = progressJson.getLong("timestamp");
String sourceDevice = progressJson.getString("device_id");
int version = progressJson.optInt("version", 0);
// 忽略本地设备发送的更新
if (sourceDevice.equals(DistributedDeviceInfo.getLocalDeviceId())) {
return;
}
// 获取当前状态
ProgressState currentState = getCurrentState(syncId);
// 冲突解决策略:选择最新版本或时间戳更大的更新
if (currentState == null ||
version > currentState.version ||
(version == currentState.version && timestamp > currentState.timestamp)) {
ProgressListener listener = progressListeners.get(syncId);
if (listener != null) {
listener.onProgressChanged(syncId, progress);
}
// 更新本地状态
saveCurrentState(syncId, new ProgressState(progress, version, timestamp));
}
} catch (JSONException e) {
HiLog.error(TAG, "Failed to parse progress data");
}
}
});
}
五、项目特色与创新点
- 自定义绘制:通过Canvas实现高性能的圆形进度绘制
- 灵活样式:支持自定义颜色、线宽、文本显示等
- 跨设备同步:进度状态可在多设备间实时同步
- 动画支持:内置平滑的进度变化动画
- 轻量级实现:核心代码精简,性能高效
六、总结
本圆形进度条组件实现了以下功能:
- 自定义Canvas绘制圆形进度条
- 支持百分比文本显示
- 多设备间进度状态同步
- 平滑的进度变化动画
- 丰富的样式定制选项
通过借鉴游戏中的状态同步机制,我们实现了可靠的跨设备进度同步,适用于以下场景:
- 健身应用中训练进度的多设备展示
- 文件传输进度的跨设备监控
- 多设备协同任务的进度同步
- IoT设备状态的多端展示
未来可扩展功能包括:
- 多段颜色进度(如红黄绿渐变)
- 内环图标显示
- 更丰富的动画效果
- 同步过程中的冲突解决策略优化
这个组件展示了HarmonyOS自定义组件开发的能力,以及如何将游戏中的同步技术应用于UI组件的开发中。
更多推荐

所有评论(0)