当前位置: 首页 > news >正文

【鸿蒙HarmonyOS Next App实战开发】​​​​ArkUI纯色图生成器

本文基于鸿蒙ArkUI框架实现了一个支持​​自定义颜色、尺寸、透明度及圆角​​的纯色图生成工具,并集成了图片保存与分享功能。以下从架构设计、核心功能与性能优化三个维度展开解析。应用商店搜索【图影工具箱】,点击“纯色图”即可查看实际效果。


​一、技术架构与设计理念​
  1. ​分层架构设计​

    • ​UI层​​:通过声明式UI构建预览区(Canvas)和参数控制区(Slider/Button)。
    • ​逻辑层​​:状态管理(@State)驱动参数实时更新,如颜色值、透明度等。
    • ​服务层​​:沙箱文件操作(CustomImageUtil)、设备能力调用(AppUtil)。
  2. ​核心依赖模块​

    import { CanvasRenderingContext2D } from '@arkui.canvas'; // Canvas绘图核心
    import { PreferencesUtil } from './Utils/PreferencesUtil'; // 本地持久化存储
    import { CustomImageUtil } from './Utils/customUtils/CustomImageUtil'; // 图片处理工具
    • ​状态管理​​:@State变量(如selectedColorcolorOpacity)绑定UI动态更新。
    • ​生命周期控制​​:aboutToAppear恢复用户设置,aboutToDisappear保存当前配置。

​二、核心功能实现解析​
​1. 动态参数控制模块​
  • ​颜色选择​​:
    通过ColorPickDialog组件实现取色器交互,点击预览区弹出对话框:

    .onClick(() => this.colorPickDialogController?.open())

    选色结果同步至Canvas渲染(drawColor()方法)。

  • ​尺寸调节​​:
    双滑动条控制宽高(40px~2000px),实时显示像素值:

    Slider({ value: this.imageWidth, min: 40, max: 2000 }).onChange((value) => this.imageWidth = value)

    配合+/-按钮微调尺寸,确保操作精度。

  • ​透明度与圆角​​:

    • 透明度范围(0~1,步长0.01):Slider绑定colorOpacity变量。
    • 圆角范围(0~100px):通过drawRoundedRect方法生成圆角路径。
​2. Canvas绘图与优化​
  • ​自适应预览区​​:
    根据屏幕尺寸动态计算预览图比例,保持宽高比:
    const maxWidth = vp2px(this.screenWidth) * 0.9; // 限制最大宽度为屏幕90%
    const previewHeight = (previewWidth * this.imageHeight) / this.imageWidth; // 等比缩放
  • ​离屏渲染技术​​:
    保存图片时使用OffscreenCanvasRenderingContext2D生成高清原图:
    const tempCanvas = new OffscreenCanvasRenderingContext2D(vpWidth, vpHeight);
    this.drawRoundedRect(tempCanvas, 0, 0, vpWidth, vpHeight, actualBorderRadius);
    避免主线程渲染阻塞,提升性能。
​3. 图片保存与分享​
  • ​沙箱存储​​:
    CustomImageUtil.saveCanvasToSandbox将图片写入应用沙箱目录,文件名用UUID防冲突。
  • ​系统分享能力​​:
    调用CustomImageUtil.shareImage唤起系统分享菜单,支持社交平台分发。

​三、性能优化与体验设计​
​1. 渲染性能提升​
  • ​按需重绘​​:
    参数变化时仅更新Canvas(updateCanvasSize()),而非全局重渲染。
  • ​圆角路径算法优化​​:
    使用quadraticCurveTo绘制平滑圆角,替代arc减少计算量:
    canvas.quadraticCurveTo(0, 0, cornerRadius, 0); // 贝塞尔曲线优化
​2. 用户体验增强​
  • ​实时预览反馈​​:
    所有参数调整(如透明度滑块)即时触发Canvas重绘,实现“所见即所得”。
  • ​持久化存储​​:
    PreferencesUtil保存用户最后一次配置(颜色/尺寸等),提升使用连贯性。
  • ​防误触设计​​:
    尺寸按钮设置边界限制(>40px且<2000px),避免无效操作。
​3. 视觉层次设计​
  • ​卡片化布局​​:
    @Extend装饰器定义settingsCard样式,统一阴影与圆角:
    .shadow({ radius: 8, color: '#1A000000' }) // 柔和阴影提升层次感
    .backgroundColor($r('sys.color.ohos_id_color_sub_background')) // 动态主题适配
  • ​交互反馈​​:
    颜色预览按钮添加borderColor高亮边框,增强可识别性。

​结语​

本文实现的纯色图生成器,通过​​状态驱动UI​​、​​Canvas离屏渲染​​与​​本地化存储​​,在保障性能的同时提供了极致交互体验。其模块化设计(如分离CustomImageUtil工具类)可复用于其他图像处理场景(如证件照生成、海报设计)。

​设计原则总结​​:

原则实现方式
​实时性​状态绑定+按需重绘机制
​轻量化​纯前端实现,无额外依赖
​用户友好​持久化存储+沙箱文件管理
​扩展性​模块化工具类设计

具体代码如下:

import { ColorPickDialog } from './ColorPicker/components/ColorPickerDialog';
import { AppUtil } from './Utils/AppUtil';
import { PreferencesUtil } from './Utils/PreferencesUtil';
import { promptAction } from '@kit.ArkUI';
import CustomImageUtil from './Utils/customUtils/CustomImageUtil';
import { TitleBar } from '../components/TitleBar';
import { BusinessError } from '@kit.BasicServicesKit';
import { CustomSaveButton } from '../components/SaveButton';@Extend(Text)
function sectionTitle() {.fontSize(18).fontWeight(FontWeight.Medium).fontColor($r('sys.color.ohos_id_color_text_primary')).margin({ bottom: 12 })
}@Extend(Column)
function settingsCard() {.width('100%').padding(20).borderRadius(16).backgroundColor($r('sys.color.ohos_id_color_sub_background')).shadow({radius: 8,color: '#1A000000',offsetX: 2,offsetY: 4})
}@Entry
@Component
struct SolidColorPage {@State @Watch('updateCanvasSize') selectedColor: string = '#E3F2FD';@State imageWidth: number = 500;@State imageHeight: number = 500;@State previewWidth: number = 0;@State previewHeight: number = 0;@State colorOpacity: number = 1.0;@State colorBorderRadius: number = 0;canvas: CanvasRenderingContext2D = new CanvasRenderingContext2D();colorPickDialogController: CustomDialogController | null = new CustomDialogController({builder: ColorPickDialog({ color: this.selectedColor }),alignment: DialogAlignment.Center,width: '80%',cornerRadius: 15,backgroundColor: $r('sys.color.background_primary')});screenWidth: number = 0;screenHeight: number = 0;async aboutToAppear(): Promise<void> {AppUtil.setWindowKeepScreenOn(true);// 恢复上次的设置this.selectedColor = await PreferencesUtil.getString('solidColor') || '#E3F2FD';this.imageWidth = await PreferencesUtil.getNumber('solidColorWidth') || 500;this.imageHeight = await PreferencesUtil.getNumber('solidColorHeight') || 500;this.colorOpacity = await PreferencesUtil.getNumber('solidColorOpacity') || 1.0;this.colorBorderRadius = await PreferencesUtil.getNumber('solidColorBorderRadius') || 0;this.updateCanvasSize();}aboutToDisappear(): void {// 保存设置PreferencesUtil.put('solidColor', this.selectedColor);PreferencesUtil.put('solidColorWidth', this.imageWidth);PreferencesUtil.put('solidColorHeight', this.imageHeight);PreferencesUtil.put('solidColorOpacity', this.colorOpacity);PreferencesUtil.put('solidColorBorderRadius', this.colorBorderRadius);this.colorPickDialogController?.close();}updateCanvasSize(): void {// 计算预览尺寸,保持宽高比const maxWidth: number = vp2px(this.screenWidth) * 0.9;const maxHeight: number = vp2px(this.screenHeight) * 0.9;// 计算保持宽高比的预览尺寸let previewWidth: number = maxWidth;let previewHeight: number = (previewWidth * this.imageHeight) / this.imageWidth;// 如果高度超出限制,则按高度缩放if (previewHeight > maxHeight) {previewHeight = maxHeight;previewWidth = (previewHeight * this.imageWidth) / this.imageHeight;}this.previewWidth = previewWidth;this.previewHeight = previewHeight;this.drawColor();}drawColor(): void {this.canvas.reset();this.canvas.fillStyle = this.selectedColor;this.canvas.globalAlpha = this.colorOpacity;this.drawRoundedRect(this.canvas, 0, 0, px2vp(this.previewWidth), px2vp(this.previewHeight),this.colorBorderRadius);this.canvas.fill();}private drawRoundedRect(canvas: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, x: number, y: number,vpWidth: number, vpHeight: number, cornerRadius: number): void {// 创建圆角路径canvas.beginPath();canvas.moveTo(cornerRadius, 0);canvas.lineTo(vpWidth - cornerRadius, 0);canvas.quadraticCurveTo(vpWidth, 0, vpWidth, cornerRadius);canvas.lineTo(vpWidth, vpHeight - cornerRadius);canvas.quadraticCurveTo(vpWidth, vpHeight, vpWidth - cornerRadius, vpHeight);canvas.lineTo(cornerRadius, vpHeight);canvas.quadraticCurveTo(0, vpHeight, 0, vpHeight - cornerRadius);canvas.lineTo(0, cornerRadius);canvas.quadraticCurveTo(0, 0, cornerRadius, 0);canvas.closePath();canvas.clip();}async saveImage(): Promise<void> {let vpWidth = px2vp(this.imageWidth);let vpHeight = px2vp(this.imageHeight);const tempCanvas: OffscreenCanvasRenderingContext2D = new OffscreenCanvasRenderingContext2D(vpWidth, vpHeight);tempCanvas.fillStyle = this.selectedColor;tempCanvas.globalAlpha = this.colorOpacity;// 计算实际图片的圆角大小const scaleRatio = this.imageWidth / this.previewWidth;const actualBorderRadius = this.colorBorderRadius * scaleRatio;this.drawRoundedRect(tempCanvas, 0, 0, vpWidth, vpHeight, actualBorderRadius);tempCanvas.fill();try {// 保存到沙箱目录const filePath = await CustomImageUtil.saveCanvasToSandbox(tempCanvas, vpWidth, vpHeight);// 分享图片await CustomImageUtil.shareImage(filePath);} catch (error) {promptAction.showToast({ message: '保存或分享失败:' + (error as BusinessError).message });}}build() {Column() {// 顶部栏TitleBar({title: '纯色图生成'})// 预览区域Column() {Canvas(this.canvas).width(px2vp(this.previewWidth)).height(px2vp(this.previewHeight)).onClick(() => {this.colorPickDialogController?.open();})}.width('100%').height('40%').justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center).onSizeChange((oldValue: SizeOptions, newValue: SizeOptions) => {this.screenWidth = Number(newValue.width);this.screenHeight = Number(newValue.height);this.updateCanvasSize();})// 设置区域Scroll() {Column({ space: 20 }) {// 颜色选择Column() {Text('选择颜色').sectionTitle()Row({ space: 15 }) {Button().width(60).height(60).backgroundColor(this.selectedColor).borderRadius(30).borderWidth(3).borderColor($r('app.color.border_color')).shadow({radius: 8,color: '#1A000000',offsetX: 0,offsetY: 2})Text(this.selectedColor).fontSize(16).fontWeight(FontWeight.Medium).fontColor($r('sys.color.ohos_id_color_text_primary'))}}.settingsCard().onClick(() => {this.colorPickDialogController?.open();})// 尺寸设置Column() {Text('图片尺寸').sectionTitle()Row({ space: 8 }) {Column() {Text('宽度:').fontSize(14).fontColor($r('sys.color.ohos_id_color_text_secondary'))Text(`${this.imageWidth}px`).fontSize(16).fontWeight(FontWeight.Medium).fontColor($r('sys.color.ohos_id_color_text_primary'))}.width(60)Button() {SymbolGlyph($r('sys.symbol.minus')).fontSize(20).fontColor(['#FF6B6B'])}.backgroundColor('#FFF0F0').borderColor('#FF6B6B').borderWidth(1).padding(5).onClick(() => {if (this.imageWidth > 40) {this.imageWidth -= 1;this.updateCanvasSize();}})Slider({value: this.imageWidth,min: 40,max: 2000,step: 1,}).showTips(true, this.imageWidth + '').layoutWeight(1).onChange((value: number) => {this.imageWidth = value;this.updateCanvasSize();})Button() {SymbolGlyph($r('sys.symbol.plus')).fontSize(20).fontColor(['#4CAF50'])}.backgroundColor('#F0FFF0').borderColor('#4CAF50').borderWidth(1).padding(5).onClick(() => {if (this.imageWidth < 2000) {this.imageWidth += 1;this.updateCanvasSize();}})}.justifyContent(FlexAlign.Center).width('100%').margin({ bottom: 15 })Row({ space: 8 }) {Column() {Text('高度:').fontSize(14).fontColor($r('sys.color.ohos_id_color_text_secondary'))Text(`${this.imageHeight}px`).fontSize(16).fontWeight(FontWeight.Medium).fontColor($r('sys.color.ohos_id_color_text_primary'))}.width(60)Button() {SymbolGlyph($r('sys.symbol.minus')).fontSize(20).fontColor(['#FF6B6B'])}.backgroundColor('#FFF0F0').borderColor('#FF6B6B').borderWidth(1).padding(5).onClick(() => {if (this.imageHeight > 40) {this.imageHeight -= 1;this.updateCanvasSize();}})Slider({value: this.imageHeight,min: 40,max: 2000,step: 1}).showTips(true, this.imageHeight + '').layoutWeight(1).onChange((value: number) => {this.imageHeight = value;this.updateCanvasSize();})Button() {SymbolGlyph($r('sys.symbol.plus')).fontSize(20).fontColor(['#4CAF50'])}.backgroundColor('#F0FFF0').borderColor('#4CAF50').borderWidth(1).padding(5).onClick(() => {if (this.imageHeight < 2000) {this.imageHeight += 1;this.updateCanvasSize();}})}.justifyContent(FlexAlign.Center).width('100%')}.settingsCard()// 透明度设置Column() {Text('透明度').sectionTitle()Row({ space: 12 }) {Text('0').fontSize(14).fontColor($r('sys.color.ohos_id_color_text_secondary'))Slider({value: this.colorOpacity,min: 0,max: 1,step: 0.01}).showTips(true, this.colorOpacity.toFixed(2)).width(200).onChange((value: number) => {this.colorOpacity = value;this.updateCanvasSize();})Text('1').fontSize(14).fontColor($r('sys.color.ohos_id_color_text_secondary'))}.justifyContent(FlexAlign.Center).width('100%').margin({ bottom: 10 })}.settingsCard()// 圆角设置Column() {Text('圆角大小').sectionTitle()Row({ space: 12 }) {Text('0').fontSize(14).fontColor($r('sys.color.ohos_id_color_text_secondary'))Slider({value: this.colorBorderRadius,min: 0,max: 100,step: 1}).showTips(true, this.colorBorderRadius + '').width(200).onChange((value: number) => {this.colorBorderRadius = value;this.updateCanvasSize();})Text('100').fontSize(14).fontColor($r('sys.color.ohos_id_color_text_secondary'))}.justifyContent(FlexAlign.Center).width('100%').margin({ bottom: 10 })}.settingsCard()CustomSaveButton().onClick(()=>{this.saveImage();})}.width('100%').padding(20)}.edgeEffect(EdgeEffect.Spring).layoutWeight(1).height('50%')}.width('100%').height('100%').backgroundColor($r('app.color.index_tab_bar'))}
} 

http://www.lqws.cn/news/442009.html

相关文章:

  • Linux中Ansible常用模块
  • 【油藏地球物理正演软件ColchisFM】为什么经常用90度相移处理代替反演使用
  • PostgreSQL的扩展dict_int
  • 【AI作画】第2章comfy ui的一般输入节点,文本框的类型和输入形式
  • Postman 的 Jenkins 管理 - 自动构建
  • 通俗解释:编码器与解码器
  • 系统性能优化-3 内存池
  • uni-app项目实战笔记15--使用uni-popup实现弹出层和uni-rate实现评分效果
  • 【python】多次重试调用ai大模型
  • FPGA基础 -- Verilog 命名事件
  • Cursor Pro取消500次请求限制,无限用的体验更好了吗?
  • 【vim】通过vim编辑器打开、修改、退出配置文件
  • 新生活的开启:从 Trae AI 离开后的三个月
  • 【研发工具】.Net创建多项目模板(Visual Studio)
  • 轻量化社交管理方案:Skout与云手机的巧妙搭配
  • 暑期前端训练day1
  • SpringBoot扩展——应用Web Service!
  • 【 感知集群】大规模分布式基础设施的AI赋能蓝图
  • 深度学习之目标检测YOLO简介和YOLO v1模型算法流程详解说明(超详细理论篇)
  • Redis
  • 动态规划算法思路详解
  • 【高录用】2025年数字金融,大数据与商业管理国际会议 (DFBBM 2025)
  • 蚀刻效果解释
  • LeetCode | 一文弄懂树:定义、原理、应用与题型分类
  • SpringBoot新闻项目学习day3--后台权限的增删改查以及权限管理分配
  • 算法导论第十九章 并行算法:解锁计算新维度
  • Oracle 数据库性能优化之重做日志(redo)
  • 刘波卸任OPPO法定代表人、经理等职务,段要辉“接棒”
  • FPGA基础 -- Verilog 禁止语句
  • django rest_framework 自定义403 Forbidden错误页面