HarmonyOS 鸿蒙Next中使用Canvas绘制折线图
HarmonyOS 鸿蒙Next中使用Canvas绘制折线图
通过PinchGesture进行缩放,缩放渲染过程中会出现闪烁(缩放越大越闪烁),这个问题应该如何解决 缩放过程中,如果内容宽度大于一屏,当前显示的位置要始终居中,又该怎么去写?
@Component
struct DrawCanvas8 {
@Consume pathStack: NavPathStack
// 获取上下文
private settings: RenderingContextSettings = new RenderingContextSettings(true)
private context2d: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
private context2daxis: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
private context2dline: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
private drawTotalScroll: Scroller = new Scroller()
@Prop private comp_width: number = 0
@Prop private comp_height: number = 0
@State private scroll_width: number = 0
@State private dataArray: number[] = []
@State scaleValue: number = 1
@State pinchValue: number = 1
@State pinchX: number = 0
@State pinchY: number = 0
aboutToAppear() {
this.loadData()
}
build() {
NavDestination() {
Column() {
Stack() {
Canvas(this.context2d)
.width('100%')
.height('100%')
.id('bgcanvas')
.onReady(() => {
const width = this.context2d.width
const height = this.context2d.height
this.context2d.strokeStyle = '#DCDCDC'
this.context2d.lineWidth = 1
this.context2d.strokeRect(0, 0, width, height)
for (let i = 1; i < 4; i++) {
this.context2d.lineWidth = 0.5
this.context2d.strokeStyle = '#F2F2F2'
this.context2d.moveTo(0, height / 4 * i)
this.context2d.lineTo(width, height / 4 * i)
}
this.context2d.stroke()
this.comp_width = width
this.comp_height = height
})
Canvas(this.context2daxis)
.id('left')
.width('100%')
.height('100%')
.onReady(() => {
})
Scroll(this.drawTotalScroll) {
Column({ space: 0 }) {
Canvas(this.context2dline)
.id('lines')
.width('100%')
.height('100%')
.onReady(() => {
})
}
.height('100%')
.width(this.scroll_width)
}
.id('totalscroll')
.width('100%')
.height('100%')
.hitTestBehavior(HitTestMode.Transparent)
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.onDidScroll(() => {
})
.gesture(
PinchGesture({ fingers: 2 })
.onActionStart((event: GestureEvent) => {
console.info('Pinch start')
})
.onActionUpdate((event: GestureEvent) => {
if (event) {
this.scaleValue = this.pinchValue * event.scale
this.pinchX = event.pinchCenterX
this.pinchY = event.pinchCenterY
this.draw()
}
})
.onActionEnd((event: GestureEvent) => {
this.pinchValue = this.scaleValue
console.info('Pinch end')
})
)
}
.width('100%')
.height(300)
.padding(10)
}
.width('100%')
.height('100%')
}
}
private loadData() {
setTimeout(() => {
let tempArray: number[] = []
for (let index = 0; index < 100; index++) {
tempArray.push(getRandomNumber(200))
}
this.dataArray = tempArray
this.scroll_width = tempArray.length * 10;
this.draw()
}, 500)
}
private draw() {
const min = Math.min(...this.dataArray.map((item: number) => item))
const max = Math.max(...this.dataArray.map((item: number) => item))
//Y轴
this.context2daxis.reset()
let sepValue = (max - min) / 4
let yValues: number[] = []
this.context2daxis.font = '8vp'
this.context2daxis.fillStyle = '#333333'
for (let index = 0; index < 5; index++) {
let y = min + index * sepValue;
if (index == 0) {
this.context2daxis.textBaseline = 'bottom'
} else if (index == 4) {
this.context2daxis.textBaseline = 'top'
} else {
this.context2daxis.textBaseline = 'middle'
}
this.context2daxis.fillText(y.toFixed(2), 0, this.comp_height / 4 * (4 -index))
yValues.push(y)
}
const w = 10 * this.scaleValue
let total_width = w * this.dataArray.length
if (total_width < this.comp_width) {
this.scroll_width = this.comp_width
} else {
this.scroll_width = total_width
}
this.context2dline.reset()
this.context2dline.beginPath()
this.context2dline.strokeStyle = '#FF0000'
this.context2dline.lineWidth = 1
for (let index = 0; index < this.dataArray.length; index++) {
const element = this.dataArray[index];
let x = w * index
let y = this.comp_height * (element - min) / (max - min)
if (index == 0) {
this.context2dline.moveTo(x, y)
} else {
this.context2dline.lineTo(x, y)
}
}
this.context2dline.stroke()
}
}
更多关于HarmonyOS 鸿蒙Next中使用Canvas绘制折线图的实战教程也可以访问 https://www.itying.com/category-93-b0.html
开发者您好,可以采取以下方式解决:
【背景知识】
Canvas
画布的缩放也就是大小改变的时候,之前在画布绘制的图形会消失,导致重新绘制。
【定位思路】
containerPinchGestureUpdate缩放监听,updateScale/updateContainerOffset手势数据处理,updateContainerOffset缩放绘制方法等一个或多个条件满足时之前绘制的图形会消失,这样就会导致频繁的绘制图形,造成卡顿。
【解决方案】
可以设置一个临界点,在手势捏合持续回调中,只有满足一定条件才重新绘制,尽可能的减少捏合手势持续调用绘制内容,通过捏合比例大于某个值得时候才重新绘制。
代码示例如下:
if (Math.abs(offsetX - this.lastOffsetX) < 0.5 && Math.abs(offsetY - this.lastOffsetY) < 0.5) {
return
}
this.lastOffsetX = offsetX
this.lastOffsetY = offsetY
this.context.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
if (this.useTransform) {
this.context.setTransform(1, 0, 0, 1, 0, 0);
this.context.setTransform(scale, 0, 0, scale, 0, 0);
}
【常见FAQ】
Q:画布组件放大会导致app闪退是什么原因? A:每次放大画布组件都会频繁触发PinchGesture().onActionUpdate()回调,回调中的containerPinchGestureUpdate(event)方法的调用链中的containerScaleDidUpdate方法中的语句。
// 注释掉改行,建议不要每次都创建新的离屏画布
this.offCanvas = new OffscreenCanvas(this.canvasWidth, this.canvasHeight)
Q:Canvas画线超出屏幕长度后,画布一直闪烁是什么原因? A:动态画线每次更新一条线都要修改Canvas宽高,这样性能消耗很高,闪烁属于正常现象。建议给Canvas预设宽高,当画线达到后在设置。
if (this.x>= this.canvasWidth) {
this.canvasWidth = this.screenWidth*4+this.x
}
更多关于HarmonyOS 鸿蒙Next中使用Canvas绘制折线图的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
为什么不用arkweb 渲染echart,这样自定义起来非常方便
刚接触鸿蒙,不知道怎么用呢,之前用的也是单纯绘制,所以就按照这种方式写了,
找HarmonyOS工作还需要会Flutter技术的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:BV1S4411E7LY/?p=17
现阶段鸿蒙的报表组件还不完善,在业务开发中为了快速实现数据可视化的效果,可以使用arkWeb加载H5的形式实现报表,这样报表就可以使用各种各样的js库了,例如echart,可以看一下这篇文章这篇文章。
也能画股票的K线图吗,
在HarmonyOS鸿蒙Next中使用Canvas绘制折线图的步骤如下:
- 创建Canvas组件并设置宽高
- 获取CanvasRenderingContext2D对象
- 使用moveTo和lineTo方法绘制折线
- 设置strokeStyle和lineWidth属性定义线条样式
- 调用stroke方法完成绘制
关键代码示例:
// 创建Canvas
Canvas(this.context)
.width('100%')
.height('100%')
// 绘制逻辑
const ctx = this.context.getContext('2d')
ctx.moveTo(x1, y1)
ctx.lineTo(x2, y2)
ctx.stroke()
注意坐标数据需自行计算处理。
针对HarmonyOS Next中使用Canvas绘制折线图的缩放闪烁问题,建议从以下方面优化:
- 闪烁问题解决方案:
- 使用双缓冲技术:在缩放时先将内容绘制到离屏Canvas,再一次性渲染到主Canvas
- 限制缩放频率:添加节流逻辑,避免频繁重绘
- 优化绘制逻辑:在draw()方法中减少不必要的reset()调用
- 居中显示实现方案:
- 在onActionUpdate回调中计算当前视口中心点
- 根据缩放比例调整scrollTo参数
- 使用Scroller的scrollTo方法实现精确定位
核心修改点示例:
.onActionUpdate((event: GestureEvent) => {
if (event) {
// 节流处理
const now = Date.now();
if (now - this.lastScaleTime < 16) return; // 60fps
// 计算缩放中心偏移
const centerOffsetX = (event.pinchCenterX / this.comp_width) * this.scroll_width;
this.scaleValue = this.pinchValue * event.scale;
this.draw();
// 保持居中
const newWidth = 10 * this.scaleValue * this.dataArray.length;
const scrollX = centerOffsetX * (newWidth / this.scroll_width) - event.pinchCenterX;
this.drawTotalScroll.scrollTo({x: scrollX, y: 0});
this.lastScaleTime = now;
}
})
- 性能优化建议:
- 对大数据集进行分段绘制
- 使用Path2D对象缓存路径数据
- 避免在缩放过程中频繁计算极值(min/max)
这些修改可以有效减少闪烁并保持居中显示效果。