Développement d'une application de niveau à bulle avec Flutter pour OpenHarmony
Dans le domaine du développement mobile, Flutter s'est imposé comme un cadre de choix pour de nombreux développeurs grâce à ses excellentes capacités de développement multiplateforme et son moteur de rendu haute performance. Avec la croissance rapide de l'écosystème OpenHarmony, Flutter pour OpenHarmony offre aux développeurs une voie efficace pour créer des applications - un seul code Dart peut s'exécuter simultanément sur les appareils Android, iOS et OpenHarmony.
Cet article vous guidera pour construire une application de niveau à bulle fonctionnelle à partir de zéro, couvrant huit fonctionnalités principales : mesure d'angle précise, affichage de la bulle de niveau, calibration horizontale, intégration de la boussole, détection d'angle de pente, enregistrement des données de mesure, capture d'écran et partage, ainsi que conversion d'unités. Tout le code est écrit en Dart pur, sans dépendre d'API spécifiques à une plateforme, garantissant un fonctionnement transparent sur les appareils OpenHarmony.
Architecture d'application
L'application de niveau à bulle adopte le modèle d'architecture MVC standard de Flutter, divisé en trois couches :
- Couche Modèle : définit les structures de données des capteurs, le modèle d'enregistrements de mesure, le modèle de données de calibration et l'énumération d'unités
- Couche Service : gère le flux de données des capteurs, l'historique des mesures, les données de calibration et les paramètres utilisateur
- Couche Vue : inclut la page d'accueil (bulle de niveau + affichage d'angle + boussole), la page de calibration, la page d'historique et la page de paramètres
Le flux de données utilise le mode Stream pour piloter les mises à jour de l'interface utilisateur, et le Service fonctionne en mode singleton pour garantir la cohérence de l'état global.
Conception des modèles de données
Nous commençons par définir l'énumération des unités d'angle, supportant la conversion entre degrés, radians et pourcentage :
enum UniteNiveau {
degre('Degré', '°'),
radian('Radian', 'rad'),
pourcent('Pourcentage', '%');
final String libelle;
final String symbole;
const UniteNiveau(this.libelle, this.symbole);
String formater(double degres) {
switch (this) {
case UniteNiveau.degre:
return '${degres.toStringAsFixed(2)}°';
case UniteNiveau.radian:
return '${(degres * pi / 180.0).toStringAsFixed(4)} rad';
case UniteNiveau.pourcent:
final pct = tan(degres * pi / 180.0) * 100;
return '${pct.toStringAsFixed(2)}%';
}
}
}
Le modèle de données des capteurs encapsule l'angle de tangage (Pitch), l'angle de roulis (Roll) et l'azimuth, tout en fournissant le calcul de l'angle d'inclinaison total et le jugement de l'état horizontal :
class DonneesCapteur {
double tangage; // Angle de tangage
double roulis; // Angle de roulis
double azimuth; // Azimuth
DateTime horodatage;
DonneesCapteur({
this.tangage = 0,
this.roulis = 0,
this.azimuth = 0,
DateTime? horodatage,
}) : horodatage = horodatage ?? DateTime.now();
double get inclinaisonTotale => sqrt(tangage * tangage + roulis * roulis);
bool get estHorizontal => inclinaisonTotale < 0.5;
}
Le modèle d'enregistrement de mesure contient des données de mesure complètes, un horodatage, un mode et un état de favori, tout en fournissant une logique de calcul d'étiquette de direction :
class MesureNiveau {
final String id;
final double tangage;
final double roulis;
final double azimuth;
final DateTime horodatage;
final ModeNiveau mode;
String? note;
bool estFavori;
String get etiquetteDirection {
if (azimuth >= 337.5 || azimuth < 22.5) return 'Nord';
if (azimuth >= 22.5 && azimuth < 67.5) return 'Nord-Est';
if (azimuth >= 67.5 && azimuth < 112.5) return 'Est';
if (azimuth >= 112.5 && azimuth < 157.5) return 'Sud-Est';
if (azimuth >= 157.5 && azimuth < 202.5) return 'Sud';
if (azimuth >= 202.5 && azimuth < 247.5) return 'Sud-Ouest';
if (azimuth >= 247.5 && azimuth < 292.5) return 'Ouest';
if (azimuth >= 292.5 && azimuth < 337.5) return 'Nord-Ouest';
return 'Nord';
}
}
Implémentation de la couche service
La couche Service constitue le cœur de l'application, responsable de la simulation des données des capteurs, de la gestion de la calibration et de la maintainance de l'historique. Étant donné les différences d'API des capteurs sur les appareils OpenHarmony, nous utilisons le mode Stream pour encapsuler la source de données, facilitant son remplacement ultérieur par des capteurs réels :
class ServiceNiveau {
static final ServiceNiveau _instance = ServiceNiveau._();
factory ServiceNiveau() => _instance;
ServiceNiveau._();
final StreamController<DonneesCapteur> _fluxCapteurController =
StreamController<DonneesCapteur>.broadcast();
final List<MesureNiveau> _historique = [];
DonneesCalibration _calibration = DonneesCalibration();
Stream<DonneesCapteur> get fluxCapteur => _fluxCapteurController.stream;
void demarrerSimulationCapteur() {
Timer.periodic(const Duration(milliseconds: 50), (_) {
_simTangage += (_simCibleTangage - _simTangage) * 0.15;
_simRoulis += (_simCibleRoulis - _simRoulis) * 0.15;
_simAzimuth += (_simCibleAzimuth - _simAzimuth) * 0.1;
_donneesCapteurCourante.tangage = _simTangage - _calibration.decalageTangage;
_donneesCapteurCourante.roulis = _simRoulis - _calibration.decalageRoulis;
_donneesCapteurCourante.horodatage = DateTime.now();
_fluxCapteurController.add(_donneesCapteurCourante.copier());
});
}
}
Le Service fournit également des fonctionnalités statistiques riches, y compris l'angle d'inclinaison moyen, les valeurs d'inclinaison maximale/minimale et le comptage des niveaux horizontaux, ces données étant affichées dans le panneau statistique de la page d'historique.
Implémentation de l'niterface utilisateur de la bulle de niveau
La bulle de niveau est le composant visuel le plus central de l'application. Nous utilisons CustomPaint pour dessiner la réticule et l'anneau de graduation, et AnimatedContainer pour réaliser un mouevment fluide de la bulle :
Widget _construireBulleNiveau(ThemeData theme, bool estHorizontal) {
final taille = MediaQuery.of(context).size.width - 64;
final decalageMax = taille * 0.35;
final decalageTangage = (_donneesCapteur.tangage / 90.0 * decalageMax).clamp(-decalageMax, decalageMax);
final decalageRoulis = (_donneesCapteur.roulis / 90.0 * decalageMax).clamp(-decalageMax, decalageMax);
return Center(
child: Container(
width: taille,
height: taille,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: estHorizontal
? [const Color(0xFF4CAF50).withValues(alpha: 0.3), const Color(0xFF16213E)]
: [const Color(0xFF4A90D9).withValues(alpha: 0.2), const Color(0xFF16213E)],
),
border: Border.all(
color: estHorizontal ? const Color(0xFF4CAF50) : const Color(0xFF4A90D9).withValues(alpha: 0.5),
width: 2,
),
),
child: Stack(
alignment: Alignment.center,
children: [
CustomPaint(size: Size(taille, taille), painter: _PeintreReticule(estHorizontal: estHorizontal)),
AnimatedContainer(
duration: const Duration(milliseconds: 200),
transform: Matrix4.identity()..translate(decalageRoulis, decalageTangage),
// Style de la bulle...
),
],
),
),
);
}
Lorsque l'angle d'inclinaison de l'appareil est inférieur à 0,5°, la bulle devient verte et affiche l'étiquette "Horizontal", tout en produisant un effet de lueur sur l'anneau extérieur, offrant un retour visuel clair à l'utilisateur.
Boussole et affichage d'angle
La fonctionnalité de boussole est réalisée par Transform.rotate pour faire tourner l'icône de navigation en fonction de l'azimuth, tout en affichant la valeur d'angle précise et le texte directionnel :
Widget _construireBarreBoussole(ThemeData theme) {
return Container(
child: Row(
children: [
Transform.rotate(
angle: _donneesCapteur.azimuth * pi / 180,
child: const Icon(Icons.navigation, color: Colors.red, size: 20),
),
Text('${_donneesCapteur.azimuth.toStringAsFixed(1)}°'),
Text(_obtenirEtiquetteDirection(_donneesCapteur.azimuth)),
],
),
);
}
La zone d'affichage d'angle montre simultanément les angles de tangage et de roulis, accompagnés de flèches directionnelles indiquant la direction d'inclinaison. En mode détection de pente, l'application affiche en plus le pourcentage de pente, calculé par la formule tan(θ) × 100%.
Fonctionnalité de calibration
La calibration est essentielle pour garantir la précision des mesures. L'utilisateur place l'appareil sur une surface horizontale, puis clique sur "Démarrer la calibration". Le système automatiquement collecte 50 échantillons après un compte à rebours de 3 secondes, calculant le décalage moyen pour chaque axe :
void _terminerCalibration() {
final moyenneTangage = _sommeTangage / _nombreEchantillons;
final moyenneRoulis = _sommeRoulis / _nombreEchantillons;
final moyenneAzimuth = _sommeAzimuth / _nombreEchantillons;
_service.mettreAJourCalibration(moyenneTangage, moyenneRoulis, moyenneAzimuth);
// Afficher l'état de calibration terminée
}
Une fois la calibration terminée, toutes les mesures ultérieures soustrairont automatiquement le décalage, garantissant l'exactitude des résultats. L'utilisateur peut également restaurer à tout moment les paramètres de calibration par défaut.
Capture d'écran et export de données
La fonction de capture utilise RenderRepaintBoundary de Flutter pour générer des images PNG haute définition avec un ratio de 3 pixels :
Future<void> _capturerEtPartager() async {
final limite = _cleCapture.currentContext?.findRenderObject()
as RenderRepaintBoundary?;
final image = await limite.toImage(pixelRatio: 3.0);
final donneesOctets = await image.toByteData(format: ui.ImageByteFormat.png);
// Enregistrer ou partager les donneesOctets
}
La page d'historique prend en charge l'exportation des données au format CSV et JSON, facilitant l'analyse et l'archivage ultérieurs des données par l'utilisateur.
Capture d'écran en cours d'exécution
Voici des captures d'écran réelles de l'application de niveau à bulle sur un appareil OpenHarmony :
Capture : Page d'accueil de l'application de niveauMontre l'interface utilisateur complète de la bulle de niveau circulaire, incluant la réticule, l'anneau de graduation et la bulle se déplaçant en temps réel. Lorsque l'appareil est dans une position horizontale, la bulle se centre et devient verte, affichant l'étiquette "Horizontal" en haut.
Conclusion
Cet article a détaillé comment développer une application de niveau à bulle fonctionnelle en utilisant Flutter pour OpenHarmony. Grâce au modèle d'architecture MVC, à la mise à jour de l'interface utilisateur pilotée par les données Stream, et aux techniques de dessin personnalisé avec CustomPaint, nous avons réalisé huit fonctionnalités principales : mesure d'angle précise, affichage de la bulle de niveau, calibration horizontale, intégration de la boussole, détection d'angle de pente, enregistrement des données de mesure, capture d'écran et partage, et conversion d'unités.
Tout le code est écrit en Dart pur, sans dépendre d'API spécifiques à une plateforme, garantissant un fonctionnement transparent sur les appareils OpenHarmony. Le code source complet est disponible sur AtomGit. Rejoignez la communauté OpenHarmony multiplateforme pour échanger et discuter, et contribuez ensemble au développement de l'écosystème Flutter pour OpenHarmony.