模块一:数与代数 (1-10)
  1. 整数加减法数轴演示
  • 功能:在数轴上动态移动小人来演示加减法运算,支持负数运算,帮助理解“向右加、向左减”的几何意义。
    这是一个使用 HarmonyOS (ArkTS) 编写的教学应用项目。该项目通过可视化交互方式,帮助学生理解有理数加减法在数轴上的几何意义。
    项目名称:数轴上的加减法探险
    功能介绍:
    本应用提供了一个交互式数轴界面,屏幕上有一个可爱的“小人”代表当前数值位置。用户可以通过点击数字按钮选择移动步数,通过选择“加”或“减”决定运算方式。点击“移动”后,小人会以流畅的动画效果向右(加法)或向左(减法)移动,并实时显示运算算式和结果。特别针对负数运算,如“0 - 3”,小人会直观地向左跨越零点,帮助学生形象理解“减去一个数即向数轴负方向移动”的抽象概念。
    布局结构:
    使用 Stack 容器将 Canvas 绘制的数轴背景和动态移动的小人(Text 组件)叠加在一起。
    currentPos 是核心状态变量,表示小人在数轴上的逻辑位置(如 -3, 0, 5)。
    动画机制:
    小人的位置通过 .translate({ x: this.currentPos * this.unitWidth }) 进行偏移。
    . animation() 属性修饰在组件上,当 currentPos 状态改变时,系统会自动计算偏移量并生成平滑的过渡动画,模拟行走效果。
    教学逻辑:
    正向移动:点击“加法”,currentPos 增加,小人向右移动。
    负向移动:点击“减法”,currentPos 减少,小人向左移动。
    边界处理:设置了 -10 到 10 的边界,防止小人移出屏幕,并给予 Toast 提示。
    在这里插入图片描述
@Entry
@Component
struct NumberLineAdventure {
  // 状态变量:当前位置 (逻辑坐标,如 -5, 0, 3)
  @State currentPos: number = 0
  // 状态变量:移动步数
  @State moveSteps: number = 1
  // 状态变量:运算符号 (true为加法,false为减法)
  @State isAddition: boolean = true
  // 状态变量:动画执行状态
  @State isMoving: boolean = false
  // 动画相关常量
  private unitWidth: number = 30 // 数轴单位像素宽度
  private rangeMin: number = -10 // 数轴最小值
  private rangeMax: number = 10  // 数轴最大值
  // Canvas 上下文
  private settings: RenderingContextSettings = new RenderingContextSettings(true)
  private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)

  build() {
    Scroll() {
      Column({ space: 15 }) {
        // 标题区
        Text('数轴加减法演示')
          .fontSize(28)
          .fontWeight(FontWeight.Bold)

        Text('观察小人如何移动:向右加,向左减')
          .fontSize(14)
          .fontColor('#666')

      // ================= 核心可视化区域 =================
      Stack() {
        // 1. 背景:数轴刻度 (使用Canvas绘制)
        Canvas(this.context)
          .width('100%')
          .height(150)
          .backgroundColor('#F0F4F8')
          .onReady(() => {
            this.drawNumberLine()
          })

        // 2. 前景:移动的小人 (使用Stack定位)
        Row() {
          // 小人组件
          Column() {
            Text('🏃')
              .fontSize(30)
            Text(`${this.currentPos}`)
              .fontSize(14)
              .fontWeight(FontWeight.Bold)
              .fontColor('#FFFFFF')
              .backgroundColor('#FF5722')
              .borderRadius(10)
              .padding({ left: 6, right: 6, top: 2, bottom: 2 })
          }
          // 关键动画逻辑:根据 currentPos 计算偏移量
          .offset({ x: this.currentPos * this.unitWidth+20, y: 0 })
          .animation({ duration: 500, curve: Curve.EaseOut }) // 启用平滑动画
        }
        .width('100%')
        .height(150)
        .justifyContent(FlexAlign.Center)
        .alignItems(VerticalAlign.Top)
      }
      .width('100%')
      .height(150)
      .backgroundColor('#F0F4F8')
      .borderRadius(15)

      // ================= 运算结果显示 =================
      Column({ space: 10 }) {
        Text('当前位置:')
          .fontSize(14)
          .fontColor('#666')
        Text(`${this.currentPos}`)
          .fontSize(36)
          .fontWeight(FontWeight.Bold)
          .fontColor('#2196F3')

        Text('即将执行:')
          .fontSize(14)
          .fontColor('#666')
          .margin({ top: 10 })
        Text() {
          Span(`${this.currentPos}`)
            .fontSize(28)
            .fontWeight(FontWeight.Bold)
          Span(this.isAddition ? ' + ' : ' - ')
            .fontSize(28)
            .fontColor(this.isAddition ? '#4CAF50' : '#F44336')
          Span(`${this.moveSteps}`)
            .fontSize(28)
            .fontWeight(FontWeight.Bold)
          Span(' = ')
            .fontSize(24)
            .fontColor('#999')
          Span(`${this.isAddition ? this.currentPos + this.moveSteps : this.currentPos - this.moveSteps}`)
            .fontSize(28)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FF9800')
        }
      }
      .width('100%')
      .padding(15)
      .backgroundColor('#FFFFFF')
      .borderRadius(12)

      // ================= 交互控制区 =================
      Column({ space: 15 }) {
        // 符号选择
        Row({ space: 15 }) {
          Button('加法 (+)')
            .width('45%')
            .backgroundColor(this.isAddition ? '#4CAF50' : '#CCCCCC')
            .onClick(() => {
              this.isAddition = true
            })

          Button('减法 (-)')
            .width('45%')
            .backgroundColor(this.isAddition ? '#CCCCCC' : '#F44336')
            .onClick(() => {
              this.isAddition = false
            })
        }
        .width('100%')

        // 步数选择
        Text('选择移动步数:').fontSize(16)
        Grid() {
          ForEach([1, 2, 3, 4, 5], (num: number) => {
            GridItem() {
              Button(num.toString())
                .width(50)
                .height(40)
                .backgroundColor(this.moveSteps === num ? '#2196F3' : '#EEEEEE')
                .fontColor(this.moveSteps === num ? '#FFFFFF' : '#333333')
                .onClick(() => { this.moveSteps = num })
            }
          })
        }
        .columnsTemplate('1fr 1fr 1fr 1fr 1fr')
        .width('100%')

        // 执行按钮
        Button('开始移动')
          .width('100%')
          .height(50)
          .fontSize(20)
          .backgroundColor('#FF9800')
          .onClick(() => this.executeMove())
      }
      .width('100%')
      .padding(15)
      .backgroundColor('#FFFFFF')
      .borderRadius(12)

      // 重置按钮
      Button('重置位置')
        .width('100%')
        .height(40)
        .backgroundColor('#9E9E9E')
        .onClick(() => {
          this.currentPos = 0
        })

      // 提示信息
      Text('提示:负数运算时,小人会向左越过原点(0)。')
        .fontSize(12)
        .fontColor('#999')
        .margin({ top: 10 })

      }
      .width('100%')
      .padding(20)
      .backgroundColor('#F5F5F5')
    }
    .width('100%')
    .height('100%')
  }

  // 绘制数轴
  private drawNumberLine() {
    const ctx = this.context
    const width = 360 // 画布宽度
    const height = 150 // 画布高度
    const centerX = width / 2
    const centerY = height / 2 + 20

    // 清空画布
    ctx.clearRect(0, 0, width, height)

    // 绘制数轴线
    ctx.strokeStyle = '#333333'
    ctx.lineWidth = 2
    ctx.beginPath()
    ctx.moveTo(20, centerY)
    ctx.lineTo(width - 20, centerY)
    ctx.stroke()

    // 绘制箭头
    ctx.beginPath()
    ctx.moveTo(width - 20, centerY)
    ctx.lineTo(width - 30, centerY - 5)
    ctx.lineTo(width - 30, centerY + 5)
    ctx.closePath()
    ctx.fillStyle = '#333333'
    ctx.fill()

    // 绘制刻度和数字
    ctx.font = '14px sans-serif'
    ctx.textAlign = 'center'
    ctx.fillStyle = '#333333'

    for (let i = this.rangeMin; i <= this.rangeMax; i++) {
      const x = centerX + i * this.unitWidth

      // 绘制刻度线
      ctx.beginPath()
      ctx.moveTo(x, centerY - 8)
      ctx.lineTo(x, centerY + 8)
      ctx.strokeStyle = i === 0 ? '#FF5722' : '#333333'
      ctx.lineWidth = i === 0 ? 3 : 2
      ctx.stroke()

      // 绘制数字
      ctx.fillStyle = i === 0 ? '#FF5722' : '#333333'
      ctx.font = i === 0 ? 'bold 16px sans-serif' : '14px sans-serif'
      ctx.fillText(i.toString(), x, centerY + 30)
    }

    // 绘制原点标记
    ctx.beginPath()
    ctx.arc(centerX, centerY, 4, 0, Math.PI * 2)
    ctx.fillStyle = '#FF5722'
    ctx.fill()
  }

  // 执行移动逻辑
  private executeMove() {
    if (this.isMoving) return

    let targetPos = this.currentPos
    if (this.isAddition) {
      targetPos += this.moveSteps
    } else {
      targetPos -= this.moveSteps
    }

    // 边界检查
    if (targetPos < this.rangeMin || targetPos > this.rangeMax) {
      // 超出范围不执行
      return
    }

    this.isMoving = true
    // 更新状态,触发动画
    this.currentPos = targetPos

    // 动画结束后重置状态
    setTimeout(() => {
      this.isMoving = false
    }, 600)
  }
}
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐