Implementação de Reprodução de Trajetória em Aplicativos de Exercício no HarmonyOS

Introdução Técnica

Em aplicativos de exercício físico, a funcionalidade de reprodução de trajetória é crucial para melhorar a experiência do usuário. Este recurso permite visualizar dinamicamente a rota percorrida durante uma atividade física, adicionando interatividade e imersão. Neste artigo, exploraremos como desenvolver um efeito de reprodução de trajetória semelhante ao observado em aplicativos populares, utilizando o sistema HarmonyOS.

Funcionalidades Principais

Para implementar a reprodução de trajetória, é necessário abordar dois copmonentes técnicos essenciais:

  • Reprodução Dinâmica da Trajetória: Uso de temporizadores e interpolação linear para simular o movimento ao longo da rota.
  • Interação com o Mapa: Atualização contínua do centro do mapa e ângulo de rotação para manter o foco no ponto atual.

Implementação da Reprodução Dinâmica

Lógica de Reprodução

A reprodução da trajetória é controlada por um temporizador principal que avança pelos pontos da rota. Para cada segmento, um temporizador secundário gerencia a animação suave do marcader móvel.

private iniciarReproducao() {
  // Verifica se já está em reprodução e interrompe, se necessário
  if (this.temporizadorPrincipal) {
    this.controladorMapa?.removerSobreposicao(this.polilinha);
    clearInterval(this.temporizadorPrincipal);
    this.temporizadorPrincipal = undefined;
    if (this.temporizadorAnimacao) {
      clearInterval(this.temporizadorAnimacao);
    }
    if (this.marcadorMovel) {
      this.controladorMapa?.removerSobreposicao(this.marcadorMovel);
      this.marcadorMovel = undefined;
    }
    this.indicePontoAtual = 0;
    return;
  }

  // Cria o marcador móvel na posição inicial
  this.marcadorMovel = new Marcador({
    posicao: this.pontosRota[0],
    icone: new EntidadeImagem("rawfile://imagens/marcador_inicio.png"),
    evitarColisao: EnumSistema.ComportamentoColisao.NAO_COLIDIR,
    localizacao: EnumSistema.Localizacao.CENTRO
  });
  this.controladorMapa?.adicionarSobreposicao(this.marcadorMovel);

  // Inicia a reprodução com intervalo de 100ms
  this.temporizadorPrincipal = setInterval(() => {
    this.indicePontoAtual++;
    if (this.indicePontoAtual >= this.pontosRota.length) {
      clearInterval(this.temporizadorPrincipal);
      this.temporizadorPrincipal = undefined;
      this.indicePontoAtual = 0;
      if (this.marcadorMovel) {
        this.controladorMapa?.removerSobreposicao(this.marcadorMovel);
        this.marcadorMovel = undefined;
      }
      return;
    }

    // Executa a animação de movimento suave entre pontos adjacentes
    if (this.marcadorMovel && this.indicePontoAtual < this.pontosRota.length - 1) {
      const pontoAtual = this.pontosRota[this.indicePontoAtual];
      const pontoProximo = this.pontosRota[this.indicePontoAtual + 1];
      let progressoAnimacao = 0;

      if (this.temporizadorAnimacao) {
        clearInterval(this.temporizadorAnimacao);
      }

      this.temporizadorAnimacao = setInterval(() => {
        progressoAnimacao += 0.1;
        if (progressoAnimacao >= 1) {
          clearInterval(this.temporizadorAnimacao);
          this.temporizadorAnimacao = undefined;
          this.marcadorMovel?.definirPosicao(new Coordenada(pontoProximo.lat, pontoProximo.lng));
        } else {
          const latInterpolada = pontoAtual.lat + (pontoProximo.lat - pontoAtual.lat) * progressoAnimacao;
          const lngInterpolada = pontoAtual.lng + (pontoProximo.lng - pontoAtual.lng) * progressoAnimacao;
          this.marcadorMovel?.definirPosicao(new Coordenada(latInterpolada, lngInterpolada));
        }
      }, 10);
    }

    // Atualiza a polilinha da trajetória até o ponto atual
    const pontosAtuais = this.pontosRota.slice(0, this.indicePontoAtual + 1);
    const coresGradiente = FerramentaGradiente.obterCoresRota(this.gravacao!.pontos.slice(0, this.indicePontoAtual + 1), 100);

    if (this.polilinha) {
      this.controladorMapa?.removerSobreposicao(this.polilinha);
      this.polilinha.remover();
      this.polilinha.destruir();
    }

    this.polilinha = new Polilinha({
      pontos: pontosAtuais,
      espessura: 5,
      juncao: EnumSistema.TipoJuncaoLinha.REDONDO,
      terminal: EnumSistema.TipoTerminalLinha.REDONDO,
      gradiente: true,
      listaCores: coresGradiente!
    });
    this.controladorMapa?.adicionarSobreposicao(this.polilinha);

    // Ajusta a visualização do mapa
    this.atualizarVisualizacaoMapa();
  }, 100);
}

Efeito de Animação

A interpolação linear entre coordenadas consecutivas cria uma transição suave para o marcador. O progresso é incremnetado a cada 10ms, resultando em um movimento fluido.

private executarAnimacao(pontoAtual, pontoProximo) {
  let progresso = 0;
  if (this.temporizadorAnimacao) {
    clearInterval(this.temporizadorAnimacao);
  }
  this.temporizadorAnimacao = setInterval(() => {
    progresso += 0.1;
    if (progresso >= 1) {
      clearInterval(this.temporizadorAnimacao);
      this.marcadorMovel?.definirPosicao(new Coordenada(pontoProximo.lat, pontoProximo.lng));
    } else {
      const lat = pontoAtual.lat + (pontoProximo.lat - pontoAtual.lat) * progresso;
      const lng = pontoAtual.lng + (pontoProximo.lng - pontoAtual.lng) * progresso;
      this.marcadorMovel?.definirPosicao(new Coordenada(lat, lng));
    }
  }, 10);
}

Interação com o Mapa

Para manter o usuário orientado, o mapa é atualizado dinamicamente. O centro é deslocado para o ponto atual e a rotação é calculada com base na direção entre pontos adjacentes.

private atualizarVisualizacaoMapa() {
  let anguloRotacao = 0;
  if (this.indicePontoAtual < this.pontosRota.length - 1) {
    const pontoAtual = this.pontosRota[this.indicePontoAtual];
    const pontoProximo = this.pontosRota[this.indicePontoAtual + 1];
    anguloRotacao = Math.atan2(
      pontoProximo.lat - pontoAtual.lat,
      pontoProximo.lng - pontoAtual.lng
    ) * 180 / Math.PI;
    anguloRotacao = (anguloRotacao + 360) % 360;
    anguloRotacao = (360 - anguloRotacao + 90) % 360;
  }

  this.controladorMapa?.statusMapa
    .definirRotacao(anguloRotacao)
    .definirInclinacao(90)
    .definirCentro(new Coordenada(this.pontosRota[this.indicePontoAtual].lat, this.pontosRota[this.indicePontoAtual].lng))
    .atualizar();
}

Tags: HarmonyOS Desenvolvimento de Aplicativos Móveis Reprodução de Trajetória SDK de Mapas Animação com JavaScript

Publicado em 6-8 02:25 por Thomas