1. Introduction
Que sont les widgets ?
Pour les développeurs Flutter, la définition commune de widget fait référence aux composants d'interface utilisateur créés à l'aide du framework Flutter. Dans le cadre de cet atelier de programmation, un widget fait référence à une version miniature d'une application qui permet d'afficher les informations de l'application sans ouvrir celle-ci. Sur Android, les widgets s'affichent sur l'écran d'accueil. Sur iOS, vous pouvez les ajouter à l'écran d'accueil, à l'écran de verrouillage ou à la vue "Aujourd'hui".
Quelle est la complexité d'un widget ?
La plupart des widgets de l'écran d'accueil sont simples. Elles peuvent contenir du texte de base, des images simples ou, sur Android, des commandes de base. Android et iOS limitent les composants d'interface utilisateur et les fonctionnalités que vous pouvez utiliser.
Créer l'interface utilisateur des widgets
En raison de ces limites d'interface utilisateur, vous ne pouvez pas dessiner directement l'interface utilisateur d'un widget de l'écran d'accueil à l'aide du framework Flutter. À la place, vous pouvez ajouter à votre application Flutter des widgets créés avec des frameworks de plate-forme tels que Jetpack Compose ou SwiftUI. Cet atelier de programmation présente des exemples de partage de ressources entre votre application et les widgets pour éviter de réécrire une UI complexe.
Objectifs de l'atelier
Dans cet atelier de programmation, vous allez créer des widgets d'écran d'accueil sur Android et iOS pour une application Flutter simple à l'aide du package home_widget, qui permet aux utilisateurs de lire des articles. Vos widgets:
- Affichez les données de votre application Flutter.
- Affichez du texte à l'aide des éléments de police partagés depuis l'application Flutter.
- Affichez l'image d'un widget Flutter rendu.
Cette application Flutter comprend deux écrans (ou routes):
- La première affiche une liste d'articles de presse avec des titres et des descriptions.
- La seconde affiche l'article complet avec un graphique créé à l'aide de
CustomPaint
.
.
Points abordés
- Créer des widgets sur l'écran d'accueil sur iOS et Android
- Utiliser le package home_widget pour partager des données entre le widget de l'écran d'accueil et votre application Flutter
- Découvrez comment réduire la quantité de code à réécrire.
- Mettre à jour le widget de l'écran d'accueil depuis votre application Flutter
2. Configurer l'environnement de développement
Pour les deux plates-formes, vous avez besoin du SDK Flutter et d'un IDE. Vous pouvez utiliser l'IDE de votre choix pour travailler avec Flutter. Il peut s'agir de Visual Studio Code avec les extensions Dart Code et Flutter, ou d'Android Studio ou d'IntelliJ avec les plug-ins Flutter et Dart installés.
Pour créer le widget de l'écran d'accueil iOS:
- Vous pouvez exécuter cet atelier de programmation sur un appareil iOS physique ou sur le simulateur iOS.
- Vous devez configurer un système macOS avec l'IDE Xcode. Le compilateur requis pour créer la version iOS de votre application est alors installé.
Pour créer le widget de l'écran d'accueil Android:
- Vous pouvez exécuter cet atelier de programmation sur un appareil Android physique ou sur l'émulateur Android.
- Vous devez configurer votre système de développement avec Android Studio. Cette action permet d'installer le compilateur nécessaire à la compilation de la version Android de votre application.
Télécharger le code de démarrage
Télécharger la version initiale du projet sur GitHub
À partir de la ligne de commande, clonez le dépôt GitHub dans un répertoire flutter-codelabs:
$ git clone https://github.com/flutter/codelabs.git flutter-codelabs
Après avoir cloné le dépôt, vous trouverez le code de cet atelier de programmation dans le répertoire flutter-codelabs/homescreen_codelab. Ce répertoire contient le code du projet finalisé pour chaque étape de l'atelier de programmation.
Ouvrir l'application de démarrage
Ouvrez le répertoire flutter-codelabs/homescreen_codelab/step_03
dans l'IDE de votre choix.
Installer les packages
Tous les packages requis ont été ajoutés au fichier pubspec.yaml du projet. Pour récupérer les dépendances du projet, exécutez la commande suivante:
$ flutter pub get
3. Ajouter un widget de base sur l'écran d'accueil
Tout d'abord, ajoutez le widget de l'écran d'accueil à l'aide des outils de la plate-forme native.
Créer un widget de base pour l'écran d'accueil iOS
L'ajout d'une extension d'application à votre application iOS Flutter est semblable à l'ajout d'une extension d'application à une application SwiftUI ou UIKit:
- Exécutez
open ios/Runner.xcworkspace
dans une fenêtre de terminal à partir du répertoire de votre projet Flutter. Vous pouvez également effectuer un clic droit sur le dossier ios à partir de VSCode, puis sélectionner Ouvrir dans Xcode. L'espace de travail Xcode par défaut s'ouvre dans votre projet Flutter. - Dans le menu, sélectionnez File → New → Target (Fichier → Nouveau → Cible). Une nouvelle cible est alors ajoutée au projet.
- La liste des modèles s'affiche. Sélectionnez Extension de widget.
- Saisissez "NewsWidgets". dans le champ Nom du produit pour ce widget. Décochez les cases Inclure l'activité en direct et Inclure l'intent de configuration.
Inspecter l'exemple de code
Lorsque vous ajoutez une cible, Xcode génère un exemple de code basé sur le modèle que vous avez sélectionné. Pour en savoir plus sur le code généré et sur WidgetKit, consultez la documentation sur les extensions d'application d'Apple.
Déboguer et tester votre exemple de widget
- Commencez par mettre à jour la configuration de votre application Flutter. Vous devez le faire lorsque vous ajoutez des packages dans votre application Flutter et que vous prévoyez d'exécuter une cible dans le projet à partir de Xcode. Pour mettre à jour la configuration de votre application, exécutez la commande suivante dans le répertoire de votre application Flutter:
$ flutter build ios --config-only
- Cliquez sur Runner (Exécuter) pour afficher la liste des cibles. Sélectionnez la cible du widget que vous venez de créer (NewsWidgets), puis cliquez sur Run (Exécuter). Exécutez la cible du widget à partir de Xcode lorsque vous modifiez le code du widget iOS.
- L'écran du simulateur ou de l'appareil doit afficher un widget de base sur l'écran d'accueil. Si vous ne la voyez pas, vous pouvez l'ajouter à l'écran. Cliquez de manière prolongée sur l'écran d'accueil, puis cliquez sur + en haut à gauche.
- Recherchez le nom de l'application. Pour cet atelier de programmation, recherchez "Widgets pour l'écran d'accueil".
- Une fois que vous avez ajouté le widget de l'écran d'accueil, il doit afficher un simple texte indiquant l'heure.
Créer un widget Android de base
- Pour ajouter un widget de l'écran d'accueil dans Android, ouvrez le fichier de compilation du projet dans Android Studio. Vous pouvez trouver ce fichier dans android/build.gradle. Vous pouvez également effectuer un clic droit sur le dossier android dans VSCode, puis sélectionner Ouvrir dans Android Studio.
- Une fois le projet créé, localisez le répertoire de l'application en haut à gauche. Ajoutez votre nouveau widget d'écran d'accueil à ce répertoire. Effectuez un clic droit sur le répertoire, puis sélectionnez New -> (Nouveau ->) Widget -> Widget d'application.
- Android Studio affiche un nouveau formulaire. Ajoutez des informations de base sur votre widget de l'écran d'accueil, y compris le nom de la classe, son emplacement, sa taille et la langue source.
Pour cet atelier de programmation, définissez les valeurs suivantes:
- Class Name (Nom de classe) dans NewsWidget
- Menu déroulant Largeur minimale (cellules) sur 3
- Menu déroulant Hauteur minimale (cellules) sur 3
Inspecter l'exemple de code
Lorsque vous envoyez le formulaire, Android Studio crée et met à jour plusieurs fichiers. Les modifications pertinentes pour cet atelier de programmation sont répertoriées dans le tableau ci-dessous
Action | Fichier cible | Modifier |
Mettre à jour |
| Ajoute un nouveau récepteur qui enregistre le widget NewsWidget. |
Créer |
| Définit l'interface utilisateur du widget de l'écran d'accueil. |
Créer |
| Définit la configuration du widget de l'écran d'accueil. Vous pouvez ajuster les dimensions ou le nom de votre widget dans ce fichier. |
Créer |
| Contient votre code Kotlin pour ajouter des fonctionnalités à votre widget de l'écran d'accueil. |
Vous trouverez plus de détails sur ces fichiers tout au long de cet atelier de programmation.
Déboguer et tester votre exemple de widget
Exécutez maintenant votre application et affichez le widget de l'écran d'accueil. Une fois l'application créée, accédez à l'écran de sélection de l'application sur votre appareil Android et appuyez de manière prolongée sur l'icône de ce projet Flutter. Sélectionnez Widgets dans le menu pop-up.
L'appareil ou l'émulateur Android affiche le widget de l'écran d'accueil par défaut pour Android.
4. Envoyer des données de votre application Flutter vers le widget de l'écran d'accueil
Vous pouvez personnaliser le widget de base de l'écran d'accueil que vous avez créé. Mettez à jour le widget de l'écran d'accueil pour afficher un titre et un résumé pour un article d'actualité. La capture d'écran suivante montre un exemple de widget de l'écran d'accueil affichant un titre et un résumé.
Pour transmettre des données entre votre application et le widget de l'écran d'accueil, vous devez écrire du code natif et Dart. Ce processus est divisé en trois parties:
- Écrire dans votre application Flutter du code Dart compatible avec Android et iOS
- Ajouter des fonctionnalités iOS natives
- Ajouter des fonctionnalités Android natives
Utiliser des groupes d'applications iOS
Pour partager des données entre une application iOS parente et une extension de widget, les deux cibles doivent appartenir au même groupe d'applications. Pour en savoir plus sur les groupes d'applications, consultez la documentation sur les groupes d'applications d'Apple.
Mettez à jour votre identifiant de bundle:
Dans Xcode, accédez aux paramètres de votre cible. Dans la section Signing & Capacités, vérifiez que l'identifiant de votre équipe et de votre groupe est défini.
Ajoutez le groupe d'applications à la fois à la cible Runner et à la cible NewsWidgetExtension dans Xcode:
Sélectionnez + Capability -> (+ Capacité ->). Groupes d'applications et ajoutez un nouveau groupe d'applications. Répétez l'opération pour la cible Runner (application parent) et la cible du widget.
Ajouter le code Dart
Les applications iOS et Android peuvent partager des données avec une application Flutter de différentes manières.Pour communiquer avec ces applications, utilisez le key/value
local de l'appareil. Pour iOS, ce magasin est appelé UserDefaults
, et Android l'appelle SharedPreferences
. Le package home_widget encapsule ces API pour simplifier l'enregistrement des données sur les deux plates-formes et permet aux widgets de l'écran d'accueil d'extraire les données mises à jour.
Les données relatives au titre et à la description proviennent du fichier news_data.dart
. Ce fichier contient des données fictives et une classe de données NewsArticle
.
lib/news_data.dart
class NewsArticle {
final String title;
final String description;
final String? articleText;
NewsArticle({
required this.title,
required this.description,
this.articleText = loremIpsum,
});
}
Modifier les valeurs du titre et de la description
Pour ajouter la fonctionnalité permettant de mettre à jour le widget de l'écran d'accueil à partir de votre application Flutter, accédez au fichier lib/home_screen.dart
. Remplacez le contenu du fichier par le code suivant. Remplacez ensuite <YOUR APP GROUP>
par l'identifiant de votre groupe d'applications.
lib/home_screen.dart
import 'package:flutter/material.dart';
import 'package:home_widget/home_widget.dart'; // Add this import
import 'article_screen.dart';
import 'news_data.dart';
// TODO: Replace with your App Group ID
const String appGroupId = '<YOUR APP GROUP>'; // Add from here
const String iOSWidgetName = 'NewsWidgets';
const String androidWidgetName = 'NewsWidget'; // To here.
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
void updateHeadline(NewsArticle newHeadline) { // Add from here
// Save the headline data to the widget
HomeWidget.saveWidgetData<String>('headline_title', newHeadline.title);
HomeWidget.saveWidgetData<String>(
'headline_description', newHeadline.description);
HomeWidget.updateWidget(
iOSName: iOSWidgetName,
androidName: androidWidgetName,
);
} // To here.
class _MyHomePageState extends State<MyHomePage> {
@override // Add from here
void initState() {
super.initState();
HomeWidget.setAppGroupId(appGroupId);
// Mock read in some data and update the headline
final newHeadline = getNewsStories()[0];
updateHeadline(newHeadline);
} // To here.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Top Stories'),
centerTitle: false,
titleTextStyle: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Colors.black)),
body: ListView.separated(
separatorBuilder: (context, idx) {
return const Divider();
},
itemCount: getNewsStories().length,
itemBuilder: (context, idx) {
final article = getNewsStories()[idx];
return ListTile(
key: Key('$idx ${article.hashCode}'),
title: Text(article.title!),
subtitle: Text(article.description!),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) {
return ArticleScreen(article: article);
},
),
);
},
);
},
));
}
}
La fonction updateHeadline
enregistre les paires clé/valeur dans l'espace de stockage local de votre appareil. La clé headline_title
contient la valeur de newHeadline.title
. La clé headline_description
contient la valeur de newHeadline.description
. Elle informe également la plate-forme native que les nouvelles données des widgets de l'écran d'accueil peuvent être récupérées et affichées.
Modifier lefloatActionButton
Appelez la fonction updateHeadline
lorsque vous appuyez sur floatingActionButton
, comme indiqué ci-dessous:
lib/article_screen.dart
// New: import the updateHeadline function
import 'home_screen.dart';
...
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('Updating home screen widget...'),
));
// New: call updateHeadline
updateHeadline(widget.article);
},
label: const Text('Update Homescreen'),
),
...
Avec cette modification, lorsqu'un utilisateur appuie sur le bouton Mettre à jour le titre sur la page d'un article, les détails du widget de l'écran d'accueil sont mis à jour.
Modifier le code iOS pour afficher les données de l'article
Pour mettre à jour le widget de l'écran d'accueil pour iOS, utilisez Xcode.
Ouvrez le fichier NewsWidgets.swift
dans Xcode:
Configurez le TimelineEntry
.
Remplacez la structure SimpleEntry
par le code suivant:
ios/NewsWidgets/NewsWidgets.swift
// The date and any data you want to pass into your app must conform to TimelineEntry
struct NewsArticleEntry: TimelineEntry {
let date: Date
let title: String
let description:String
}
Cette structure NewsArticleEntry
définit les données entrantes à transmettre au widget de l'écran d'accueil lors de sa mise à jour. Le type TimelineEntry
nécessite un paramètre de date.Pour en savoir plus sur le protocole TimelineEntry
, consultez la documentation Apple TimelineEntry.
Modifiez l'élément View
qui affiche le contenu.
Modifiez le widget de l'écran d'accueil pour qu'il affiche le titre et la description de l'article de presse plutôt que la date. Pour afficher du texte en SwiftUI, utilisez la vue Text
. Pour empiler des vues dans SwiftUI, utilisez la vue VStack
.
Remplacez la vue NewsWidgetEntryView
générée par le code suivant:
ios/NewsWidgets/NewsWidgets.swift
//View that holds the contents of the widget
struct NewsWidgetsEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack {
Text(entry.title)
Text(entry.description)
}
}
}
Modifier le fournisseur pour indiquer au widget de l'écran d'accueil quand et comment effectuer la mise à jour
Remplacez le Provider
existant par le code suivant. Remplacez ensuite <YOUR APP GROUP> par l'identifiant de votre groupe d'applications :
ios/NewsWidgets/NewsWidgets.swift
struct Provider: TimelineProvider {
// Placeholder is used as a placeholder when the widget is first displayed
func placeholder(in context: Context) -> NewsArticleEntry {
// Add some placeholder title and description, and get the current date
NewsArticleEntry(date: Date(), title: "Placeholder Title", description: "Placeholder description")
}
// Snapshot entry represents the current time and state
func getSnapshot(in context: Context, completion: @escaping (NewsArticleEntry) -> ()) {
let entry: NewsArticleEntry
if context.isPreview{
entry = placeholder(in: context)
}
else{
// Get the data from the user defaults to display
let userDefaults = UserDefaults(suiteName: <YOUR APP GROUP>)
let title = userDefaults?.string(forKey: "headline_title") ?? "No Title Set"
let description = userDefaults?.string(forKey: "headline_description") ?? "No Description Set"
entry = NewsArticleEntry(date: Date(), title: title, description: description)
}
completion(entry)
}
// getTimeline is called for the current and optionally future times to update the widget
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
// This just uses the snapshot function you defined earlier
getSnapshot(in: context) { (entry) in
// atEnd policy tells widgetkit to request a new entry after the date has passed
let timeline = Timeline(entries: [entry], policy: .atEnd)
completion(timeline)
}
}
}
Le Provider
dans le code précédent est conforme à un TimelineProvider
. Provider
propose trois méthodes différentes:
- La méthode
placeholder
génère une entrée d'espace réservé lorsque l'utilisateur affiche pour la première fois un aperçu du widget de l'écran d'accueil.
- La méthode
getSnapshot
lit les données à partir des valeurs par défaut de l'utilisateur et génère l'entrée pour l'heure actuelle. - La méthode
getTimeline
renvoie des entrées de chronologie. Cela s'avère utile lorsque vous pouvez mettre à jour votre contenu à des moments prévisibles. Cet atelier de programmation utilise la fonction getSnapshot pour obtenir l'état actuel. La méthode.atEnd
indique au widget de l'écran d'accueil d'actualiser les données une fois l'heure actuelle écoulée.
Mettez en commentaire le NewsWidgets_Previews
L'utilisation d'aperçus n'est pas abordée dans cet atelier de programmation. Pour en savoir plus sur la prévisualisation des widgets de l'écran d'accueil SwiftUI, consultez la documentation d'Apple sur le débogage des widgets.
Enregistrez tous les fichiers, puis réexécutez l'application et la cible du widget.
Exécutez à nouveau les cibles pour vérifier que l'application et le widget de l'écran d'accueil fonctionnent.
- Sélectionnez le schéma d'application dans Xcode pour exécuter la cible de l'application.
- Sélectionnez le schéma de l'extension dans Xcode pour exécuter la cible de l'extension.
- Accédez à la page d'un article dans l'application.
- Cliquez sur le bouton pour modifier le titre. Le widget de l'écran d'accueil doit également modifier le titre.
Mettre à jour le code Android
Ajoutez le fichier XML du widget de l'écran d'accueil.
Dans Android Studio, mettez à jour les fichiers générés à l'étape précédente.Ouvrez le fichier res/layout/news_widget.xml
. Il définit la structure et la mise en page de votre widget sur l'écran d'accueil. Sélectionnez Code en haut à droite et remplacez le contenu de ce fichier par le code suivant:
android/app/res/layout/news_widget.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/widget_container"
style="@style/Widget.Android.AppWidget.Container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@android:color/white"
android:theme="@style/Theme.Android.AppWidgetContainer">
<TextView
android:id="@+id/headline_title"
style="@style/Widget.Android.AppWidget.InnerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:background="@android:color/white"
android:text="Title"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/headline_description"
style="@style/Widget.Android.AppWidget.InnerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/headline_title"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="4dp"
android:background="@android:color/white"
android:text="Title"
android:textSize="16sp" />
</RelativeLayout>
Ce fichier XML définit deux affichages de texte, l'un pour le titre de l'article et l'autre pour la description de l'article. Ces affichages de texte définissent également le style. Vous reviendrez sur ce fichier tout au long de cet atelier de programmation.
Mettre à jour la fonctionnalité NewsWidget
Ouvrez le fichier de code source Kotlin NewsWidget.kt
. Ce fichier contient une classe générée appelée NewsWidget
qui étend la classe AppWidgetProvider
.
La classe NewsWidget
contient trois méthodes de sa super-classe. Vous allez modifier la méthode onUpdate
. Android appelle cette méthode pour les widgets à intervalles fixes.
Remplacez le contenu du fichier NewsWidget.kt
par le code suivant:
android/app/java/com.mydomain.homescreen_widgets/NewsWidget.kt
// Import will depend on App ID.
package com.mydomain.homescreen_widgets
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.widget.RemoteViews
// New import.
import es.antonborri.home_widget.HomeWidgetPlugin
/**
* Implementation of App Widget functionality.
*/
class NewsWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray,
) {
for (appWidgetId in appWidgetIds) {
// Get reference to SharedPreferences
val widgetData = HomeWidgetPlugin.getData(context)
val views = RemoteViews(context.packageName, R.layout.news_widget).apply {
val title = widgetData.getString("headline_title", null)
setTextViewText(R.id.headline_title, title ?: "No title set")
val description = widgetData.getString("headline_description", null)
setTextViewText(R.id.headline_description, description ?: "No description set")
}
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
}
Désormais, lorsque onUpdate
est appelé, Android récupère les valeurs les plus récentes du stockage local à l'aide de la méthode the widgetData.getString()
, puis appelle setTextViewText
pour modifier le texte affiché dans le widget de l'écran d'accueil.
Tester les mises à jour
Testez l'application pour vous assurer que les widgets de l'écran d'accueil se mettent à jour avec les nouvelles données. Pour modifier les données, utilisez l'option Mettre à jour l'écran d'accueil FloatingActionButton
sur les pages des articles. Le widget de l'écran d'accueil devrait se mettre à jour avec le titre de l'article.
5. Utiliser les polices personnalisées de l'application Flutter dans le widget de l'écran d'accueil iOS
Jusqu'à présent, vous avez configuré le widget de l'écran d'accueil pour qu'il lise les données fournies par l'application Flutter. L'application Flutter inclut une police personnalisée que vous pouvez utiliser dans le widget de l'écran d'accueil. Vous pouvez utiliser la police personnalisée dans le widget de l'écran d'accueil iOS. L'utilisation de polices personnalisées dans les widgets de l'écran d'accueil n'est pas disponible sur Android.
Mettre à jour le code iOS
Flutter stocke ses éléments dans le mainBundle d'applications iOS. Vous pouvez accéder aux éléments de ce pack à partir du code du widget de l'écran d'accueil.
Dans la structure NewsWidgetsEntryView de votre fichier NewsWidgets.swift, apportez les modifications suivantes :
Créez une fonction d'assistance pour obtenir le chemin d'accès au répertoire des éléments Flutter:
ios/NewsWidgets/NewsWidgets.swift
struct NewsWidgetsEntryView : View {
...
// New: Add the helper function.
var bundle: URL {
let bundle = Bundle.main
if bundle.bundleURL.pathExtension == "appex" {
// Peel off two directory levels - MY_APP.app/PlugIns/MY_APP_EXTENSION.appex
var url = bundle.bundleURL.deletingLastPathComponent().deletingLastPathComponent()
url.append(component: "Frameworks/App.framework/flutter_assets")
return url
}
return bundle.bundleURL
}
...
}
Enregistrez la police à l'aide de l'URL de votre fichier de police personnalisée.
ios/NewsWidgets/NewsWidgets.swift
struct NewsWidgetsEntryView : View {
...
// New: Register the font.
init(entry: Provider.Entry){
self.entry = entry
CTFontManagerRegisterFontsForURL(bundle.appending(path: "/fonts/Chewy-Regular.ttf") as CFURL, CTFontManagerScope.process, nil)
}
...
}
Mettez à jour l'affichage Texte du titre pour utiliser votre police personnalisée.
ios/NewsWidgets/NewsWidgets.swift
struct NewsWidgetsEntryView : View {
...
var body: some View {
VStack {
// Update the following line.
Text(entry.title).font(Font.custom("Chewy", size: 13))
Text(entry.description)
}
}
...
}
Lorsque vous exécutez le widget de l'écran d'accueil, il utilise désormais la police personnalisée pour le titre, comme illustré dans l'image suivante:
6. Afficher des widgets Flutter en tant qu'image
Dans cette section, vous allez afficher un graphique de votre application Flutter en tant que widget de l'écran d'accueil.
Ce widget est plus complexe que le texte affiché sur l'écran d'accueil. Il est beaucoup plus facile d'afficher le graphique Flutter sous forme d'image que d'essayer de le recréer à l'aide de composants d'UI natifs.
Codez votre widget de l'écran d'accueil pour afficher votre graphique Flutter au format PNG. Le widget de l'écran d'accueil peut afficher cette image.
Écrire le code Dart
Du côté de Dart, ajoutez la méthode renderFlutterWidget
du package home_widget. Cette méthode utilise un widget, un nom de fichier et une clé. Il renvoie une image du widget Flutter et l'enregistre dans un conteneur partagé. Indiquez le nom de l'image dans votre code et assurez-vous que le widget de l'écran d'accueil peut accéder au conteneur. key
enregistre le chemin d'accès complet au fichier sous forme de chaîne dans l'espace de stockage local de l'appareil. Cela permet au widget de l'écran d'accueil de trouver le fichier si son nom change dans le code Dart.
Pour cet atelier de programmation, la classe LineChart
du fichier lib/article_screen.dart
représente le graphique. Sa méthode de compilation renvoie un CustomPainter qui affiche ce graphique à l'écran.
Pour implémenter cette fonctionnalité, ouvrez le fichier lib/article_screen.dart
. Importez le package home_widget. Remplacez ensuite le code de la classe _ArticleScreenState
par le code suivant:
lib/article_screen.dart
import 'package:flutter/material.dart';
// New: import the home_widget package.
import 'package:home_widget/home_widget.dart';
import 'home_screen.dart';
import 'news_data.dart';
...
class _ArticleScreenState extends State<ArticleScreen> {
// New: add this GlobalKey
final _globalKey = GlobalKey();
String? imagePath;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.article.title!),
),
// New: add this FloatingActionButton
floatingActionButton: FloatingActionButton.extended(
onPressed: () async {
if (_globalKey.currentContext != null) {
var path = await HomeWidget.renderFlutterWidget(
const LineChart(),
fileName: 'screenshot',
key: 'filename',
logicalSize: _globalKey.currentContext!.size,
pixelRatio:
MediaQuery.of(_globalKey.currentContext!).devicePixelRatio,
);
setState(() {
imagePath = path as String?;
});
}
updateHeadline(widget.article);
},
label: const Text('Update Homescreen'),
),
body: ListView(
padding: const EdgeInsets.all(16.0),
children: [
Text(
widget.article.description!,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 20.0),
Text(widget.article.articleText!),
const SizedBox(height: 20.0),
Center(
// New: Add this key
key: _globalKey,
child: const LineChart(),
),
const SizedBox(height: 20.0),
Text(widget.article.articleText!),
],
),
);
}
}
Cet exemple apporte trois modifications à la classe _ArticleScreenState
.
Crée une GlobalKey
GlobalKey
récupère le contexte du widget spécifique, qui est nécessaire pour obtenir la taille de ce widget .
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
// New: add this GlobalKey
final _globalKey = GlobalKey();
...
}
Ajoute imagePath
La propriété imagePath
stocke l'emplacement de l'image où le widget Flutter est affiché.
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
...
// New: add this imagePath
String? imagePath;
...
}
Ajoute la clé au widget pour le rendu
_globalKey
contient le widget Flutter qui s'affiche dans l'image. Dans ce cas, le widget Flutter est le centre qui contient LineChart
.
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
...
Center(
// New: Add this key
key: _globalKey,
child: const LineChart(),
),
...
}
- Enregistre le widget en tant qu'image
La méthode renderFlutterWidget
est appelée lorsque l'utilisateur clique sur floatingActionButton
. La méthode enregistre le fichier PNG obtenu en tant que "capture d'écran". vers le répertoire partagé du conteneur. Elle enregistre également le chemin d'accès complet à l'image en tant que clé de nom de fichier dans l'espace de stockage de l'appareil.
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
...
floatingActionButton: FloatingActionButton.extended(
onPressed: () async {
if (_globalKey.currentContext != null) {
var path = await HomeWidget.renderFlutterWidget(
LineChart(),
fileName: 'screenshot',
key: 'filename',
logicalSize: _globalKey.currentContext!.size,
pixelRatio:
MediaQuery.of(_globalKey.currentContext!).devicePixelRatio,
);
setState(() {
imagePath = path as String?;
});
}
updateHeadline(widget.article);
},
...
}
Modifier le code iOS
Pour iOS, mettez à jour le code pour récupérer le chemin d'accès au fichier à partir de l'espace de stockage et afficher le fichier en tant qu'image à l'aide de SwiftUI.
Ouvrez le fichier NewsWidgets.swift
pour apporter les modifications suivantes:
Ajoutez filename
et displaySize
au struct NewsArticleEntry
.
La propriété filename
contient la chaîne représentant le chemin d'accès au fichier image. La propriété displaySize
contient la taille du widget de l'écran d'accueil sur l'appareil de l'utilisateur. La taille du widget de l'écran d'accueil est déterminée par context
.
ios/NewsWidgets/NewsWidgets.swift
struct NewsArticleEntry: TimelineEntry {
...
// New: add the filename and displaySize.
let filename: String
let displaySize: CGSize
}
Mettez à jour la fonction placeholder
.
Incluez un espace réservé filename
et displaySize
.
ios/NewsWidgets/NewsWidgets.swift
func placeholder(in context: Context) -> NewsArticleEntry {
NewsArticleEntry(date: Date(), title: "Placeholder Title", description: "Placeholder description", filename: "No screenshot available", displaySize: context.displaySize)
}
Récupérez le nom de fichier à partir de userDefaults
dans getSnapshot.
Cela définit la variable filename
sur la valeur filename
dans l'espace de stockage userDefaults
lors de la mise à jour du widget de l'écran d'accueil.
ios/NewsWidgets/NewsWidgets.swift
func getSnapshot(
...
let title = userDefaults?.string(forKey: "headline_title") ?? "No Title Set"
let description = userDefaults?.string(forKey: "headline_description") ?? "No Description Set"
// New: get fileName from key/value store
let filename = userDefaults?.string(forKey: "filename") ?? "No screenshot available"
...
)
Créer une image ChartImage qui affiche l'image à partir d'un chemin d'accès
La vue ChartImage
crée une image à partir du contenu du fichier généré côté Dart. Ici, vous définissez la taille sur 50% du cadre.
ios/NewsWidgets/NewsWidgets.swift
struct NewsWidgetsEntryView : View {
...
// New: create the ChartImage view
var ChartImage: some View {
if let uiImage = UIImage(contentsOfFile: entry.filename) {
let image = Image(uiImage: uiImage)
.resizable()
.frame(width: entry.displaySize.height*0.5, height: entry.displaySize.height*0.5, alignment: .center)
return AnyView(image)
}
print("The image file could not be loaded")
return AnyView(EmptyView())
}
...
}
Utilisez l'élément ChartImage dans le corps de NewsWidgetsEntryView.
Ajoutez la vue ChartImage au corps de NewsWidgetsEntryView pour afficher la vue ChartImage dans le widget de l'écran d'accueil.
ios/NewsWidgets/NewsWidgets.swift
VStack {
Text(entry.title).font(Font.custom("Chewy", size: 13))
Text(entry.description).font(.system(size: 12)).padding(10)
// New: add the ChartImage to the NewsWidgetEntryView
ChartImage
}
Tester les modifications
Pour tester les modifications, exécutez de nouveau votre cible d'application Flutter (Runner) et votre cible d'extension à partir de Xcode. Pour voir l'image, accédez à l'une des pages d'article dans l'application, puis appuyez sur le bouton pour mettre à jour le widget de l'écran d'accueil.
Mettre à jour le code Android
Le code Android fonctionne de la même manière que le code iOS.
- Ouvrez le fichier
android/app/res/layout/news_widget.xml
. Il contient les éléments d'interface utilisateur de votre widget sur l'écran d'accueil. Remplacez son contenu par le code suivant:
android/app/res/layout/news_widget.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/widget_container"
style="@style/Widget.Android.AppWidget.Container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@android:color/white"
android:theme="@style/Theme.Android.AppWidgetContainer">
<TextView
android:id="@+id/headline_title"
style="@style/Widget.Android.AppWidget.InnerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:background="@android:color/white"
android:text="Title"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/headline_description"
style="@style/Widget.Android.AppWidget.InnerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/headline_title"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="4dp"
android:background="@android:color/white"
android:text="Title"
android:textSize="16sp" />
<!--New: add this image view -->
<ImageView
android:id="@+id/widget_image"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_below="@+id/headline_description"
android:layout_alignBottom="@+id/headline_title"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="-134dp"
android:layout_weight="1"
android:adjustViewBounds="true"
android:background="@android:color/white"
android:scaleType="fitCenter"
android:src="@android:drawable/star_big_on"
android:visibility="visible"
tools:visibility="visible" />
</RelativeLayout>
Ce nouveau code ajoute une image au widget de l'écran d'accueil, qui affiche (pour le moment) une icône générique en forme d'étoile. Remplacez cette icône en forme d'étoile par l'image que vous avez enregistrée dans le code Dart.
- Ouvrez le fichier
NewsWidget.kt
. Remplacez son contenu par le code suivant:
android/app/java/com.mydomain.homescreen_widgets/NewsWidget.kt
// Import will depend on App ID.
package com.mydomain.homescreen_widgets
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.widget.RemoteViews
import java.io.File
import es.antonborri.home_widget.HomeWidgetPlugin
/**
* Implementation of App Widget functionality.
*/
class NewsWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray,
) {
for (appWidgetId in appWidgetIds) {
val widgetData = HomeWidgetPlugin.getData(context)
val views = RemoteViews(context.packageName, R.layout.news_widget).apply {
val title = widgetData.getString("headline_title", null)
setTextViewText(R.id.headline_title, title ?: "No title set")
val description = widgetData.getString("headline_description", null)
setTextViewText(R.id.headline_description, description ?: "No description set")
// New: Add the section below
// Get chart image and put it in the widget, if it exists
val imageName = widgetData.getString("filename", null)
val imageFile = File(imageName)
val imageExists = imageFile.exists()
if (imageExists) {
val myBitmap: Bitmap = BitmapFactory.decodeFile(imageFile.absolutePath)
setImageViewBitmap(R.id.widget_image, myBitmap)
} else {
println("image not found!, looked @: ${imageName}")
}
// End new code
}
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
}
Ce code Dart enregistre une capture d'écran dans un espace de stockage local à l'aide de la clé filename
. Elle récupère également le chemin d'accès complet de l'image et crée un objet File
à partir de celle-ci. Si l'image existe déjà, le code Dart remplace l'image présente dans le widget de l'écran d'accueil par la nouvelle image.
- Actualisez votre application et accédez à l'écran d'un article. Appuyez sur Mettre à jour l'écran d'accueil. Le widget de l'écran d'accueil affiche le graphique.
7. Étapes suivantes
Félicitations !
Félicitations, vous avez réussi à créer des widgets d'écran d'accueil pour vos applications Flutter iOS et Android.
Créer des liens vers du contenu de votre application Flutter
Vous pouvez rediriger l'utilisateur vers une page spécifique de votre application, en fonction de l'endroit où il clique. Par exemple, dans l'application d'actualités de cet atelier de programmation, vous souhaiterez peut-être que l'utilisateur voie l'article d'actualité correspondant au titre affiché.
Cette fonctionnalité n'entre pas dans le cadre de cet atelier de programmation. Vous trouverez des exemples d'utilisation d'un flux fourni par le package home_widget pour identifier les lancements d'applications à partir des widgets de l'écran d'accueil et envoyer des messages à partir du widget de l'écran d'accueil via l'URL. Pour en savoir plus, consultez la documentation sur les liens profonds sur docs.flutter.dev.
Mettre à jour votre widget en arrière-plan
Dans cet atelier de programmation, vous avez déclenché une mise à jour du widget de l'écran d'accueil à l'aide d'un bouton. Bien que cela soit raisonnable pour les tests, dans le code de production, vous pouvez souhaiter que votre application mette à jour le widget de l'écran d'accueil en arrière-plan. Vous pouvez utiliser le plug-in Workmanager pour créer des tâches en arrière-plan afin de mettre à jour les ressources dont le widget de l'écran d'accueil a besoin. Pour en savoir plus, consultez la section Mise à jour en arrière-plan du package home_widget.
Pour iOS, vous pouvez également faire en sorte que le widget de l'écran d'accueil envoie une requête réseau pour mettre à jour son interface utilisateur. Pour contrôler les conditions ou la fréquence de cette requête, utilisez la timeline. Pour en savoir plus sur l'utilisation de la chronologie, consultez le document d'Apple intitulé Keeping a widget up to date dans la documentation Google Cloud.