HarmonyOS 鸿蒙Next中如何使用Canvas进行自定义绘图?
HarmonyOS 鸿蒙Next中如何使用Canvas进行自定义绘图?
如何使用Canvas进行自定义绘图?
如何绘制复杂图形、实现动画效果和手势交互?
解决方案
1. Canvas基础绘图
@Entry
@Component
struct BasicCanvas {
private settings: RenderingContextSettings = new RenderingContextSettings(true)
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
build() {
Column() {
Canvas(this.context)
.width('100%')
.height(300)
.backgroundColor('#f5f5f5')
.onReady(() => {
this.drawBasicShapes()
})
}
}
private drawBasicShapes() {
// 绘制矩形
this.context.fillStyle = '#ff6b6b'
this.context.fillRect(20, 20, 100, 60)
// 绘制圆形
this.context.fillStyle = '#4ecdc4'
this.context.beginPath()
this.context.arc(200, 50, 30, 0, Math.PI * 2)
this.context.fill()
// 绘制线条
this.context.strokeStyle = '#45b7d1'
this.context.lineWidth = 3
this.context.beginPath()
this.context.moveTo(20, 120)
this.context.lineTo(120, 120)
this.context.lineTo(70, 180)
this.context.closePath()
this.context.stroke()
// 绘制文本
this.context.font = '20px sans-serif'
this.context.fillStyle = '#2d3436'
this.context.fillText('Canvas绘图示例', 150, 150)
}
}
2. 绘制渐变和阴影
@Component
struct GradientCanvas {
private settings: RenderingContextSettings = new RenderingContextSettings(true)
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
build() {
Canvas(this.context)
.width('100%')
.height(300)
.onReady(() => {
this.drawGradients()
})
}
private drawGradients() {
// 线性渐变
let linearGradient = this.context.createLinearGradient(20, 20, 200, 20)
linearGradient.addColorStop(0, '#667eea')
linearGradient.addColorStop(1, '#764ba2')
this.context.fillStyle = linearGradient
this.context.fillRect(20, 20, 180, 80)
// 径向渐变
let radialGradient = this.context.createRadialGradient(150, 180, 10, 150, 180, 60)
radialGradient.addColorStop(0, '#f093fb')
radialGradient.addColorStop(1, '#f5576c')
this.context.fillStyle = radialGradient
this.context.fillRect(90, 120, 120, 120)
// 阴影效果
this.context.shadowColor = 'rgba(0, 0, 0, 0.3)'
this.context.shadowBlur = 10
this.context.shadowOffsetX = 5
this.context.shadowOffsetY = 5
this.context.fillStyle = '#4facfe'
this.context.fillRect(240, 100, 80, 80)
}
}
3. Canvas动画
@Component
struct AnimatedCanvas {
private settings: RenderingContextSettings = new RenderingContextSettings(true)
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
@State private ballX: number = 50
@State private ballY: number = 50
private ballSpeedX: number = 3
private ballSpeedY: number = 2
private timer: number = -1
build() {
Canvas(this.context)
.width('100%')
.height(400)
.backgroundColor('#1e3a8a')
.onReady(() => {
this.startAnimation()
})
}
aboutToDisappear() {
clearInterval(this.timer)
}
private startAnimation() {
this.timer = setInterval(() => {
this.updateBall()
this.draw()
}, 16) // 约60fps
}
private updateBall() {
// 更新位置
this.ballX += this.ballSpeedX
this.ballY += this.ballSpeedY
// 边界碰撞检测
if (this.ballX <= 20 || this.ballX >= 340) {
this.ballSpeedX = -this.ballSpeedX
}
if (this.ballY <= 20 || this.ballY >= 380) {
this.ballSpeedY = -this.ballSpeedY
}
}
private draw() {
// 清空画布
this.context.clearRect(0, 0, 360, 400)
// 绘制小球
this.context.fillStyle = '#fbbf24'
this.context.beginPath()
this.context.arc(this.ballX, this.ballY, 20, 0, Math.PI * 2)
this.context.fill()
// 添加光晕效果
let gradient = this.context.createRadialGradient(
this.ballX, this.ballY, 5,
this.ballX, this.ballY, 20
)
gradient.addColorStop(0, 'rgba(251, 191, 36, 1)')
gradient.addColorStop(1, 'rgba(251, 191, 36, 0.2)')
this.context.fillStyle = gradient
this.context.beginPath()
this.context.arc(this.ballX, this.ballY, 20, 0, Math.PI * 2)
this.context.fill()
}
}
4. 手势交互绘图
@Component
struct DrawingCanvas {
private settings: RenderingContextSettings = new RenderingContextSettings(true)
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
private isDrawing: boolean = false
private lastX: number = 0
private lastY: number = 0
@State private lineColor: string = '#000000'
@State private lineWidth: number = 3
build() {
Column({ space: 16 }) {
// 工具栏
Row({ space: 12 }) {
Text('粗细:')
Slider({
value: this.lineWidth,
min: 1,
max: 20,
step: 1
})
.width(120)
.onChange((value) => {
this.lineWidth = value
})
Button('清空')
.onClick(() => {
this.context.clearRect(0, 0, 360, 400)
})
}
.padding(12)
// 画布
Canvas(this.context)
.width('100%')
.height(400)
.backgroundColor(Color.White)
.border({ width: 1, color: '#e0e0e0' })
.onReady(() => {
this.initCanvas()
})
.onTouch((event) => {
this.handleTouch(event)
})
}
.padding(16)
}
private initCanvas() {
this.context.lineCap = 'round'
this.context.lineJoin = 'round'
}
private handleTouch(event: TouchEvent) {
const touch = event.touches[0]
const x = touch.x
const y = touch.y
if (event.type === TouchType.Down) {
this.isDrawing = true
this.lastX = x
this.lastY = y
} else if (event.type === TouchType.Move && this.isDrawing) {
this.drawLine(this.lastX, this.lastY, x, y)
this.lastX = x
this.lastY = y
} else if (event.type === TouchType.Up) {
this.isDrawing = false
}
}
private drawLine(x1: number, y1: number, x2: number, y2: number) {
this.context.strokeStyle = this.lineColor
this.context.lineWidth = this.lineWidth
this.context.beginPath()
this.context.moveTo(x1, y1)
this.context.lineTo(x2, y2)
this.context.stroke()
}
}
5. 绘制图表
interface ChartData {
label: string
value: number
color: string
}
@Component
struct BarChart {
private settings: RenderingContextSettings = new RenderingContextSettings(true)
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
private data: ChartData[] = [
{ label: '周一', value: 120, color: '#667eea' },
{ label: '周二', value: 200, color: '#764ba2' },
{ label: '周三', value: 150, color: '#f093fb' },
{ label: '周四', value: 180, color: '#4facfe' },
{ label: '周五', value: 220, color: '#00f2fe' }
]
build() {
Canvas(this.context)
.width('100%')
.height(300)
.backgroundColor(Color.White)
.onReady(() => {
this.drawChart()
})
}
private drawChart() {
const padding = 40
const chartWidth = 360 - padding * 2
const chartHeight = 300 - padding * 2
const barWidth = chartWidth / this.data.length - 10
const maxValue = Math.max(...this.data.map(d => d.value))
// 绘制坐标轴
this.context.strokeStyle = '#666'
this.context.lineWidth = 2
this.context.beginPath()
this.context.moveTo(padding, padding)
this.context.lineTo(padding, padding + chartHeight)
this.context.lineTo(padding + chartWidth, padding + chartHeight)
this.context.stroke()
// 绘制柱状图
this.data.forEach((item, index) => {
const barHeight = (item.value / maxValue) * chartHeight
const x = padding + index * (barWidth + 10) + 5
const y = padding + chartHeight - barHeight
// 绘制柱子
this.context.fillStyle = item.color
this.context.fillRect(x, y, barWidth, barHeight)
// 绘制标签
this.context.fillStyle = '#333'
this.context.font = '12px sans-serif'
this.context.textAlign = 'center'
this.context.fillText(item.label, x + barWidth / 2, padding + chartHeight + 20)
// 绘制数值
this.context.fillText(item.value.toString(), x + barWidth / 2, y - 5)
})
}
}
关键要点
- Canvas初始化: 必须创建RenderingContextSettings和CanvasRenderingContext2D
- 绘图时机: 在onReady回调中进行绘制操作
- 性能优化: 使用clearRect清空画布,避免重复绘制
- 动画实现: 使用setInterval配合clearRect实现流畅动画
- 手势交互: 通过onTouch事件处理用户绘图操作
最佳实践
- 资源管理: 组件销毁时清除定时器
- 性能优化: 复杂绘制使用离屏Canvas
- 坐标系: 注意Canvas坐标系原点在左上角
- 抗锯齿: 创建RenderingContextSettings时开启抗锯齿
- 响应式: 使用百分比宽度适配不同屏幕
更多关于HarmonyOS 鸿蒙Next中如何使用Canvas进行自定义绘图?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
在HarmonyOS Next中,使用Canvas进行自定义绘图需通过ArkTS实现。首先,在UI组件中创建CanvasRenderingContext2D对象,并设置画布属性。然后,调用绘图API如fillRect绘制矩形、strokeText绘制文本等。可通过Path2D定义复杂路径,使用fill或stroke方法渲染。支持渐变、阴影等效果,通过变换矩阵实现图形变换。
在HarmonyOS Next中,使用Canvas进行自定义绘图主要通过CanvasRenderingContext2D API实现。以下是核心步骤和关键方法:
1. 创建Canvas组件并获取绘图上下文
在ArkTS的UI中声明Canvas组件,并通过getContext('2d')获取2D绘图上下文:
Canvas()
.width('100%')
.height('100%')
.onReady((canvas: CanvasRenderingContext2D) => {
// 在此执行绘图操作
})
2. 基本绘图操作
- 路径绘制:使用
beginPath()、moveTo()、lineTo()、arc()等方法创建路径,通过stroke()或fill()渲染。 - 样式设置:通过
strokeStyle、fillStyle、lineWidth等属性设置颜色和线条样式。 - 图形绘制:直接使用
fillRect()、strokeRect()等方法绘制矩形。
3. 绘制复杂图形
组合使用路径命令绘制多边形、曲线等复杂形状。例如,使用quadraticCurveTo()或bezierCurveTo()绘制贝塞尔曲线。
4. 实现动画效果
在onReady回调中结合requestAnimationFrame实现逐帧动画:
let angle = 0;
function drawFrame(ctx: CanvasRenderingContext2D) {
ctx.clearRect(0, 0, width, height); // 清空画布
// 根据角度更新绘制内容
ctx.save();
ctx.translate(centerX, centerY);
ctx.rotate(angle);
// 绘制图形
ctx.restore();
angle += 0.02;
requestAnimationFrame(() => drawFrame(ctx)); // 循环下一帧
}
5. 手势交互处理
为Canvas组件绑定手势事件,如onTouch、onPinch等,在事件回调中更新绘图数据并触发重绘:
Canvas()
.onTouch((event: TouchEvent) => {
const x = event.touches[0].x;
const y = event.touches[0].y;
// 根据触摸位置更新图形参数
// 然后调用绘图函数
})
关键提示
- 使用
save()和restore()管理绘图状态,确保变换和样式不会相互干扰。 - 复杂动画建议使用
CanvasRenderingContext2D的变换方法(translate、rotate、scale)而非直接计算坐标。 - 性能敏感场景可考虑离屏绘制技术。
通过上述方法,你可以灵活实现从静态图形到交互式动画的各种Canvas绘图需求。

