💨

鴻蒙運動開発実践:Keep式の軌跡再生効果の作成

に公開

前言

運動系アプリでは、軌跡再生効果はユーザー体験を向上させるための鍵となる機能の1つです。ユーザーの運動ルートを直感的に表示するだけでなく、動的効果で運動の楽しさを高めることができます。Keepは有名な運動フィットネスアプリで、その軌跡再生効果はユーザーに大変好評です。では、鴻蒙システムでKeepのような軌跡再生効果を開発するにはどうすればよいでしょうか。この記事では、実際のコード例を用いて、この機能を実現するための主要な手順と技術的なポイントを詳しく解析します。

効果:

Image description

上記の画像のリンクは、ネットワークの問題のために解析に失敗しました。この問題は、リンク自体の合法性やネットワーク状況に関連する可能性があります。ウェブページのリンクが正しいことを確認し、適宜再度お試しください。

一、核心機能の分解

Keepのような軌跡再生効果を実現するには、以下の主要な機能を完成する必要があります:

• 動的軌跡再生:タイマーとアニメーション効果を使用して、軌跡の動的再生を実現し、ユーザーの運動プロセスをシミュレートする。

• マップインタラクション:マップ上に軌跡を描画し、再生の進行に応じてマップの中心点と回転角度を更新する。

二、動的軌跡再生

  1. 再生ロジック

タイマーとアニメーション効果を使用して軌跡の動的再生を実現します。以下が軌跡を再生するためのコアコードです:

private playTrack() {
  // 再生中であれば停止する
  if (this.playTimer) {
    this.mapController?.removeOverlay(this.polyline);
    clearInterval(this.playTimer);
    this.playTimer = undefined;
    if (this.animationTimer) {
      clearInterval(this.animationTimer);
    }
    if (this.movingMarker) {
      this.mapController?.removeOverlay(this.movingMarker);
      this.movingMarker = undefined;
    }
    this.currentPointIndex = 0;
    return;
  }

  // 動的場所マーカーの作成
  this.movingMarker = new Marker({
    position: this.trackPoints[0],
    icon: new ImageEntity("rawfile://images/ic_run_detail_start.png"),
    isJoinCollision: SysEnum.CollisionBehavior.NOT_COLLIDE,
    located: SysEnum.Located.CENTER
  });
  this.mapController?.addOverlay(this.movingMarker);

  // 再生を開始する
  this.playTimer = setInterval(() => {
    this.currentPointIndex++;
    if (this.currentPointIndex >= this.trackPoints.length) {
      clearInterval(this.playTimer);
      this.playTimer = undefined;
      this.currentPointIndex = 0;
      if (this.movingMarker) {
        this.mapController?.removeOverlay(this.movingMarker);
        this.movingMarker = undefined;
      }
      return;
    }

    // 動的場所マーカーの位置を更新し、setIntervalを使用して滑らかな移動を実現する
    if (this.movingMarker && this.currentPointIndex < this.trackPoints.length - 1) {
      const currentPoint = this.trackPoints[this.currentPointIndex];
      const nextPoint = this.trackPoints[this.currentPointIndex + 1];
      let animationProgress = 0;

      // 以前のアニメーションタイマーをクリアする
      if (this.animationTimer) {
        clearInterval(this.animationTimer);
      }

      // 新しいアニメーションタイマーを作成し、10msごとに位置を更新する
      this.animationTimer = setInterval(() => {
        animationProgress += 0.1; // 毎回0.1の進捗を追加する

        if (animationProgress >= 1) {
          clearInterval(this.animationTimer);
          this.animationTimer = undefined;
          this.movingMarker?.setPosition(new LatLng(nextPoint.lat, nextPoint.lng));
        } else {
          const interpolatedLat = currentPoint.lat + (nextPoint.lat - currentPoint.lat) * animationProgress;
          const interpolatedLng = currentPoint.lng + (nextPoint.lng - currentPoint.lng) * animationProgress;
          this.movingMarker?.setPosition(new LatLng(interpolatedLat, interpolatedLng));
        }
      }, 10); // 10msごとに実行する
    }

    // 現在の軌跡線分を描画する
    const currentPoints = this.trackPoints.slice(0, this.currentPointIndex + 1);
    const currentColors = PathGradientTool.getPathColors(this.record!.points.slice(0, this.currentPointIndex + 1), 100);

    if (this.polyline) {
      this.mapController?.removeOverlay(this.polyline);
      this.polyline.remove();
      this.polyline.destroy();
    }

    this.polyline = new Polyline({
      points: currentPoints,
      width: 5,
      join: SysEnum.LineJoinType.ROUND,
      cap: SysEnum.LineCapType.ROUND,
      isGradient: true,
      colorList: currentColors!
    });
    this.mapController?.addOverlay(this.polyline);

    // マップの中心点と回転角度を更新する
    let bearing = 0;
    if (this.currentPointIndex < this.trackPoints.length - 1) {
      const currentPoint = this.trackPoints[this.currentPointIndex];
      const nextPoint = this.trackPoints[this.currentPointIndex + 1];
      bearing = Math.atan2(
        nextPoint.lat - currentPoint.lat,
        nextPoint.lng - currentPoint.lng
      ) * 180 / Math.PI;
      bearing = (bearing + 360) % 360;
      bearing = (360 - bearing + 90) % 360;
    }

    this.mapController?.mapStatus.setRotate(bearing).setOverlooking(90).setCenterPoint(new LatLng(this.trackPoints[this.currentPointIndex].lat, this.trackPoints[this.currentPointIndex].lng)).refresh();
  }, 100); // 毎回100msで移動する
}
  1. アニメーション効果

タイマーと線形補間を使用して、動的軌跡の滑らかな移動効果を実現します。以下がアニメーション効果のコアコードです:

if (this.movingMarker && this.currentPointIndex < this.trackPoints.length - 1) {
  const currentPoint = this.trackPoints[this.currentPointIndex];
  const nextPoint = this.trackPoints[this.currentPointIndex + 1];
  let animationProgress = 0;

  // 以前のアニメーションタイマーをクリアする
  if (this.animationTimer) {
    clearInterval(this.animationTimer);
  }

  // 新しいアニメーションタイマーを作成し、10msごとに位置を更新する
  this.animationTimer = setInterval(() => {
    animationProgress += 0.1; // 毎回0.1の進捗を追加する

    if (animationProgress >= 1) {
      clearInterval(this.animationTimer);
      this.animationTimer = undefined;
      this.movingMarker?.setPosition(new LatLng(nextPoint.lat, nextPoint.lng));
    } else {
      const interpolatedLat = currentPoint.lat + (nextPoint.lat - currentPoint.lat) * animationProgress;
      const interpolatedLng = currentPoint.lng + (nextPoint.lng - currentPoint.lng) * animationProgress;
      this.movingMarker?.setPosition(new LatLng(interpolatedLat, interpolatedLng));
    }
  }, 10); // 10msごとに実行する
}

三、マップインタラクション

  1. マップの中心点と回転角度の更新

軌跡を再生するプロセス中では、マップの中心点と回転角度を動的に更新し、ユーザーが常に現在再生されている位置を見られるようにします。以下がマップの中心点と回転角度を更新するコードです:

let bearing = 0;
if (this.currentPointIndex < this.trackPoints.length - 1) {
  const currentPoint = this.trackPoints[this.currentPointIndex];
  const nextPoint = this.trackPoints[this.currentPointIndex + 1];
  bearing = Math.atan2(
    nextPoint.lat - currentPoint.lat,
    nextPoint.lng - currentPoint.lng
  ) * 180 / Math.PI;
  bearing = (bearing + 360) % 360;
  bearing = (360 - bearing + 90) % 360;
}

this.mapController?.mapStatus.setRotate(bearing).setOverlooking(90).setCenterPoint(new LatLng(this.trackPoints[this.currentPointIndex].lat, this.trackPoints[this.currentPointIndex].lng)).refresh();

四、まとめ

上記の手順を通じて、Keepのような軌跡再生効果を成功裏に実現しました。これにより、ユーザー体験が向上し、運動データの可視化が強力にサポートされました。

Discussion