横竖屏切换后,canvas无法正常显示,麻烦帮忙看一下
见demo文件
import { mediaquery,window } from '@kit.ArkUI';
import { ArrayList, JSON } from '@kit.ArkTS'
import { Animator, AnimatorResult, MeasureText } from '@kit.ArkUI';
interface TtsLineData {
ttsWidth: number; //最大宽度
ttsHeight: number; //最大宽度
ttsX: number
ttsY: number
ttsStartIndex: number
ttsEndIndex: number
text: string //文字
delta: number
}
@ComponentV2
export struct TtsComponent {
@Param @Require progress: number // 0~1.0
@Param @Require text: string
@Param textSize = 14
@Param textLines = 3
@Param textColor = "#ffffff"
@Param textHighlightColor = "#00000000"
@Local offsetY: number = 0
@Event sizeChange: (width: number, height: number) => void
private isFinished: boolean = false;
//用来配置CanvasRenderingContext2D对象的参数,包括是否开启抗锯齿,true表明开启抗锯齿。
private settings: RenderingContextSettings = new RenderingContextSettings(true)
//用来创建CanvasRenderingContext2D对象,通过在canvas中调用CanvasRenderingContext2D对象来绘制。
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
private viewWidth: number = 0
private viewHeight: number = 0
// private contentWidth = 0
private contentHeight = 0
// private startOffsetX = 0
private startOffsetY = 0
private fontWeight = 'bold';
private fontFamily = 'HarmonyHeiTi';
private canScroll = true
private isInTouched = false
// 超过边界功能不太丝滑,先不管了吧
// 超过边界距离
private overScrollDis = 0;
// 超过边界回滚时间
private duration = 0;
// 平滑滚动时间
private smoothDuration = 500;
// 任务ID
private timeOutTaskId = -1
/**
* tts分割数据
*/
private ttsData = new ArrayList<TtsLineData>()
/**
* 分割后的tts文字总长度
*/
private ttsLength = 0
/**
* 动画
*/
private animator: AnimatorResult | undefined;
/**
* 进度监听
*/
@Monitor("progress")
onProgressChanged() {
// console.log("onProgress==" + this.progress)
if (this.progress > 1.0) {
this.isFinished = true
} else {
this.isFinished = false
}
this.draw()
}
@Monitor("offsetY")
onOffsetChanged() {
// console.log("onOffsetChanged==" + this.offsetY)
this.draw()
}
/**
* 文本监听
*/
@Monitor("text")
onTextChanged() {
this.draw()
}
/**
* 绘制
*/
private draw() {
// this.context.fillRect(0, 30, 100, 100)
// this.context.fillText("hello world", 10, 10, 100)
this.context.clearRect(0, 0, this.viewWidth, this.viewHeight)
console.log("currentIndex:" + this.progress + ",offset:" + this.offsetY)
// 绘制view背景
// this.drawRoundedRect(0, 0, this.viewWidth, this.viewHeight, this.radius, this.backGroundColor)
// // 绘制content背景
// this.drawRoundedRect(this.paddingLeft, this.paddingTop, this.contentRectWidth, this.contentRectHeight, this.radius,
// this.contentColor)
// 对画布进行裁剪 (超过content宽高的其他内容,都被裁剪掉)
this.context.save()
this.context.beginPath()
this.context.rect(0, 0, this.viewWidth, this.viewHeight)
this.context.closePath()
this.context.clip()
// 绘制文字
let progress = this.progress
if (this.isFinished) {
progress = 1.0
}
let currentIndex = progress * this.ttsLength
for (let ttsDataElement of this.ttsData) {
if (ttsDataElement.ttsStartIndex < currentIndex && currentIndex <= ttsDataElement.ttsEndIndex) { // 正在读
this.context.save()
this.context.fillStyle = this.textColor
this.context.fillText(ttsDataElement.text, ttsDataElement.ttsX, ttsDataElement.ttsY + this.offsetY,
ttsDataElement.ttsWidth)
this.context.restore()
} else if (ttsDataElement.ttsEndIndex < currentIndex) { // 已读
this.context.save()
this.context.fillStyle = this.textHighlightColor
this.context.fillText(ttsDataElement.text, ttsDataElement.ttsX, ttsDataElement.ttsY + this.offsetY,
ttsDataElement.ttsWidth)
this.context.restore()
} else { // 未读
this.context.save()
this.context.fillStyle = this.textColor
this.context.fillText(ttsDataElement.text, ttsDataElement.ttsX, ttsDataElement.ttsY + this.offsetY,
ttsDataElement.ttsWidth)
this.context.restore()
}
}
// 绘制进度
this.drawProgress()
}
private drawProgress() {
let progress = this.progress
if (this.isFinished) {
progress = 1.0
}
let currentIndex = progress * this.ttsLength
for (let ttsDataElement of this.ttsData) {
if (currentIndex >= ttsDataElement.ttsStartIndex && currentIndex <= ttsDataElement.ttsEndIndex) { // 正在读
this.context.save()
// 进度
let progress =
(currentIndex - ttsDataElement.ttsStartIndex) / (ttsDataElement.ttsEndIndex - ttsDataElement.ttsStartIndex)
let currentWidth = ttsDataElement.ttsWidth * progress
// console.log("drawProgress --->" + currentIndex + ",-->" + currentWidth)
this.context.beginPath()
this.context.rect(ttsDataElement.ttsX,
ttsDataElement.ttsY - ttsDataElement.ttsHeight + this.offsetY + ttsDataElement.delta, currentWidth,
ttsDataElement.ttsHeight)
this.context.closePath()
this.context.clip()
this.context.fillStyle = this.textHighlightColor
this.context.fillText(ttsDataElement.text, ttsDataElement.ttsX, ttsDataElement.ttsY + this.offsetY,
ttsDataElement.ttsWidth)
// this.context.fillRect(ttsDataElement.ttsX,
// ttsDataElement.ttsY - ttsDataElement.ttsHeight + this.offsetY + ttsDataElement.delta, currentWidth,
// ttsDataElement.ttsHeight)
this.context.restore()
// 自动滚动画布(单行滚动)
if (!this.isInTouched && !this.animator) {
// console.debug("跟随滚动")
this.smoothScrollTo(this.getOffsetY(ttsDataElement, currentIndex), this.smoothDuration, "smooth")
}
}
}
}
private calOffsetY(): number {
let currentIndex = this.progress * this.ttsLength
// console.log("drawProgress->currentIndex:" + currentIndex)
for (let ttsDataElement of this.ttsData) {
if (currentIndex >= ttsDataElement.ttsStartIndex && currentIndex <= ttsDataElement.ttsEndIndex) { // 正在读
return this.getOffsetY(ttsDataElement, currentIndex)
}
}
return 0
}
private getOffsetY(ttsDataElement: TtsLineData, currentIndex: number) {
let halfViewHeight = this.viewHeight / 2.0
let progress =
(currentIndex - ttsDataElement.ttsStartIndex) / (ttsDataElement.ttsEndIndex - ttsDataElement.ttsStartIndex)
if (!this.canScroll) {
return 0
}
if ((ttsDataElement.ttsY - ttsDataElement.ttsHeight) > halfViewHeight) {
let offY = halfViewHeight - ttsDataElement.ttsY + ttsDataElement.ttsHeight - (ttsDataElement.ttsHeight * progress)
if (offY < (-this.contentHeight + this.viewHeight)) {
return (-this.contentHeight + this.viewHeight)
} else {
return offY
}
} else {
return 0
}
}
// 平滑滚动
private smoothScrollTo(desOffsetY: number, duration: number, easing: string) {
if (this.animator) {
this.animator.finish();
this.animator = undefined
}
this.animator = Animator.create({
duration: duration,
easing: easing,
delay: 0,
fill: 'forwards',
direction: 'normal',
iterations: 1,
begin: this.offsetY,
end: desOffsetY
});
// console.info('[smoothScrollTo] curOffset:' + this.offsetY + ',desOffset:' + desOffsetY + ",duration:" + duration);
this.animator.onFrame = (value) => {
// console.info('onframe:' + value);
this.offsetY = value
};
this.animator.onCancel = () => {
// console.log("oncancel:" + this.offsetY)
this.offsetY = 0
};
this.animator.onFinish = () => {
this.animator = undefined
};
this.animator.play();
}
private judgeEdge(dest: number): number {
if (dest <= -this.contentHeight - this.overScrollDis + this.viewHeight) {
dest = -this.contentHeight - this.overScrollDis + this.viewHeight
} else if (dest > this.overScrollDis) {
dest = this.overScrollDis
}
return dest
}
onMeasureSize(selfLayoutInfo: GeometryInfo, children: Measurable[], constraint: ConstraintSizeOptions): SizeResult {
console.log("tts component - onMeasureSize" + JSON.stringify(selfLayoutInfo) + JSON.stringify(constraint))
console.log("onMeasureSize - child:" + JSON.stringify(children))
console.log("onMeasureSize - child:" + children.length)
let top: number = 0
let right: number = 0
let left: number = 0
let bottom: number = 0
if (selfLayoutInfo.padding.top) {
top = Number.parseFloat(selfLayoutInfo.padding.top!.toLocaleString())
}
if (selfLayoutInfo.padding.right) {
right = Number.parseFloat(selfLayoutInfo.padding.right!.toLocaleString())
}
if (selfLayoutInfo.padding.left) {
left = Number.parseFloat(selfLayoutInfo.padding.left!.toLocaleString())
}
if (selfLayoutInfo.padding.bottom) {
bottom = Number.parseFloat(selfLayoutInfo.padding.bottom!.toLocaleString())
}
this.context.font = this.fontWeight + ' ' + this.textSize + 'vp ' + this.fontFamily;
this.context.fillStyle = this.textColor;
let width = selfLayoutInfo.width
if (constraint.maxWidth) {
width = Number.parseFloat(constraint.maxWidth.toLocaleString())
}
// console.log("size:" + JSON.stringify(size))
let maxWidth = width - (left + bottom)
let words = this.text.split('\n');
this.ttsLength = 0
this.ttsData.clear()
if (this.animator) {
this.animator.finish()
this.animator = undefined
}
if (this.timeOutTaskId != -1) {
clearTimeout(this.timeOutTaskId)
this.timeOutTaskId = -1
}
let metrics = this.context.measureText("testLine");
let y = metrics.fontBoundingBoxAscent
for (let i = 0; i < words.length; i++) {
let line = words[i];
let wordOnLine = ""
let lineStart = this.ttsLength
let lineEnd = this.ttsLength
for (let n = 0; n < line.length; n++) {
let testLine = wordOnLine + line[n] + ' ';
let metrics = this.context.measureText(testLine);
// console.log("TAG----->" + testLine + "---->" + JSON.stringify(metrics))
let testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
this.ttsData.add({
ttsWidth: testWidth,
ttsHeight: metrics.height,
text: wordOnLine,
ttsStartIndex: lineStart,
ttsEndIndex: lineEnd,
ttsX: 0,
ttsY: y,
delta: metrics.fontBoundingBoxDescent
})
y += metrics.height;
wordOnLine = line[n] + ' ';
lineStart = lineEnd
} else {
lineEnd++
this.ttsLength = lineEnd
wordOnLine = testLine;
}
if (n == line.length - 1) {
this.ttsData.add({
ttsWidth: testWidth,
ttsHeight: metrics.height,
text: wordOnLine,
ttsStartIndex: lineStart,
ttsEndIndex: lineEnd,
ttsX: 0,
ttsY: y,
delta: metrics.fontBoundingBoxDescent
})
y += metrics.height;
}
}
}
y += metrics.fontBoundingBoxDescent - metrics.height
if (y < this.viewHeight) {
this.contentHeight = y
this.canScroll = false;
} else {
this.contentHeight = y
this.canScroll = true;
}
this.viewWidth = maxWidth
this.viewHeight = Math.abs(metrics.height) * (this.textLines)
// console.log("ttsData:" + JSON.stringify(this.ttsData))
children.forEach((child) => {
child.measure({
minWidth: this.viewWidth,
maxWidth: this.viewWidth,
minHeight: this.viewHeight,
maxHeight: this.viewHeight,
})
})
console.log("width:" + (this.viewWidth + (left + right)))
console.log("height:" + (this.viewHeight + (top + bottom)))
return {
width: this.viewWidth + (left + right),
height: this.viewHeight + (top + bottom)
}
}
onPlaceChildren(selfLayoutInfo: GeometryInfo, children: Array<Layoutable>, constraint: ConstraintSizeOptions) {
console.log("child-onPlaceChildren" + JSON.stringify(selfLayoutInfo))
children.forEach((child) => {
child.layout({ x: 0, y: 0 })
})
}
build() {
Canvas(this.context)
.onReady(() => {
console.log("onReady")
this.draw()
})
.gesture(PanGesture({ direction: PanDirection.Vertical })
.onActionStart((ev) => {
// this.context.fillRect(0, 30, 100, 100)
// this.context.fillText("hello world", 10, 10, 100)
// console.log("onActionStart" + JSON.stringify(ev))
this.startOffsetY = ev.offsetY
if (this.animator) {
this.animator.finish();
this.animator = undefined
}
this.isInTouched = true
if (this.timeOutTaskId != -1) {
clearTimeout(this.timeOutTaskId)
this.timeOutTaskId = -1
}
})
.onActionUpdate((ev) => { // 直接滚动
// console.log("onActionUpdate" + JSON.stringify(ev))
if (!this.canScroll) {
return
}
let dest = this.offsetY + (ev.offsetY - this.startOffsetY)
this.offsetY = this.judgeEdge(dest)
// console.log("update-->offsetY-->" + this.offsetY)
this.startOffsetY = ev.offsetY
}).onActionEnd((ev) => { // 惯性滚动、回滚
// console.log("onActionEnd" + JSON.stringify(ev))
if (!this.canScroll) {
return
}
let dest = this.judgeEdge(this.offsetY + (ev.velocityY + 0) * (this.smoothDuration / 1000.0) * 0.5)
// 滚动后,1s 后自动回滚
if (!this.animator) {
this.timeOutTaskId = setTimeout(() => {
// console.debug("回滚啦")
this.isInTouched = false
this.smoothScrollTo(this.calOffsetY(), this.smoothDuration, "fast-out-slow-in")
}, this.smoothDuration + 500)
}
// console.log("开始惯性滑动:" + dest)
// 惯性滑动
this.smoothScrollTo(dest, this.smoothDuration, "fast-out-slow-in")
}))
}
}
@Entry
@ComponentV2
struct Index {
pageStack: NavPathStack = new NavPathStack()
@Local progress:number = 0
@Local ttsText:string = "hello world - 竖屏1\n" +
"hello world - 竖屏2\n" +
"hello world - 竖屏3\n" +
"hello world - 竖屏4\n" +
"hello world - 竖屏5\n" +
"hello world - 竖屏6\n" +
"hello world - 竖屏7\n"
orientation: window.Orientation = window.Orientation.PORTRAIT
startCountDown() {
console.log("startCountDown:" + this.progress)
if (this.progress <= 1.0) {
this.progress += 0.05
} else {
return
}
setTimeout(() => {
this.startCountDown()
}, 200)
}
private listener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync('(orientation: landscape)');
aboutToAppear(): void {
this.listener.on('change', (mediaQueryResult: mediaquery.MediaQueryResult) => {
console.log("change:" + JSON.stringify(mediaQueryResult))
if (mediaQueryResult.matches) {
this.orientation = window.Orientation.LANDSCAPE
} else {
this.orientation = window.Orientation.PORTRAIT
}
this.ttsText = "hello world - 竖屏1\n" +
"hello world - 竖屏2\n" +
"hello world - 竖屏3\n" +
"hello world - 竖屏4\n" +
"hello world - 竖屏5\n" +
"hello world - 竖屏6\n" +
"hello world - 竖屏7\n" + Math.random()
});
}
build() {
Navigation(this.pageStack) {
Column() {
Column() {
Button('进度条').onClick((event: ClickEvent) => {
this.progress = 0
this.startCountDown()
})
Button('切换屏幕').onClick((event: ClickEvent) => {
let mWindow = window.getLastWindow(getContext(this))
mWindow.then(async win => {
let orien = 1
if (this.orientation == 1) {
orien = 2
}
await win.setPreferredOrientation(orien)
})
})
TtsComponent({
progress: this.progress,
textLines:3,
text: this.ttsText,
textColor: "#ff00ff",
textHighlightColor: "#ffffff"
}) .backgroundColor("#ffff00")
.borderRadius(10)
.padding(10)
}
.height('100%')
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
.width('100%')
}
.title('Main')
.height('100%')
.width('100%')
}
}
因为Navigation组件通过mode属性设置页面的显示模式,默认为自适应模式,自适应模式下,当页面宽度大于等于一定阈值( API version 9及以前:520vp,API version 10及以后:600vp )时,Navigation组件采用分栏模式,反之采用单栏模式。
可以将mode属性设置为NavigationMode.Stack,Navigation组件即可设置为单页面显示模式。此时Navigation组件导航栏与内容区独立显示,相当于两个页面。
或者不使用Navigation组件
对应文档链接:
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/cta-usage-of-the-navigation-component-V5\#设置页面显示模式
https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-basic-components-navigation-V5\#navigationmode9枚举说明