1. Wprowadzenie
Co to są widżety?
W przypadku programistów Flutter powszechna definicja widżetu odnosi się do komponentów interfejsu utworzonych przy użyciu platformy Flutter. W kontekście tego ćwiczenia w Codelabs widżet to miniaturowa wersja aplikacji, która udostępnia informacje o aplikacji bez jej otwierania. Na Androidzie widżety znajdują się na ekranie głównym. W iOS można je dodawać na ekranie głównym, ekranie blokady lub w widoku Dzisiaj.
Jak złożony może być widżet?
Większość widżetów na ekranie głównym jest prosta. Mogą zawierać podstawowy tekst, prostą grafikę, a w Androidzie – podstawowe elementy sterujące. Zarówno Android, jak i iOS, ograniczają liczbę komponentów i funkcji interfejsu, z których możesz korzystać.
Tworzenie interfejsu widżetów
Z powodu tych ograniczeń interfejsu Flutter nie umożliwia bezpośredniego rysowania interfejsu widżetu na ekranie głównym. Zamiast tego możesz dodać do swojej aplikacji Flutter widżety utworzone za pomocą platform takich jak Jetpack Compose czy SwiftUI. W tym ćwiczeniu w Codelabs znajdziesz przykłady udostępniania zasobów między aplikacją a widżetami, aby uniknąć przepisywania złożonego interfejsu.
Co utworzysz
W ramach tego ćwiczenia w programie utworzysz widżety ekranu głównego na Androida i iOS w prostej aplikacji Flutter, korzystając z pakietu home_Widget, który umożliwi użytkownikom czytanie artykułów. Twoje widżety będą:
- Pokazuj dane z aplikacji Flutter.
- Wyświetlaj tekst, korzystając z zasobów czcionek udostępnionych z aplikacji Flutter.
- Wyświetla obraz wyrenderowanego widżetu Flutter.
Ta aplikacja Flutter ma 2 ekrany (czyli trasy):
- Pierwsza zawiera listę artykułów informacyjnych z nagłówkami i opisami.
- Drugi zawiera pełny artykuł z wykresem utworzonym za pomocą funkcji
CustomPaint
.
.
Czego się nauczysz
- Jak tworzyć widżety ekranu głównego na urządzeniach z iOS i Androidem
- Jak używać pakietu home_Widget do udostępniania danych między widżetem ekranu głównego a aplikacją Flutter.
- Jak zmniejszyć ilość kodu potrzebną do przepisywania.
- Jak zaktualizować widżet ekranu głównego w aplikacji Flutter.
2. Konfigurowanie środowiska programistycznego
W przypadku obu platform potrzebujesz pakietu SDK Flutter i IDE. Do pracy z Flutter możesz używać preferowanego IDE. Może to być Visual Studio Code z rozszerzeniami Dart Code i Flutter albo Android Studio lub IntelliJ z zainstalowanymi wtyczkami Flutter i Dart.
Aby utworzyć widżet ekranu głównego iOS:
- Możesz uruchomić te ćwiczenia w Codelabs na fizycznym urządzeniu z iOS lub w symulatorze iOS.
- Musisz skonfigurować system macOS przy użyciu Xcode IDE. Spowoduje to zainstalowanie kompilatora niezbędnego do utworzenia wersji aplikacji na iOS.
Aby utworzyć widżet ekranu głównego na urządzeniu z Androidem:
- Możesz uruchomić to ćwiczenia w Codelabs na fizycznym urządzeniu z Androidem lub w emulatorze Androida.
- Musisz skonfigurować system programistyczny pod kątem Android Studio. Spowoduje to zainstalowanie kompilatora niezbędnego do utworzenia wersji aplikacji na Androida.
Pobierz kod startowy
Pobierz początkową wersję projektu z GitHuba
Przy użyciu wiersza poleceń skopiuj repozytorium GitHub do katalogu flutter-codelabs:
$ git clone https://github.com/flutter/codelabs.git flutter-codelabs
Po sklonowaniu repozytorium znajdziesz kod ćwiczenia z programowania w katalogu flutter-codelabs/homescreen_codelab. Ten katalog zawiera kod ukończonego projektu dla każdego kroku ćwiczenia w Codelabs.
Otwórz aplikację startową
Otwórz katalog flutter-codelabs/homescreen_codelab/step_03
w preferowanym 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 do ekranu głównego
Najpierw dodaj widżet do ekranu głównego za pomocą narzędzi platformy natywnej.
Tworzenie podstawowego widżetu na ekranie głównym iOS
Dodawanie rozszerzenia do aplikacji Flutter na iOS jest podobne do dodawania rozszerzenia do aplikacji SwiftUI lub UIKit:
- Uruchom
open ios/Runner.xcworkspace
w oknie terminala z katalogu projektu Flutter. Możesz też kliknąć prawym przyciskiem myszy folder ios w VSCode i wybrać Otwórz w Xcode. Otworzy się domyślny obszar roboczy Xcode w projekcie Flutter. - W menu wybierz Plik → Nowy → Cel. Spowoduje to dodanie nowego miejsca docelowego do projektu.
- Pojawi się lista szablonów. Wybierz Rozszerzenie widżetu.
- Wpisz „NewsWidgets” w polu Product Name (Nazwa produktu) dla tego widżetu. Odznacz pola wyboru Uwzględnij aktywność na żywo i Uwzględnij intencję konfiguracji.
Sprawdzanie przykładowego kodu
Gdy dodasz nowe środowisko docelowe, Xcode wygeneruje przykładowy kod na podstawie wybranego przez Ciebie szablonu. Więcej informacji o wygenerowanym kodzie i narzędziu WidgetKit znajdziesz w dokumentacji rozszerzeń aplikacji Apple .
Debugowanie i testowanie przykładowego widżetu
- Najpierw zaktualizuj konfigurację aplikacji Flutter. Musisz to zrobić podczas dodawania nowych pakietów w aplikacji Flutter i planujesz uruchomienie miejsca docelowego w projekcie z Xcode. Aby zaktualizować konfigurację aplikacji, uruchom to polecenie w katalogu aplikacji Flutter:
$ flutter build ios --config-only
- Kliknij Uruchom, aby wyświetlić listę celów. Wybierz nowo utworzony cel widżetu – NewsWidgets i kliknij Uruchom. Po zmianie kodu widżetu iOS uruchom miejsce docelowe widżetu z Xcode.
- Na ekranie symulatora lub urządzenia powinien być widoczny podstawowy widżet ekranu głównego. Jeśli go nie widzisz, możesz go dodać do ekranu. Kliknij i przytrzymaj ekran główny, a następnie kliknij + w lewym górnym rogu.
- Wyszukaj nazwę aplikacji. Na potrzeby tego ćwiczenia w programie wyszukaj „Widżety na ekranie głównym”
- Po dodaniu widżetu do ekranu głównego powinien się wyświetlić zwykły tekst z godziną.
Tworzenie podstawowego widżetu na Androida
- Aby dodać widżet na ekranie głównym w Androidzie, otwórz plik kompilacji projektu w Android Studio. Znajdziesz go pod adresem android/build.gradle. Możesz też kliknąć prawym przyciskiem myszy folder android w VSCode i wybrać Otwórz w Android Studio.
- Po skompilowaniu projektu znajdź katalog aplikacji w lewym górnym rogu. Dodaj do tego katalogu nowy widżet ekranu głównego. Kliknij katalog prawym przyciskiem myszy i wybierz opcję New (Nowy) -> Widżet -> Widżet aplikacji.
- Android Studio wyświetli nowy formularz. Dodaj podstawowe informacje o widżecie ekranu głównego, w tym nazwę zajęć, miejsce docelowe, rozmiar i język źródłowy.
W tym ćwiczeniu z programowania ustaw te wartości:
- pole Nazwa klasy w widżecie NewsWidget,
- Menu Minimalna szerokość (komórka) do 3
- Menu Minimalna wysokość (komórki) do 3
Sprawdzanie przykładowego kodu
Gdy prześlesz formularz, Android Studio utworzy i zaktualizuje kilka plików. Zmiany, które pojawią się w tym ćwiczeniach z programowania, znajdziesz w tabeli poniżej
Działanie | Plik docelowy | Zmień |
Aktualizuj |
| Dodaje nowego odbiorcę, 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 Kotlin, który dodaje funkcje do widżetu na ekranie głównym. |
Więcej informacji o tych plikach znajdziesz w tym ćwiczeniu z programowania.
Debugowanie i testowanie przykładowego widżetu
Teraz uruchom aplikację, aby zobaczyć widżet ekranu głównego. Po skompilowaniu aplikacji przejdź do ekranu wyboru aplikacji na urządzeniu z Androidem i przytrzymaj ikonę tego projektu Flutter. Wybierz Widżety z menu.
Urządzenie z Androidem lub emulator wyświetla domyślny widżet ekranu głównego dla Androida.
4. Wysyłaj dane 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 ekranu głównego tak, aby wyświetlał nagłówek i podsumowanie artykułu z wiadomościami. 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 Dart oraz kod natywny. W tej sekcji proces dzieli się na 3 części:
- Pisanie kodu DART w aplikacji Flutter, którego można używać zarówno na Androida, jak i iOS
- Dodawanie natywnych funkcji systemu iOS
- Dodawanie natywnych funkcji Androida
Korzystanie z grup aplikacji na iOS
Aby można było 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 dotyczącej grup aplikacji.
Zaktualizuj identyfikator pakietu:
W Xcode otwórz ustawienia celu. W sekcji Capabilities (Możliwości), sprawdź, czy skonfigurowano identyfikator zespołu i pakietu.
Dodaj grupę aplikacji zarówno do środowiska docelowego Runner, jak i do celu NewsWidgetExtension w Xcode:
Wybierz + Możliwość -> grupy aplikacji i dodaj nową. Powtórz te czynności w przypadku środowiska docelowego Runner (aplikacji nadrzędnej) oraz docelowego widżetu.
Dodawanie kodu Dart
Zarówno aplikacje na iOS, jak i na Androida, mogą udostępniać dane aplikacji Flutter na kilka różnych sposobów.Aby komunikować się z tymi aplikacjami, użyj lokalnego sklepu key/value
na urządzeniu. iOS nazywa ten sklep UserDefaults
, a na Androidzie – SharedPreferences
. home_Widget zawiera wszystkie te interfejsy API, aby uprościć zapisywanie danych na każdej z tych platform i umożliwić widżetom ekranu głównego pobieranie zaktualizowanych danych.
Nagłówek i tekst reklamy 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, przejdź do pliku lib/home_screen.dart
. Zastąp zawartość pliku poniższym kodem. Następnie zastąp <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ż natywną platformę, że można pobrać i renderować nowe dane dla widżetów na ekranie głównym.
Modyfikowanie obiektu FloatActionButton
Wywołaj funkcję updateHeadline
po naciśnięciu klawisza floatingActionButton
w następujący sposób:
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 z artykułem, dane widżetu na ekranie głównym zostaną zaktualizowane.
Zaktualizuj kod iOS, aby wyświetlić dane 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
definiuje przychodzące dane, które są przekazywane do widżetu na ekranie głównym po aktualizacji. Typ TimelineEntry
wymaga parametru daty.Aby dowiedzieć się więcej o protokole TimelineEntry
, zapoznaj się z dokumentacją firmy Apple BillingEntry.
Edytuj pole View
wyświetlające treść
Zmodyfikuj widżet ekranu głównego tak, aby zamiast daty wyświetlał się nagłówek i opis artykułu z wiadomościami. Aby wyświetlić tekst w interfejsie SwiftUI, użyj widoku Text
. Aby nakładać widoki na siebie 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)
}
}
}
Edytuj dostawcę, aby informować widżet na ekranie głównym o czasie i sposobie aktualizacji
Zastąp obecny kod Provider
tym kodem. Następnie zastąp identyfikator grupy aplikacji ciągiem <TWOJA GRUPA APLIKACJI>:
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 TimelineProvider
. Provider
udostępnia 3 różne metody:
- Metoda
placeholder
generuje wpis zastępczy, gdy użytkownik po raz pierwszy wyświetli podgląd widżetu na ekranie głównym.
- Metoda
getSnapshot
odczytuje dane z wartości domyślnych użytkownika i generuje wpis z bieżącą godziną. - Metoda
getTimeline
zwraca wpisy osi czasu. Jest to przydatne, gdy w odpowiednim momencie masz zaplanowaną aktualizację. To ćwiczenie w Codelabs wykorzystuje funkcję getSnapshot do pobrania bieżącego stanu. Metoda.atEnd
informuje widżet ekranu głównego, że ma odświeżyć dane po upływie bieżącego czasu.
Skomentuj: NewsWidgets_Previews
W tym ćwiczeniu z programowania korzystanie z podglądów nie jest możliwe. Więcej informacji o wyświetlaniu podglądu widżetów na ekranie głównym SwiftUI znajdziesz w dokumentacji Apple dotyczącej widżetów debugowania.
Zapisz wszystkie pliki i ponownie uruchom aplikację i cel widżetu.
Ponownie uruchom elementy docelowe, aby sprawdzić, czy aplikacja i widżet na ekranie głównym działają.
- Wybierz schemat aplikacji w Xcode, aby uruchomić miejsce docelowe aplikacji.
- Wybierz schemat rozszerzenia w Xcode, aby uruchomić cel rozszerzenia.
- Otwórz stronę artykułu w aplikacji.
- Kliknij ten przycisk, aby zaktualizować nagłówek. Widżet ekranu głównego 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
. Definiuje strukturę i układ widżetu ekranu głównego. W prawym górnym rogu kliknij Kod i zastąp zawartość tego 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 określa dwa widoki tekstu – jeden dla nagłówka artykułu, a drugi dla opisu artykułu. Te widoki tekstu określają również styl. Wrócisz do tego pliku w trakcie tego ćwiczenia z programowania.
Aktualizowanie funkcji NewsWidget
Otwórz plik z kodem źródłowym Kotlin NewsWidget.kt
. Ten plik zawiera wygenerowaną klasę o nazwie NewsWidget
, która rozszerza klasę AppWidgetProvider
.
Klasa NewsWidget
zawiera 3 metody ze swojej 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 po wywołaniu funkcji onUpdate
Android pobiera najnowsze wartości z pamięci lokalnej za pomocą metody the widgetData.getString()
, a następnie wywołuje setTextViewText
, aby zmienić tekst wyświetlany w widżecie na ekranie głównym.
Testowanie aktualizacji
Przetestuj aplikację, aby upewnić się, że widżety na ekranie głównym są aktualizowane na podstawie nowych danych. Aby zaktualizować dane, kliknij Zaktualizuj ekran główny FloatingActionButton
na stronach artykułów. W widżecie ekranu głównego powinien pojawić się tytuł artykułu.
5. Używanie niestandardowych czcionek aplikacji Flutter w widżecie ekranu głównego iOS
Jak dotąd widżet ekranu głównego został skonfigurowany 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 na ekranie głównym iOS. Używanie niestandardowych czcionek w widżetach na ekranie głównym nie jest dostępne na urządzeniach z Androidem.
Zaktualizuj kod iOS
Flutter przechowuje swoje zasoby w mainBundle aplikacji na iOS. Dostęp do zasobów w tym pakiecie możesz uzyskać za pomocą kodu widżetu na ekranie głównym.
W strukturze NewsWidgetsEntryView w pliku NewsWidgets.swift wprowadź te zmiany
Utwórz funkcję pomocniczą, aby uzyskać ścieżkę do katalogu zasobów 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
}
...
}
Zarejestruj czcionkę, podając adres URL pliku czcionek niestandardowych.
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ć czcionki niestandardowej.
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)
}
}
...
}
Gdy uruchomisz widżet ekranu głównego, używa on teraz czcionki niestandardowej w nagłówku pokazanym na tym obrazie:
6. Renderowanie widżetów Flutter jako obrazów
W tej sekcji zobaczysz 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. O wiele łatwiej jest wyświetlić wykres Flutter jako obraz, niż odtworzyć go za pomocą natywnych komponentów interfejsu.
Zakoduj widżet ekranu głównego, aby renderował wykres Flutter jako plik PNG. Widżet ekranu głównego może wyświetlać ten obraz.
Pisanie kodu Dart
Po stronie Dart dodaj metodę renderFlutterWidget
z pakietu home_Widget. Ta metoda wymaga widżetu, nazwy pliku i klucza. Zwraca obraz widżetu Flutter i zapisuje go w udostępnionym kontenerze. Wpisz w kodzie nazwę obrazu 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 może znaleźć plik, jeśli nazwa zmieni się w kodzie Dart.
W tym ćwiczeniu w Codelabs klasa LineChart
w pliku lib/article_screen.dart
reprezentuje wykres. Jej metoda kompilacji zwraca obiekt CustomPainter, który umieszcza ten wykres na ekranie.
Aby zaimplementować 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 wprowadzono 3 zmiany w klasie _ArticleScreenState
.
Tworzy klucz GlobalKey
GlobalKey
pobiera kontekst konkretnego widżetu, który jest potrzebny do pobrania rozmiaru tego widżetu .
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
// New: add this GlobalKey
final _globalKey = GlobalKey();
...
}
Dodaje ścieżkę imagePath
Właściwość imagePath
przechowuje lokalizację obrazu, w której jest renderowany widżet Flutter.
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
...
// New: add this imagePath
String? imagePath;
...
}
Dodaje klucz do widżetu, który ma być renderowany
_globalKey
zawiera widżet Flutter renderowany w obrazie. W tym przypadku widżet Flutter to centrum, które zawiera obiekt 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 „zrzut ekranu”. do katalogu udostępnionego kontenera. Ta metoda 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);
},
...
}
Aktualizowanie kodu iOS
W iOS zaktualizuj kod, aby uzyskać ścieżkę pliku z pamięci i wyświetl go jako obraz za pomocą SwiftUI.
Otwórz plik NewsWidgets.swift
, aby wprowadzić te zmiany:
Dodaj filename
i displaySize
do struktury NewsArticleEntry
Właściwość filename
zawiera ciąg znaków reprezentujący ścieżkę do pliku obrazu. Właściwość displaySize
przechowuje rozmiar widżetu ekranu głównego na urządzeniu użytkownika. Rozmiar widżetu ekranu głównego pochodzi z context
.
ios/NewsWidgets/NewsWidgets.swift
struct NewsArticleEntry: TimelineEntry {
...
// New: add the filename and displaySize.
let filename: String
let displaySize: CGSize
}
Zaktualizuj funkcję placeholder
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
Powoduje to ustawienie zmiennej filename
na wartość filename
w pamięci userDefaults
podczas aktualizowania widżetu ekranu głównego.
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 obiekt ChartImage, który wyświetla obraz ze ścieżki
Widok ChartImage
tworzy obraz na podstawie zawartości pliku wygenerowanego po stronie Dart. W tym miejscu ustawiasz rozmiar 50% klatki.
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 właściwości ChartImage w treści elementu NewsWidgetsEntryView
Dodaj widok ChartImage do treści elementu NewsWidgetsEntryView, aby wyświetlić obiekt 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, uruchom ponownie miejsce docelowe aplikacji Flutter (Runner) i cel rozszerzenia z Xcode. Jeśli chcesz zobaczyć obraz, otwórz w aplikacji jedną ze stron artykułu i naciśnij przycisk, aby zaktualizować widżet na ekranie głównym.
Aktualizowanie kodu Androida
Kod na Androidzie działa tak samo jak kod na iOS.
- Otwórz plik
android/app/res/layout/news_widget.xml
. Zawiera elementy interfejsu widżetu na ekranie głównym. 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 Ekran główny, który (na razie) wyświetla ogólną ikonę gwiazdki. Zastąp tę 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 Dart zapisuje zrzut ekranu w pamięci lokalnej za pomocą klawisza filename
. Pobiera również pełną ścieżkę obrazu i tworzy z niej obiekt File
. Jeśli obraz istnieje, kod Dart zastępuje obraz w widżecie na ekranie głównym nowym obrazem.
- Załaduj ponownie aplikację i przejdź do ekranu z artykułem. Kliknij Zaktualizuj ekran główny. Widżet ekranu głównego wyświetla wykres.
7. Następne kroki
Gratulacje!
Gratulacje, udało Ci się utworzyć widżety ekranu głównego dla aplikacji Flutter na iOS i Androida.
Linki do treści w aplikacji Flutter
Możesz kierować użytkowników na określoną stronę w aplikacji w zależności od miejsca kliknięcia. Na przykład w aplikacji z wiadomościami objętymi tym ćwiczeniem z programowania możesz chcieć, aby użytkownik zobaczył artykuł z wiadomościami w wyświetlonym nagłówku.
Ta funkcja nie wykracza poza zakres tego ćwiczenia z programowania. Oto przykłady wykorzystania strumienia zawartego w pakiecie home_Widget do identyfikowania uruchamiania aplikacji z widżetów na ekranie głównym i wysyłania wiadomości z widżetu na ekranie głównym 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 ćwiczeniu w programie udało Ci się aktywować aktualizację widżetu ekranu głównego za pomocą przycisku. Chociaż jest to uzasadnione z punktu widzenia testów, w kodzie produkcyjnym warto chcieć, by aplikacja aktualizowała widżet na ekranie głównym w tle. Za pomocą wtyczki Workmanager możesz tworzyć zadania w tle i aktualizować zasoby, których potrzebuje widżet na ekranie głównym. Więcej informacji znajdziesz w sekcji Aktualizacja w tle w pakiecie home_Widget.
W iOS widżet ekranu głównego może wysyłać żądania sieciowe do aktualizacji interfejsu użytkownika. Aby kontrolować warunki lub częstotliwość tego żądania, użyj osi czasu. Więcej informacji o korzystaniu z osi czasu znajdziesz w przewodniku Apple „Zapewnianie aktualizacji widżetu” dokumentacji. .