1. Wprowadzenie
Czym są widżety?
W przypadku deweloperów Flutter powszechna definicja widżetu odnosi się do komponentów interfejsu utworzonych za pomocą platformy Flutter. W tym laboratorium widżet to miniaturowa wersja aplikacji, która umożliwia przeglądanie informacji z aplikacji bez jej otwierania. Na Androidzie widżety znajdują się na ekranie głównym. Na urządzeniach z iOS można je dodać do ekranu głównego, ekranu blokady lub widoku Dziś.

Jak złożony może być widżet?
Większość widżetów na ekranie głównym jest prosta. Mogą one zawierać podstawowy tekst, proste grafiki lub, w przypadku Androida, podstawowe elementy sterujące. Zarówno Android, jak i iOS ograniczają komponenty interfejsu i funkcje, których możesz używać.

Tworzenie interfejsu widżetów
Ze względu na te ograniczenia interfejsu nie możesz bezpośrednio rysować interfejsu widżetu ekranu głównego za pomocą platformy Flutter. Zamiast tego możesz dodać do aplikacji Flutter widżety utworzone za pomocą platform takich jak Jetpack Compose czy SwiftUI. W tym laboratorium kodowania znajdziesz przykłady udostępniania zasobów między aplikacją a widżetami, aby uniknąć ponownego pisania złożonego interfejsu.
Co utworzysz
W tym ćwiczeniu utworzysz widżety ekranu głównego na Androidzie i iOS w prostej aplikacji Flutter, która umożliwia użytkownikom czytanie artykułów. Użyjesz do tego pakietu home_widget. Widżety:
- Wyświetlaj dane z aplikacji Flutter.
- Wyświetlanie tekstu za pomocą komponentów czcionek udostępnionych z aplikacji Flutter.
- Wyświetla obraz wyrenderowanego widżetu Flutter.

Ta aplikacja Flutter zawiera 2 ekrany (lub trasy):
- Pierwsza z nich wyświetla listę artykułów z nagłówkami i tekstami.
- Drugi zawiera pełny artykuł z wykresem utworzonym za pomocą
CustomPaint.
.

Czego się nauczysz
- Jak tworzyć widżety ekranu głównego na iOS i Androidzie
- Jak używać pakietu home_widget do udostępniania danych między widżetem ekranu głównego a aplikacją Flutter.
- Jak zmniejszyć ilość kodu, który musisz przepisać.
- Jak zaktualizować widżet ekranu głównego z aplikacji Flutter.
2. Konfigurowanie środowiska programistycznego
Na obu platformach potrzebujesz pakietu Flutter SDK i zintegrowanego środowiska programistycznego. Do pracy z Flutterem możesz używać preferowanego środowiska IDE. Może to być Visual Studio Code z rozszerzeniami Dart Code i Flutter lub Android Studio lub IntelliJ z zainstalowanymi wtyczkami Flutter i Dart.
Aby utworzyć widżet ekranu głównego iOS:
- Te ćwiczenia możesz wykonać na fizycznym urządzeniu z iOS lub w symulatorze iOS.
- Musisz skonfigurować system macOS ze środowiskiem IDE Xcode. Spowoduje to zainstalowanie kompilatora potrzebnego do utworzenia wersji aplikacji na iOS.
Aby utworzyć widżet ekranu głównego Androida:
- Te warsztaty możesz przeprowadzić na fizycznym urządzeniu z Androidem lub w emulatorze Androida.
- Musisz skonfigurować system programistyczny za pomocą Androida Studio. Spowoduje to zainstalowanie kompilatora potrzebnego do utworzenia wersji aplikacji na Androida.
Pobierz kod startowy
Pobieranie początkowej wersji projektu z GitHuba
W wierszu poleceń sklonuj repozytorium GitHub do katalogu flutter-codelabs:
$ git clone https://github.com/flutter/codelabs.git flutter-codelabs
Po sklonowaniu repozytorium kod tego ćwiczenia znajdziesz w katalogu flutter-codelabs/homescreen_codelab. Ten katalog zawiera gotowy kod projektu dla każdego kroku w tym module.
Otwórz aplikację startową
Otwórz katalog flutter-codelabs/homescreen_codelab/step_03 w wybranym środowisku IDE.
Instalowanie pakietów
Wszystkie wymagane pakiety zostały dodane do pliku pubspec.yaml projektu. Aby pobrać zależności projektu, uruchom to polecenie:
$ flutter pub get
3. Dodawanie podstawowego widżetu ekranu głównego
Najpierw dodaj widżet ekranu głównego za pomocą narzędzi platformy natywnej.
Tworzenie podstawowego widżetu ekranu głównego iOS
Dodawanie rozszerzenia aplikacji do aplikacji na iOS w Flutterze jest podobne do dodawania rozszerzenia aplikacji do aplikacji w SwiftUI lub UIKit:
- Uruchom polecenie
open ios/Runner.xcworkspacew oknie terminala w katalogu projektu Flutter. Możesz też kliknąć prawym przyciskiem myszy folder ios w VSCode i wybrać Open in Xcode (Otwórz w Xcode). Spowoduje to otwarcie domyślnego obszaru roboczego Xcode w projekcie Flutter. - W menu wybierz Plik → Nowy → Cel. Spowoduje to dodanie do projektu nowego miejsca docelowego.
- Pojawi się lista szablonów. Wybierz Rozszerzenie widżetu.
- Wpisz „NewsWidgets” w polu Nazwa usługi tego widżetu. Odznacz pola Uwzględnij aktywność na żywo i Uwzględnij intencję konfiguracji.
Sprawdź przykładowy kod
Gdy dodasz nowy element docelowy, Xcode wygeneruje przykładowy kod na podstawie wybranego szablonu. Więcej informacji o wygenerowanym kodzie i WidgetKit znajdziesz w dokumentacji Apple dotyczącej rozszerzeń aplikacji .
Debugowanie i testowanie przykładowego widżetu
- Najpierw zaktualizuj konfigurację aplikacji Flutter. Musisz to zrobić, gdy dodajesz nowe pakiety w aplikacji Flutter i planujesz uruchomić w projekcie element docelowy z Xcode. Aby zaktualizować konfigurację aplikacji, uruchom to polecenie w katalogu aplikacji Flutter:
$ flutter build ios --config-only
- Kliknij Runner, aby wyświetlić listę celów. Wybierz utworzony przed chwilą element docelowy widżetu NewsWidgets i kliknij Uruchom. Po zmianie kodu widżetu iOS uruchom cel widżetu w Xcode.

- Na ekranie symulatora lub urządzenia powinien być widoczny podstawowy widżet ekranu głównego. Jeśli go nie widzisz, możesz dodać go do ekranu. Kliknij i przytrzymaj ekran główny, a potem kliknij + w lewym górnym rogu.

- Wyszukaj nazwę aplikacji. W tym laboratorium kodu wyszukaj „Widżety ekranu głównego”.

- Po dodaniu widżetu ekranu głównego powinien on wyświetlać prosty tekst z godziną.
Tworzenie podstawowego widżetu na Androida
- Aby dodać widżet ekranu głównego w Androidzie, otwórz plik kompilacji projektu w Android Studio. Ten plik znajdziesz w folderze android/build.gradle. Możesz też kliknąć prawym przyciskiem myszy folder android w VSCode i wybrać Open in Android Studio (Otwórz w Android Studio).
- Po utworzeniu projektu znajdź katalog aplikacji w lewym górnym rogu. Dodaj nowy widżet ekranu głównego do tego katalogu. Kliknij prawym przyciskiem myszy katalog i wybierz New –> Widget –> App Widget (Nowy –> Widżet –> Widżet aplikacji).

- Android Studio wyświetli nowy formularz. Dodaj podstawowe informacje o widżecie ekranu głównego, w tym nazwę klasy, umiejscowienie, rozmiar i język źródłowy.
W tym samouczku ustaw te wartości:
- Nazwa klasy na NewsWidget.
- Minimalna szerokość (komórki) na 3.
- Minimalna wysokość (komórki) na 3.
Sprawdź przykładowy kod
Po przesłaniu formularza Android Studio utworzy i zaktualizuje kilka plików. Zmiany istotne w przypadku tego laboratorium kodu znajdziesz w tabeli poniżej.
Działanie | Plik docelowy | Zmień |
Aktualizuj |
| Dodaje nowy odbiornik, który rejestruje widżet NewsWidget. |
Utwórz |
| Definiuje interfejs widżetu ekranu głównego. |
Utwórz |
| Określa konfigurację widżetu ekranu głównego. W tym pliku możesz dostosować wymiary lub nazwę widżetu. |
Utwórz |
| Zawiera kod w języku Kotlin, który dodaje funkcje do widżetu ekranu głównego. |
Więcej informacji o tych plikach znajdziesz w tym ćwiczeniu z programowania.
Debugowanie i testowanie przykładowego widżetu
Teraz uruchom aplikację i wyświetl widżet ekranu głównego. Po utworzeniu aplikacji przejdź do ekranu wyboru aplikacji na urządzeniu z Androidem i przytrzymaj ikonę tego projektu Flutter. W wyskakującym menu wybierz Widżety.

Na urządzeniu z Androidem lub emulatorze wyświetli się domyślny widżet ekranu głównego na Androida.
4. Przesyłanie danych z aplikacji Flutter do widżetu na ekranie głównym
Możesz dostosować utworzony przez siebie podstawowy widżet ekranu głównego. Zaktualizuj widżet na ekranie głównym, aby wyświetlać nagłówek i podsumowanie artykułu. Poniższy zrzut ekranu przedstawia przykładowy widżet ekranu głównego z nagłówkiem i podsumowaniem.

Aby przekazywać dane między aplikacją a widżetem ekranu głównego, musisz napisać kod w języku Dart i kod natywny. W tej sekcji proces ten jest podzielony na 3 części:
- pisanie w aplikacji Flutter kodu w języku Dart, z którego mogą korzystać zarówno Android, jak i iOS;
- Dodawanie natywnych funkcji iOS
- Dodawanie natywnych funkcji Androida
Korzystanie z grup aplikacji na iOS
Aby udostępniać dane między aplikacją nadrzędną na iOS a rozszerzeniem widżetu, oba elementy docelowe muszą należeć do tej samej grupy aplikacji. Więcej informacji o grupach aplikacji znajdziesz w dokumentacji Apple.
Zaktualizuj identyfikator pakietu:
W Xcode otwórz ustawienia projektu. Na karcie Podpisywanie i możliwości sprawdź, czy identyfikator zespołu i identyfikator pakietu są ustawione.
Dodaj grupę aplikacji do obu elementów docelowych Runner i NewsWidgetExtension w Xcode:
Kliknij + Możliwość –> Grupy aplikacji i dodaj nową grupę aplikacji. Powtórz te czynności w przypadku elementu docelowego Runner (aplikacja nadrzędna) i elementu docelowego widżetu.

Dodawanie kodu Dart
Aplikacje na iOS i Androida mogą udostępniać dane aplikacji Flutter na kilka różnych sposobów.Aby komunikować się z tymi aplikacjami, korzystaj z lokalnego key/value urządzenia. W iOS ten magazyn nazywa się UserDefaults, a w Androidzie – SharedPreferences. Pakiet home_widget obejmuje te interfejsy API, aby uprościć zapisywanie danych na obu platformach i umożliwić widżetom na ekranie głównym pobieranie zaktualizowanych danych.

Dane nagłówka i opisu pochodzą z pliku news_data.dart. Ten plik zawiera przykładowe dane i klasę danych 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,
});
}
Aktualizowanie wartości nagłówka i tekstu reklamy
Aby dodać funkcję aktualizowania widżetu ekranu głównego z aplikacji Flutter, otwórz plik lib/home_screen.dart. Zastąp zawartość pliku poniższym kodem. Następnie zastąp symbol <YOUR APP GROUP> identyfikatorem grupy aplikacji.
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);
},
),
);
},
);
},
));
}
}
Funkcja updateHeadline zapisuje pary klucz/wartość w pamięci lokalnej urządzenia. Klucz headline_title zawiera wartość newHeadline.title. Klucz headline_description zawiera wartość newHeadline.description. Funkcja powiadamia też platformę natywną, że można pobrać i wyrenderować nowe dane dla widżetów ekranu głównego.
Modyfikowanie elementu floatingActionButton
Wywołaj funkcję updateHeadline po naciśnięciu przycisku floatingActionButton, jak pokazano poniżej:
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'),
),
...
Dzięki tej zmianie, gdy użytkownik naciśnie przycisk Zaktualizuj nagłówek na stronie artykułu, szczegóły widżetu na ekranie głównym zostaną zaktualizowane.
Aktualizowanie kodu iOS w celu wyświetlania danych artykułu
Aby zaktualizować widżet ekranu głównego na iOS, użyj Xcode.
Otwórz plik NewsWidgets.swift w Xcode:
Skonfiguruj TimelineEntry.
Zastąp strukturę SimpleEntry tym kodem:
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
}
Ta struktura NewsArticleEntry określa dane przychodzące, które mają być przekazywane do widżetu ekranu głównego po jego zaktualizowaniu. Typ TimelineEntry wymaga parametru daty.Więcej informacji o protokole TimelineEntry znajdziesz w dokumentacji Apple dotyczącej TimelineEntry.
Edytuj View wyświetlający treść
Zmodyfikuj widżet na ekranie głównym, aby zamiast daty wyświetlał nagłówek i opis artykułu. Aby wyświetlić tekst w SwiftUI, użyj widoku Text. Aby ułożyć widoki jeden na drugim w SwiftUI, użyj widoku VStack.
Zastąp wygenerowany widok NewsWidgetEntryView tym kodem:
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)
}
}
}
Edytowanie dostawcy, aby określić, kiedy i jak widżet na ekranie głównym ma się aktualizować
Zastąp istniejący kod Provider tym kodem: Następnie zastąp identyfikator grupy aplikacji ciągiem <YOUR APP GROUP>:
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)
}
}
}
Element Provider w poprzednim kodzie jest zgodny z elementem TimelineProvider. Provider ma 3 różne metody:
- Metoda
placeholdergeneruje wpis zastępczy, gdy użytkownik po raz pierwszy wyświetla podgląd widżetu ekranu głównego.

- Metoda
getSnapshotodczytuje dane z ustawień domyślnych użytkownika i generuje wpis dla bieżącego czasu. - Metoda
getTimelinezwraca wpisy na osi czasu. Jest to przydatne, gdy masz przewidywalne momenty, w których aktualizujesz treści. W tym laboratorium kodu używamy funkcji getSnapshot do pobierania bieżącego stanu. Metoda.atEndinformuje widżet ekranu głównego, że po upływie bieżącego czasu ma odświeżyć dane.
Zakomentuj NewsWidgets_Previews
Korzystanie z podglądów wykracza poza zakres tego laboratorium. Więcej informacji o podglądzie widżetów ekranu głównego SwiftUI znajdziesz w dokumentacji Apple dotyczącej debugowania widżetów.
Zapisz wszystkie pliki i ponownie uruchom aplikację i widżet.
Ponownie uruchom elementy docelowe, aby sprawdzić, czy aplikacja i widżet ekranu głównego działają.
- Aby uruchomić docelową aplikację, wybierz schemat aplikacji w Xcode.
- Wybierz schemat rozszerzenia w Xcode, aby uruchomić cel rozszerzenia.
- Otwórz stronę artykułu w aplikacji.
- Kliknij przycisk, aby zaktualizować nagłówek. Widżet na ekranie głównym powinien też zaktualizować nagłówek.
Aktualizowanie kodu Androida
Dodaj kod XML widżetu ekranu głównego.
W Android Studio zaktualizuj pliki wygenerowane w poprzednim kroku.Otwórz plik res/layout/news_widget.xml. Określa strukturę i układ widżetu ekranu głównego. W prawym górnym rogu kliknij Code (Kod) i zastąp zawartość pliku tym kodem:
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>
Ten plik XML definiuje 2 widoki tekstu: jeden dla nagłówka artykułu, a drugi dla opisu artykułu. Te widoki tekstowe określają też styl. Będziesz wracać do tego pliku w trakcie tego ćwiczenia.
Aktualizacja funkcji widżetu NewsWidget
Otwórz plik kodu źródłowego NewsWidget.kt w języku Kotlin. Ten plik zawiera wygenerowaną klasę o nazwie NewsWidget, która rozszerza klasę AppWidgetProvider.
Klasa NewsWidget zawiera 3 metody z klasy nadrzędnej. Zmodyfikujesz metodę onUpdate. Android wywołuje tę metodę w przypadku widżetów w stałych odstępach czasu.
Zastąp zawartość pliku NewsWidget.kt tym kodem:
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)
}
}
}
Teraz, gdy wywoływana jest funkcja onUpdate, Android pobiera najnowsze wartości z pamięci lokalnej za pomocą metody the widgetData.getString(), a następnie wywołuje funkcję setTextViewText, aby zmienić tekst wyświetlany w widżecie ekranu głównego.
Testowanie aktualizacji
Przetestuj aplikację, aby sprawdzić, czy widżety na ekranie głównym są aktualizowane o nowe dane. Aby zaktualizować dane, użyj opcji Zaktualizuj ekran główny FloatingActionButton na stronach artykułów. Widżet na ekranie głównym powinien się zaktualizować i wyświetlić tytuł artykułu.

5. Używanie niestandardowych czcionek aplikacji Flutter w widżecie ekranu głównego na iOS
Do tej pory skonfigurowano widżet ekranu głównego tak, aby odczytywał dane dostarczane przez aplikację Flutter. Aplikacja Flutter zawiera niestandardową czcionkę, której możesz użyć w widżecie ekranu głównego. Możesz użyć niestandardowej czcionki w widżecie ekranu głównego iOS. Używanie niestandardowych czcionek w widżetach ekranu głównego nie jest dostępne na urządzeniach z Androidem.
Aktualizacja kodu iOS
Flutter przechowuje zasoby w głównym pakiecie aplikacji na iOS. Dostęp do zasobów w tym pakiecie możesz uzyskać z kodu widżetu na ekranie głównym.
W strukturze NewsWidgetsEntryView w pliku NewsWidgets.swift wprowadź te zmiany:
Utwórz funkcję pomocniczą, która pobiera ścieżkę do katalogu komponentów Fluttera:
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
}
...
}
Zarejestruj czcionkę, używając adresu URL do pliku czcionki niestandardowej.
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)
}
...
}
Zaktualizuj widok tekstu nagłówka, aby używać niestandardowej czcionki.
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)
}
}
...
}
Po uruchomieniu widżetu ekranu głównego nagłówek będzie wyświetlany w niestandardowej czcionce, jak na tym obrazie:

6. Renderowanie widżetów Fluttera jako obrazu
W tej sekcji wyświetlisz wykres z aplikacji Flutter jako widżet na ekranie głównym.
Ten widżet stanowi większe wyzwanie niż tekst wyświetlany na ekranie głównym. Wyświetlenie wykresu Fluttera jako obrazu jest znacznie łatwiejsze niż próba odtworzenia go za pomocą natywnych komponentów interfejsu.
Napisz kod widżetu ekranu głównego, aby renderować wykres Fluttera jako plik PNG. Widżet na ekranie głównym może wyświetlać to zdjęcie.
Pisanie kodu w Dart
Po stronie Dart dodaj metodę renderFlutterWidget z pakietu home_widget. Ta metoda przyjmuje widżet, nazwę pliku i klucz. Zwraca obraz widżetu Fluttera i zapisuje go w kontenerze współdzielonym. Podaj nazwę obrazu w kodzie i upewnij się, że widżet na ekranie głównym ma dostęp do kontenera. key zapisuje pełną ścieżkę pliku jako ciąg znaków w pamięci lokalnej urządzenia. Dzięki temu widżet ekranu głównego będzie mógł znaleźć plik, jeśli nazwa zmieni się w kodzie Dart.
W tym samouczku klasa LineChart w pliku lib/article_screen.dart reprezentuje wykres. Jego metoda build zwraca obiekt CustomPainter, który rysuje ten wykres na ekranie.
Aby wdrożyć tę funkcję, otwórz plik lib/article_screen.dart. Zaimportuj pakiet home_widget. Następnie zastąp kod w klasie _ArticleScreenState tym kodem:
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!),
],
),
);
}
}
W tym przykładzie wprowadzamy 3 zmiany w klasie _ArticleScreenState.
Tworzy obiekt GlobalKey
Funkcja GlobalKey pobiera kontekst konkretnego widżetu, który jest potrzebny do określenia jego rozmiaru .
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
// New: add this GlobalKey
final _globalKey = GlobalKey();
...
}
Dodaje imagePath
Właściwość imagePath przechowuje lokalizację obrazu, w której renderowany jest widżet Fluttera.
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
...
// New: add this imagePath
String? imagePath;
...
}
Dodaje klucz do widżetu, aby go wyrenderować
_globalKey zawiera widżet Fluttera, który jest renderowany na obrazie. W tym przypadku widżetem Flutter jest element Center zawierający element LineChart.
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
...
Center(
// New: Add this key
key: _globalKey,
child: const LineChart(),
),
...
}
- Zapisuje widżet jako obraz
Metoda renderFlutterWidget jest wywoływana, gdy użytkownik kliknie floatingActionButton. Metoda zapisuje wynikowy plik PNG jako „screenshot” w katalogu kontenera udostępnionego. Metoda ta zapisuje też pełną ścieżkę do obrazu jako klucz nazwy pliku w pamięci urządzenia.
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);
},
...
}
Aktualizacja kodu iOS
W przypadku iOS zaktualizuj kod, aby pobrać ścieżkę pliku z pamięci i wyświetlić plik jako obraz za pomocą SwiftUI.
Otwórz plik NewsWidgets.swift, aby wprowadzić te zmiany:
Dodaj filename i displaySize do NewsArticleEntry struct
Właściwość filename zawiera ciąg znaków reprezentujący ścieżkę do pliku obrazu. Właściwość displaySize zawiera rozmiar widżetu ekranu głównego na urządzeniu użytkownika. Rozmiar widżetu na ekranie głównym pochodzi z context.
ios/NewsWidgets/NewsWidgets.swift
struct NewsArticleEntry: TimelineEntry {
...
// New: add the filename and displaySize.
let filename: String
let displaySize: CGSize
}
Zaktualizuj placeholder funkcję
Dodaj obiekty zastępcze filename i 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)
}
Pobierz nazwę pliku z userDefaults w getSnapshot.
Gdy widżet ekranu głównego zostanie zaktualizowany, zmienna filename zostanie ustawiona na wartość filename w pamięci userDefaults.
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"
...
)
Utwórz element ChartImage wyświetlający obraz z określonej ścieżki
ChartImage View tworzy obraz na podstawie zawartości pliku wygenerowanego po stronie Dart. Ustaw rozmiar na 50% ramki.
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())
}
...
}
Użyj elementu ChartImage w treści elementu NewsWidgetsEntryView.
Dodaj widok ChartImage do treści NewsWidgetsEntryView, aby wyświetlać ChartImage w widżecie ekranu głównego.
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
}
Testowanie zmian
Aby przetestować zmiany, ponownie uruchom w Xcode zarówno aplikację Fluttera (Runner), jak i rozszerzenie. Aby zobaczyć obraz, otwórz jedną ze stron artykułów w aplikacji i naciśnij przycisk aktualizacji widżetu ekranu głównego.

Aktualizowanie kodu Androida
Kod na Androida działa tak samo jak kod na iOS.
- Otwórz plik
android/app/res/layout/news_widget.xml. Zawiera elementy interfejsu widżetu ekranu głównego. Zastąp jego zawartość tym kodem:
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>
Ten nowy kod dodaje obraz do widżetu ekranu głównego, który (na razie) wyświetla ogólną ikonę gwiazdy. Zastąp ikonę gwiazdki obrazem zapisanym w kodzie Dart.
- Otwórz plik
NewsWidget.kt. Zastąp jego zawartość tym kodem:
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)
}
}
}
Ten kod w języku Dart zapisuje zrzut ekranu w pamięci lokalnej za pomocą klucza filename. Pobiera też pełną ścieżkę obrazu i tworzy z niej obiekt File. Jeśli obraz istnieje, kod Dart zastąpi go w widżecie na ekranie głównym.
- Załaduj ponownie aplikację i przejdź do ekranu artykułu. Kliknij Zaktualizuj ekran główny. W widżecie na ekranie głównym wyświetli się wykres.
7. Następne kroki
Gratulacje!
Gratulacje! Udało Ci się utworzyć widżety ekranu głównego dla aplikacji Flutter na iOS i Androida.
Linkowanie do treści w aplikacji Flutter
W zależności od tego, gdzie użytkownik kliknie, możesz chcieć skierować go na konkretną stronę w aplikacji. Na przykład w aplikacji z wiadomościami z tego laboratorium możesz chcieć, aby użytkownik zobaczył artykuł z wiadomościami odpowiadający wyświetlonemu nagłówkowi.
Ta funkcja wykracza poza zakres tego laboratorium. Przykłady użycia strumienia udostępnianego przez pakiet home_widget znajdziesz w artykule na temat identyfikowania uruchomień aplikacji z widżetów ekranu głównego i wysyłania wiadomości z widżetu ekranu głównego za pomocą adresu URL. Więcej informacji znajdziesz w dokumentacji precyzyjnych linków na stronie docs.flutter.dev.
Aktualizowanie widżetu w tle
W tym laboratorium kodowania aktualizację widżetu na ekranie głównym wywoływaliśmy za pomocą przycisku. W przypadku testowania jest to uzasadnione, ale w kodzie produkcyjnym możesz chcieć, aby aplikacja aktualizowała widżet ekranu głównego w tle. Za pomocą wtyczki workmanager możesz tworzyć zadania w tle, które będą aktualizować zasoby potrzebne widżetowi ekranu głównego. Więcej informacji znajdziesz w sekcji Background update w pakiecie home_widget.
W przypadku iOS widżet na ekranie głównym może też wysyłać żądanie sieciowe, aby zaktualizować interfejs. Aby kontrolować warunki lub częstotliwość tego żądania, użyj osi czasu. Więcej informacji o korzystaniu z osi czasu znajdziesz w dokumentacji Apple „Keeping a widget up to date” .