HarmonyOS canvas 横竖屏切换问题?

横竖屏切换后,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%')
  }
}
阅读 584
1 个回答

因为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枚举说明