Visão Geral dos Cartões Serviço e Benefícios
Os cartões serviço no HarmonyOS são componentes UI leves com características principais como:
- Exibição Avançada: Mostra informações cruciais diretamente na tela inicial ou bloqueio, sem necessidade de abrir o aplicativo.
- Interatividade Simples: Suporte para ações básicas como toques, facilitando acesso rápido.
- Compatibilidade Multi-dispositivo: Funciona em smartphones, tablets, PCs, smart TVs, relógios intleigentes (exceto dispositivos vestíveis ultra-leves).
- Desenvolvimento Eficiente: Templates automáticos do IDE e SDK aceleram o desenvolvimento.
Configuração Básica do Ambiente
1. Declaração de Permissões
No modulo.json5, declare as permissões essenciais:
{
"modulo": {
"permissoesRequeridas": [
{ "nome": "ohos.permission.REQUERIR_FORMA" }
],
"capacidades": [
{
"nome": "CapacidadeFormaEntrada",
"tipo": "extensao",
"tipoExtensaoCapacidade": "forma",
"metadados": [
{ "nome": "ohos.extensao.forma", "recurso": "$perfil:config_forma" }
]
}
]
}
}
2. Arquivo de Configuração da Forma
Configure os atributos da forma no src/main/recursos/base/perfil/config_forma.json:
{
"formas": [
{
"nome": "widget",
"nomeExibicao": "$string:nome_exibicao_widget",
"descricao": "$string:desc_widget",
"fonte": "./ets/widget/paginas/WidgetCard.ets",
"sintaxeUI": "arkts",
"janela": { "larguraDesign": 720, "autoLarguraDesign": true },
"modoCor": "auto",
"dinamico": true,
"padrao": true,
"atualizacaoHabilitada": true,
"horaAtualizacaoProgramada": "10:30",
"duracaoAtualizacao": 1,
"dimensaoPadrao": "2 * 4",
"suportaDimensoes": ["2 * 2", "2 * 4", "4 * 4"]
}
]
}
Implementação de Cartões Dinâmicos
1. Criação da Capacidade de Extensão da Forma
import { CapacidadeExtensaoForma, dadosVinculadosForma, provedorForma } from '@kit.FormKit';
import { Querer } from '@kit.CapacidadeKit';
import { ErroNegocio } from '@ohos.base';
import log from '@ohos.log';
const DOMINIO = 0x00001;
const ETIQUETA = 'CapacidadeFormaEntrada';
export default class CapacidadeFormaEntrada extends CapacidadeExtensaoForma {
onAdicionarForma(querer: Querer): dadosVinculadosForma.DadosVinculadosForma {
log.info(DOMINIO, ETIQUETA, 'onAdicionarForma chamado');
const idForma = querer.parametros?.['ohos.extra.param.key.form_identity'] as string;
const nomeForma = querer.parametros?.['ohos.extra.param.key.form_nome'] as string;
const dadosIniciais = {
titulo: 'Cartão Dinâmico',
conteudo: 'Conteúdo Inicial',
horaAtualizacao: new Date().toLocaleTimeString(),
idForma: idForma
};
return dadosVinculadosForma.criarDadosVinculadosForma(dadosIniciais);
}
onEventoForma(idForma: string, mensagem: string): void {
log.info(DOMINIO, ETIQUETA, `onEventoForma: idForma=${idForma}, mensagem=${mensagem}`);
try {
const parametros = JSON.parse(mensagem);
this.manipularEventoForma(idForma, parametros);
} catch (erro) {
log.error(DOMINIO, ETIQUETA, `Falha ao analisar mensagem: ${(erro as ErroNegocio).mensagem}`);
}
}
private async manipularEventoForma(idForma: string, parametros: any): Promise<void> {
if (parametros.acao === 'atualizar') {
await this.atualizarDadosForma(idForma);
} else if (parametros.acao === 'alterarConteudo') {
await this.alterarConteudoForma(idForma, parametros.conteudo);
}
}
private async atualizarDadosForma(idForma: string): Promise<void> {
const novosDados = {
titulo: 'Cartão Dinâmico',
conteudo: `Hora Atualização: ${new Date().toLocaleTimeString()}`,
horaAtualizacao: new Date().toLocaleTimeString()
};
await this.atualizarDadosForma(idForma, novosDados);
}
private async alterarConteudoForma(idForma: string, conteudo: string): Promise<void> {
const novosDados = {
conteudo: conteudo,
horaAtualizacao: new Date().toLocaleTimeString()
};
await this.atualizarDadosForma(idForma, novosDados);
}
private async atualizarDadosForma(idForma: string, dados: any): Promise<void> {
try {
const vinculoForma = dadosVinculadosForma.criarDadosVinculadosForma(dados);
await provedorForma.atualizarForma(idForma, vinculoForma);
log.info(DOMINIO, ETIQUETA, 'Dados da forma atualizados com sucesso');
} catch (erro) {
log.error(DOMINIO, ETIQUETA, `Falha ao atualizar forma: ${(erro as ErroNegocio).mensagem}`);
}
}
onRemoverForma(idForma: string): void {
log.info(DOMINIO, ETIQUETA, `Forma removida: ${idForma}`);
}
}
2. Implementação do Componente UI Dinâmico
// WidgetCard.ets
import { enviarAcaoCartao } from '@kit.FormKit';
@PontoEntrada
@Componente
struct WidgetCard {
@PropriedadeArmazenamentoLocal('titulo') titulo: string = 'Título Padrão';
@PropriedadeArmazenamentoLocal('conteudo') conteudo: string = 'Conteúdo Padrão';
@PropriedadeArmazenamentoLocal('horaAtualizacao') horaAtualizacao: string = '';
@PropriedadeArmazenamentoLocal('idForma') idForma: string = '';
build() {
Coluna() {
Text(this.titulo)
.tamanhoFonte(18)
.pesoFonte(FontPeso.Negrito)
.margem({ topo: 10, baixo: 5 })
.alinhamentoTexto(TextoAlinhamento.Centro)
Text(this.conteudo)
.tamanhoFonte(16)
.margem({ baixo: 10 })
.alinhamentoTexto(TextoAlinhamento.Centro)
Text(`Atualizado: ${this.horaAtualizacao}`)
.tamanhoFonte(12)
.corFonte(Cor.Cinza)
.margem({ baixo: 10 })
Linha() {
Botao('Atualizar')
.onClick(() => this.enviarMensagem('atualizar'))
.margem(5)
Botao('Alterar')
.onClick(() => this.enviarMensagem('alterarConteudo', { conteudo: 'Novo Conteúdo' }))
.margem(5)
}
.margem({ topo: 10 })
}
.largura('100%')
.altura('100%')
.preenchimento(10)
.corFundo(Cor.Branco)
}
private enviarMensagem(acao: string, parametros?: object): void {
const mensagem = {
acao: acao,
idForma: this.idForma,
...parametros
};
enviarAcaoCartao(this, {
acao: 'mensagem',
parametros: JSON.stringify(mensagem)
});
}
}
Ligação de Dados e Gerenciamento de Estado
1. Ligação de Dados Local
// Criar instância de Armazenamento Local
const armazenamentoLocal = new ArmazenamentoLocal();
@PontoEntrada(armazenamentoLocal)
@Componente
struct CartaoPrincipalWidget {
@PropriedadeArmazenamentoLocal('titulo') titulo: string = '';
@PropriedadeArmazenamentoLocal('dados') @Observador('onDadosAlterado') dados: objeto = {};
onDadosAlterado(): void {
console.info('Dados atualizados:', this.dados);
}
build() {
Coluna() {
if (this.dados.itens && this.dados.itens.length > 0) {
ForEach(this.dados.itens, (item: qualquer) => {
Text(item.texto)
.tamanhoFonte(14)
.margem(2)
})
}
Botao('Adicionar Item')
.onClick(() => this.adicionarItem())
}
}
private adicionarItem(): void {
enviarAcaoCartao(this, {
acao: 'mensagem',
parametros: JSON.stringify({
acao: 'adicionarItem',
texto: `Item${Date.now()}`
})
});
}
}
2. Mecanismo de Atualização de Dados em Tempo Real
// No CapacidadeExtensaoForma implementar atualizações periódicas
export default class CapacidadeDadosForma extends CapacidadeExtensaoForma {
private temporizadores: Map<string, número> = new Map();
onAdicionarForma(querer: Querer): dadosVinculadosForma.DadosVinculadosForma {
const idForma = querer.parametros?.['ohos.extra.param.key.form_identity'] as string;
this.iniciarAtualizacaoPeriodica(idForma);
return dadosVinculadosForma.criarDadosVinculadosForma({
itens: this.obterDadosIniciais()
});
}
private iniciarAtualizacaoPeriodica(idForma: string): void {
const temporizador = setInterval(async () => {
await this.atualizarDadosForma(idForma);
}, 30000);
this.temporizadores.set(idForma, temporizador);
}
private async atualizarDadosForma(idForma: string): Promise<void> {
const novosDados = {
itens: this.buscarUltimosDados(),
horaAtualizacao: new Date().toLocaleTimeString()
};
try {
const vinculoForma = dadosVinculadosForma.criarDadosVinculadosForma(novosDados);
await provedorForma.atualizarForma(idForma, vinculoForma);
} catch (erro) {
log.error(DOMINIO, ETIQUETA, `Falha na atualização periódica: ${(erro as ErroNegocio).mensagem}`);
}
}
onRemoverForma(idForma: string): void {
const temporizador = this.temporizadores.get(idForma);
if (temporizador) {
clearInterval(temporizador);
this.temporizadores.delete(idForma);
}
}
}
Sincronização de Dados Distribuída
O HarmonyOS permite sincronização de dados entre dispositivos usando capacidades distribuídas.
import { dadosDistribuidos } from '@kit.DadosDistribuidos';
class GestorDadosDistribuidos {
private gerenciadorKV: dadosDistribuidos.GerenciadorKV | null = null;
private armazenamentoKV: dadosDistribuidos.ArmazenamentoUnicoKV | null = null;
async inicializarArmazenamentoDistribuidoKV(): Promise<void> {
try {
const contexto = getContext(this) as Contexto;
const configuracao: dadosDistribuidos.Configuracao = {
nomePacote: contexto.informacoesAplicacao.nome,
informacoesUsuario: {
idUsuario: dadosDistribuidos.TipoUsuario.MESMO_ID_USUARIO
}
};
this.gerenciadorKV = dadosDistribuidos.criarGerenciadorKV(configuracao);
const opcoes: dadosDistribuidos.ConfiguracaoArmazenamento = {
idArmazenamento: 'armazenamento_dados_widget',
tipoArmazenamentoKV: dadosDistribuidos.TipoArmazenamentoKV.VERSAO_UNICA,
nivelSeguranca: dadosDistribuidos.NivelSeguranca.S2,
sincronizacaoAutomatica: true
};
this.armazenamentoKV = await this.gerenciadorKV.obterArmazenamento<dadosDistribuidos.ArmazenamentoUnicoKV>(opcoes);
} catch (erro) {
log.error(DOMINIO, ETIQUETA, `Falha na inicialização do banco de dados distribuído: ${(erro as ErroNegocio).mensagem}`);
}
}
async sincronizarDadosCartao(idForma: string, dados: any): Promise<void> {
if (!this.armazenamentoKV) return;
try {
await this.armazenamentoKV.colocar(`${idForma}_dados`, JSON.stringify(dados));
log.info(DOMINIO, ETIQUETA, 'Dados do cartão sincronizados com o banco de dados distribuído');
} catch (erro) {
log.error(DOMINIO, ETIQUETA, `Falha na sincronização de dados: ${(erro as ErroNegocio).mensagem}`);
}
}
async obterDadosSincronizados(idForma: string): Promise<qualquer> {
if (!this.armazenamentoKV) return null;
try {
const valor = await this.armazenamentoKV.obter(`${idForma}_dados`);
return valor ? JSON.parse(valor.toString()) : null;
} catch (erro) {
log.error(DOMINIO, ETIQUETA, `Falha ao obter dados sincronizados: ${(erro as ErroNegocio).mensagem}`);
return null;
}
}
}
Funcionalidades Avançadas e Interações
1. Adaptação de Tamanhos de Cartões
@PontoEntrada
@Componente
struct CartaoAdaptativoWidget {
@PropriedadeArmazenamentoLocal('dimensao') dimensao: string = '2 * 4';
@PropriedadeArmazenamentoLocal('dados') dados: qualquer;
build() {
Coluna() {
if (this.dimensao === '2 * 2') {
this.buildLayoutPequeno();
} else if (this.dimensao === '2 * 4') {
this.buildLayoutMedio();
} else if (this.dimensao === '4 * 4') {
this.buildLayoutGrande();
}
}
}
@Construtor
buildLayoutPequeno() {
Text(this.dados.titulo)
.tamanhoFonte(16)
.margem(5);
Text(this.dados.valor)
.tamanhoFonte(20)
.pesoFonte(FontPeso.Negrito);
}
@Construtor
buildLayoutMedio() {
Linha() {
Imagem(this.dados.icone)
.largura(40)
.altura(40)
.margem({ direita: 10 })
Coluna() {
Text(this.dados.titulo)
.tamanhoFonte(16)
Text(this.dados.valor)
.tamanhoFonte(18)
.pesoFonte(FontPeso.Negrito)
}
}
}
@Construtor
buildLayoutGrande() {
Coluna() {
Text(this.dados.titulo)
.tamanhoFonte(18)
.pesoFonte(FontPeso.Negrito)
Divisor()
.margem(10)
ForEach(this.dados.itens, (item: qualquer) => {
Linha() {
Text(item.rotulo)
.tamanhoFonte(14)
.pesoLayout(1)
Text(item.valor)
.tamanhoFonte(14)
.corFonte(Cor.Azul)
}
.largura('100%')
.margem({ baixo: 5 })
})
Divisor()
.margem(10)
Text(`Atualizado em: ${this.dados.horaAtualizacao}`)
.tamanhoFonte(12)
.corFonte(Cor.Cinza)
}
}
}
2. Animações e Efeitos Visuais
@Componente
struct CartaoAnimadoWidget {
@Estado privado estaAtualizando: boolean = false;
@Estado privado rotacao: numero = 0;
build() {
Coluna() {
Linha() {
Imagem($r('app.media.icone_atualizar'))
.largura(20)
.altura(20)
.rotacionar({ angulo: this.rotacao })
.onClick(() => this.iniciarAnimacaoAtualizacao())
Text('Atualizar Dados')
.tamanhoFonte(14)
.margem({ esquerda: 5 })
}
.margem(10)
.onClick(() => this.manipularAtualizacao())
}
}
private iniciarAnimacaoAtualizacao(): void {
this.estaAtualizando = true;
this.rotacao = 0;
animarPara({
duracao: 1000,
iteracoes: -1, // Loop infinito
curva: Curva.Linear
}, () => {
this.rotacao = 360;
});
}
private pararAnimacao(): void {
this.estaAtualizando = false;
this.rotacao = 0;
}
private async manipularAtualizacao(): Promise<void> {
this.iniciarAnimacaoAtualizacao();
try {
await new Promise(resolve => setTimeout(resolve, 2000));
this.pararAnimacao();
} catch (erro) {
this.pararAnimacao();
}
}
}
Práticas Recomendadas e Otimização de Desempanho
1. Sugestões de Otimização de Desempenho
- Controle de Volume de Dados: Limitar a quantidade de dados transmitidos por cartão, idealmente até 5KB.
- Frequência de Atualização: Definir intervalos razoáveis para evitar impacto no desempenho.
- Gestão de Memória: Liberar recursos e listeners não utilizados.
- Otimização de Imagens: Usar imagens adequadas em tamanho para reduzir consumo de memória.
2. Considerações de Desanvolvimento
- Restrições de Cartões: Cartões ArkTS suportam apenas módulos marcados como compatíveis, não permitem pacotes compartilhados nem desenvolvimento em linguagem nativa (C++).
- Tratamento de Eventos: Eventos de cartão e uso devem ser tratados separadamente, evitando conflito de gestos.
- Limitações de Depuração: Não há suporte para depuração com breakpoints, Hot Reload ou setTimeOut/setInterval.
3. Tratamento de Erros e Robustez
class ManipuladorErros {
static tratarErroCartao(erro: ErroNegocio, contexto: string): void {
log.error(DOMINIO, ETIQUETA, `Erro no Cartão [${contexto}]: ${erro.mensagem}`);
if (erro.codigo === 1001) { // Supondo que 1001 é erro de rede
this.exibirErroRede();
} else if (erro.codigo === 1002) { // Erro de formato de dados
this.exibirErroDados();
}
}
static exibirErroRede(): void {
enviarAcaoCartao(this, {
acao: 'mensagem',
parametros: JSON.stringify({
acao: 'exibirErro',
mensagem: 'Falha na conexão de rede, verifique configurações'
})
});
}
static exibirErroDados(): void {
enviarAcaoCartao(this, {
acao: 'mensagem',
parametros: JSON.stringify({
acao: 'exibirErro',
mensagem: 'Formato de dados incorreto, tente atualizar'
})
});
}
}