应用实例二:勾股定理交互证明

知识点:第十七章《勾股定理》—— 勾股定理的证明(赵爽弦图)。
功能:经典的“赵爽弦图”拼图演示。屏幕上展示四个全等的直角三角形围成一个大正方形。学生拖动三角形重组位置,直观验证“大正方形面积 = 四个小三角形 + 中间小正方形”,即 c2=a2+b2c^2 = a^2 + b^2c2=a2+b2
在这里插入图片描述

/**
 * 勾股定理证明 - 赵爽弦图(组件化拼图版)
 * 核心原理:c² = 4 * (1/2 * a * b) + (b - a)²
 */

interface Point {
  x: number;
  y: number;
}

// 向量接口
interface Vector {
  x: number;
  y: number;
}

// 三角形数据模型
interface PieceModel {
  id: number;
  color: string;
  // 三角形顶点路径
  points: Point[];
  // 初始位置偏移
  initOffset: Point;
  // 当前位置偏移
  currentOffset: Point;
  // 旋转角度
  rotation: number;
}

@Entry
@Component
struct PythagoreanProofV2 {
  // 基础参数:直角三角形边长 (经典 3:4:5 比例放大)
  private readonly A: number = 75;  // 勾
  private readonly B: number = 100; // 股
  private readonly C: number = 125; // 弦

  // 画布参数
  private readonly CANVAS_SIZE: number = 360;
  private readonly CENTER: number = 180;

  // 状态:是否打乱
  @State isScrambled: boolean = false;
  // 状态:四个拼图块的数据
  @State pieces: PieceModel[] = [];

  aboutToAppear() {
    this.initPieces();
  }

  build() {
    Column() {
      // 标题区
      Row() {
        Text('📐 勾股定理证明')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor('#2C3E50')
      }
      .margin({ bottom: 10 })

      Text('赵爽弦图:拖动三角形观察中间的小正方形')
        .fontSize(14)
        .fontColor('#7F8C8D')
        .margin({ bottom: 15 })

      // 游戏区域
      Stack() {
        // 1. 底层:绘制网格和中心小正方形
        Canvas(this.context)
          .width(this.CANVAS_SIZE)
          .height(this.CANVAS_SIZE)
          .backgroundColor('#FFFFFF')
          .borderRadius(12)
          .onReady(() => {
            this.drawBackground();
          })

        // 2. 上层:四个可拖拽的三角形组件
        ForEach(this.pieces, (piece: PieceModel, index: number) => {
          this.TrianglePiece(piece, index)
        })
      }
      .width(this.CANVAS_SIZE)
      .height(this.CANVAS_SIZE)
      .clip(true) // 限制在区域内

      // 数学原理面板
      Column() {
        this.MathPanel()
      }
      .width('92%')
      .padding(15)
      .margin({ top: 20 })
      .backgroundColor('#FFFFFF')
      .borderRadius(12)
      .shadow({ radius: 8, color: '#00000015', offsetY: 2 })

      // 控制按钮
      Row() {
        Button(this.isScrambled ? '复原拼图' : '打乱演示')
          .onClick(() => this.toggleScramble())
          .backgroundColor(this.isScrambled ? '#27AE60' : '#3498DB')
          .width('45%')

        Button('重置数据')
          .onClick(() => this.initPieces())
          .backgroundColor('#95A5A6')
          .width('45%')
          .margin({ left: '5%' })
      }
      .width('92%')
      .margin({ top: 15 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F0F4F8')
  }

  // ------------------ 组件构建区 ------------------

  // 单个三角形拼图块
  @Builder
  TrianglePiece(piece: PieceModel, index: number) {
    // 使用 Stack 包装 Shape,实现绝对定位
    Stack() {
      Shape() {
        Path()
        // 根据三个顶点绘制三角形
          .commands(this.getTrianglePath(piece.points))
          .fill(piece.color)
          .stroke(Color.White)
          .strokeWidth(2)
          .opacity(0.9)

        // 绘制直角标记 (小方块)
        if (index === 0) { // 只在一个三角形上画直角标记演示
          Path()
            .commands(this.getRightAngleMarker(piece.points))
            .stroke(Color.White)
            .strokeWidth(1.5)
            .fill(Color.Transparent)
        }
      }
      .width(this.CANVAS_SIZE) // 给 Shape 足够大的画布
      .height(this.CANVAS_SIZE)
      .viewPort({ x: 0, y: 0, width: this.CANVAS_SIZE, height: this.CANVAS_SIZE })
    }
    .width(this.CANVAS_SIZE)
    .height(this.CANVAS_SIZE)
    // 绝对定位:初始位置 + 当前拖拽偏移
    .position({
      x: piece.initOffset.x + piece.currentOffset.x,
      y: piece.initOffset.y + piece.currentOffset.y
    })
    .rotate({ angle: piece.rotation }) // 旋转
    .gesture(
      PanGesture()
        .onActionUpdate((e: GestureEvent) => {
          // 更新当前位置
          this.pieces[index].currentOffset = {
            x: piece.currentOffset.x + e.offsetX,
            y: piece.currentOffset.y + e.offsetY
          };
        })
    )
    .animation({ duration: 100, curve: Curve.Linear })
  }

  // 数学证明面板
  @Builder
  MathPanel() {
    Column() {
      Text('面积验证 (以 a=3, b=4, c=5 为例)')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 10 })

      Divider().margin({ bottom: 8 })

      // 公式展示
      Row() {
        Text('大正方形面积 (弦²): ')
          .fontSize(14)
        Text(`c² = ${this.C}² = ${this.C * this.C}`)
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor('#E74C3C')
      }.width('100%').justifyContent(FlexAlign.SpaceBetween)

      Row() {
        Text('4个三角形面积: ')
          .fontSize(14)
        Text(`4 × (½ × ${this.A} × ${this.B}) = ${2 * this.A * this.B}`)
          .fontSize(14)
          .fontWeight(FontWeight.Medium)
      }.width('100%').justifyContent(FlexAlign.SpaceBetween).margin({ top: 5 })

      Row() {
        Text('小正方形面积 (股-勾)²: ')
          .fontSize(14)
        Text(`(${this.B - this.A})² = ${(this.B - this.A) * (this.B - this.A)}`)
          .fontSize(14)
          .fontColor('#F1C40F')
          .fontWeight(FontWeight.Medium)
      }.width('100%').justifyContent(FlexAlign.SpaceBetween).margin({ top: 5 })

      Divider().margin({ top: 10, bottom: 10 })

      // 证明结论
      Column() {
        Text('证明推导:')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
        Text('大正方形 = 4个三角形 + 小正方形')
          .fontSize(14)
          .margin({ top: 4 })
        Text('c² = 2ab + (b-a)²')
          .fontSize(16)
          .fontColor('#8E44AD')
          .margin({ top: 4 })
        Text('c² = 2ab + b² - 2ab + a²')
          .fontSize(14)
          .margin({ top: 2 })
        Text('c² = a² + b²')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor('#27AE60')
          .margin({ top: 6 })
      }
      .width('100%')
      .alignItems(HorizontalAlign.Start)
      .padding(10)
      .backgroundColor('#F9F9F9')
      .borderRadius(8)
    }
    .width('100%')
    .alignItems(HorizontalAlign.Start)
  }

  // ------------------ 逻辑处理区 ------------------

  private settings: RenderingContextSettings = new RenderingContextSettings(true)
  private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)

  // 绘制背景:网格 + 中心小正方形
  private drawBackground() {
    const ctx = this.context;
    ctx.clearRect(0, 0, this.CANVAS_SIZE, this.CANVAS_SIZE);

    // 1. 绘制网格
    ctx.strokeStyle = '#EEEEEE';
    ctx.lineWidth = 1;
    for (let i = 0; i < this.CANVAS_SIZE; i += 25) {
      ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, this.CANVAS_SIZE); ctx.stroke();
      ctx.beginPath(); ctx.moveTo(0, i); ctx.lineTo(this.CANVAS_SIZE, i); ctx.stroke();
    }

    // 2. 绘制中心小正方形 (显露出的部分)
    const side = this.B - this.A; // 边长 b-a
    // 中心小正方形的坐标计算较为复杂,这里通过几何关系定位
    // 它是倾斜的,角度取决于三角形。
    // 为了简化视觉,我们绘制一个视觉参考区域或者仅靠拖拽后的留白展示。
    // 这里我们在中心绘制一个半透明的虚线框作为提示
    ctx.save();
    ctx.translate(this.CENTER, this.CENTER);
    ctx.strokeStyle = '#F1C40F';
    ctx.lineWidth = 2;
    ctx.setLineDash([5, 5]);
    ctx.strokeRect(-side/2, -side/2, side, side); // 简化的正方形,实际赵爽弦图中间是正方形
    ctx.restore();
  }

  // 初始化拼图块数据
  private initPieces() {
    this.isScrambled = false;
    const a = this.A;
    const b = this.B;
    const c = this.C;

    // 计算大正方形左上角坐标,使其居中
    const startX = this.CENTER - c / 2;
    const startY = this.CENTER - c / 2;

    // 定义四个三角形相对于大正方形左上角(0,0)的坐标
    // 三角形形状:直角顶点朝外,斜边朝内

    this.pieces = [
      {
        id: 0, color: '#E74C3C', // 红
        // 上方三角形:直角在左上角 (0,0)
        // 顶点:(0,0) -> (b,0) -> (0,a)
        // 修正:大正方形边长是c。
        // 几何构造:
        // 三角形1 (上): 直角在 (0, b-a)?? 不,直角在 (0,0) 或 ...
        // 标准赵爽弦图:
        // 顶点 P1(0,0). P2(c,0). P3(c,c). P4(0,c).
        // T1 (上): 顶点 P1(0,0), Q1(a,0), Q2(0,b).
        //   验证:P1->Q1 = a, P1->Q2 = b. Q1->Q2 = sqrt(a²+b²) = c. 正确。
        points: [
          {x: startX, y: startY},
          {x: startX + a, y: startY},
          {x: startX, y: startY + b}
        ],
        initOffset: {x:0, y:0},
        currentOffset: {x:0, y:0},
        rotation: 0
      },
      {
        id: 1, color: '#3498DB', // 蓝
        // 右方三角形:直角在右上角 -> Q3(c, a)?? 不,需要旋转匹配。
        // 手动计算顶点映射:
        // 直角顶点在 (c,0) (大正方形右上角)
        // 顶点:, (c-a, 0), (c, b) ?? 不,边长需匹配。
        // 正确映射:
        // 直角顶点 P2(c,0). 一条边长为a向左,一条边长为b向下。
        points: [
          {x: startX + c, y: startY},
          {x: startX + c - a, y: startY},
          {x: startX + c, y: startY + b}
        ],
        initOffset: {x:0, y:0},
        currentOffset: {x:0, y:0},
        rotation: 0
      },
      {
        id: 2, color: '#2ECC71', // 绿
        // 下方三角形:直角在右下角
        // 直角顶点 P3(c,c). 边长a向左,边长b向上。
        // 这里的顶点顺序决定了三角形的方向,需要保证是闭合的
        // 稍作修正:为了保证全等,只是旋转。
        // T3的顶点应为: 直角, (c-b, c), (c, c-a)
        points: [
          {x: startX + c, y: startY + c}, // 直角
          {x: startX + c, y: startY + c - a}, // 向上 a
          {x: startX + c - b, y: startY + c}  // 向左 b
        ],
        initOffset: {x:0, y:0},
        currentOffset: {x:0, y:0},
        rotation: 0
      },
      {
        id: 3, color: '#F1C40F', // 黄
        // 左方三角形:直角在左下角
        // 直角顶点 P4(0,c). 边长a向右,边长b向上。
        points: [
          {x: startX, y: startY + c},
          {x: startX + a, y: startY + c},
          {x: startX, y: startY + c - b}
        ],
        initOffset: {x:0, y:0},
        currentOffset: {x:0, y:0},
        rotation: 0
      }
    ];
  }

  // 生成三角形Path命令
  private getTrianglePath(points: Point[]): string {
    return `M ${points[0].x} ${points[0].y} L ${points[1].x} ${points[1].y} L ${points[2].x} ${points[2].y} Z`;
  }

  // 生成直角标记路径
  private getRightAngleMarker(points: Point[]): string {
    // 在直角顶点(points[0])处画小正方形标记
    const p0 = points[0];
    const p1 = points[1];
    const p2 = points[2];

    // 计算单位向量
    const len = 10; // 标记大小
    const v1: Vector = { x: (p1.x - p0.x) / this.A * len, y: (p1.y - p0.y) / this.A * len };
    const v2: Vector = { x: (p2.x - p0.x) / this.B * len, y: (p2.y - p0.y) / this.B * len };

    const m1: Point = { x: p0.x + v1.x, y: p0.y + v1.y };
    const m2: Point = { x: p0.x + v2.x, y: p0.y + v2.y };
    const m3: Point = { x: p0.x + v1.x + v2.x, y: p0.y + v1.y + v2.y };

    return `M ${m1.x} ${m1.y} L ${m3.x} ${m3.y} L ${m2.x} ${m2.y}`;
  }

  // 打乱或复原
  private toggleScramble() {
    if (this.isScrambled) {
      // 复原
      this.pieces.forEach((p, i) => {
        p.currentOffset = { x: 0, y: 0 };
      });
    } else {
      // 打乱:随机偏移
      this.pieces.forEach((p, i) => {
        p.currentOffset = {
          x: (Math.random() - 0.5) * 200,
          y: (Math.random() - 0.5) * 200
        };
      });
    }
    this.isScrambled = !this.isScrambled;
  }
}
Logo

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

更多推荐