1. مقدمة
ما هي "التطبيقات المصغّرة"؟
بالنسبة إلى مطوّري برامج Flutter، يشير التعريف الشائع للأدوات إلى مكوّنات واجهة المستخدم التي تم إنشاؤها باستخدام إطار عمل Flutter. في سياق هذا الدرس التطبيقي حول الترميز، يشير التطبيق المصغّر إلى إصدار مصغّر من تطبيق يوفّر عرضًا لمعلومات التطبيق بدون فتح التطبيق. على أجهزة Android، تظهر التطبيقات المصغّرة على الشاشة الرئيسية. وعلى أجهزة iOS، يمكن إضافتها إلى الشاشة الرئيسية أو شاشة القفل أو عرض اليوم.
إلى أي مدى يمكن أن يكون التطبيق المصغّر معقّدًا؟
تتميز معظم أدوات الشاشة الرئيسية بالبساطة. وقد تحتوي على نص أساسي أو رسومات بسيطة أو عناصر تحكُّم أساسية على Android. يحدّ كل من نظامي التشغيل Android وiOS من مكونات وميزات واجهة المستخدم التي يمكنك استخدامها.
إنشاء واجهة مستخدم "التطبيقات المصغّرة"
بسبب قيود واجهة المستخدم هذه، لا يمكنك رسم واجهة المستخدم مباشرةً لتطبيق مصغّر على الشاشة الرئيسية باستخدام إطار عمل Flutter. بدلاً من ذلك، يمكنك إضافة تطبيقات مصغّرة تم إنشاؤها باستخدام أُطر عمل الأنظمة الأساسية، مثل Jetpack Compose أو SwiftUI، إلى تطبيق Flutter. يناقش هذا الدرس التطبيقي حول الترميز أمثلة لمشاركة الموارد بين تطبيقك والتطبيقات المصغّرة لتجنُّب إعادة كتابة واجهة المستخدم المعقّدة.
ما الذي ستنشئه
في هذا الدرس التطبيقي، ستعمل على إنشاء تطبيقات مصغّرة من "الشاشة الرئيسية" على كلّ من نظامَي التشغيل Android وiOS لتطبيق Flutter البسيط، وذلك باستخدام حزمة home_{@et التي تتيح للمستخدمين قراءة المقالات. ستنفّذ التطبيقات المصغّرة ما يلي:
- عرض البيانات من تطبيق Flutter
- عرض النص باستخدام مواد عرض الخطوط التي تمّت مشاركتها من تطبيق Flutter
- يمكنك عرض صورة لتطبيق Flutter المصغّر المعروض.
يتضمّن تطبيق Flutter هذا شاشتَين (أو مسارات):
- يعرض الأول قائمة بالمقالات الإخبارية مع عناوين وأوصاف.
- ويعرض العنصر الثاني المقالة الكاملة من خلال رسم بياني تم إنشاؤه باستخدام السمة
CustomPaint
.
.
المعلومات التي ستتعلّمها
- كيفية إنشاء تطبيقات مصغّرة للشاشة الرئيسية على أجهزة iOS وAndroid
- كيفية استخدام حزمة home_Widgetet لمشاركة البيانات بين تطبيق الشاشة الرئيسية وتطبيق Flutter
- كيفية تقليل مقدار التعليمات البرمجية التي تحتاج إلى إعادة كتابتها.
- كيفية تحديث تطبيق "الشاشة الرئيسية" المصغّر من تطبيق Flutter
2. إعداد بيئة التطوير
بالنسبة إلى كِلا النظامَين الأساسيَين، تحتاج إلى Flutter SDK وبيئة تطوير متكاملة (IDE). يمكنك استخدام بيئة تطوير البرامج (IDE) المفضّلة لديك للعمل مع Flutter. يمكن أن يكون هذا الرمز Visual Studio Code مع الإضافتَينDart Code وFlutter، أو "استوديو Android" أو IntelliJ مع تثبيت المكوّنَين الإضافيَين Flutter وDart.
لإنشاء تطبيق iOS المصغّر في الشاشة الرئيسية:
- يمكنك تشغيل هذا الدرس التطبيقي حول الترميز على جهاز iOS فعلي أو محاكي iOS.
- يجب ضبط نظام التشغيل macOS باستخدام Xcode IDE. يؤدي هذا إلى تثبيت برنامج التجميع اللازم لإنشاء إصدار iOS من تطبيقك.
لإنشاء تطبيق Android المصغّر في الشاشة الرئيسية:
- يمكنك تشغيل هذا الدرس التطبيقي حول الترميز على جهاز Android أو محاكي Android.
- يجب ضبط نظام التطوير باستخدام "استوديو Android". يؤدي هذا إلى تثبيت برنامج التجميع اللازم لإنشاء إصدار Android من تطبيقك.
الحصول على رمز إجراء التفعيل
تنزيل النسخة الأولية من مشروعك من GitHub
من سطر الأوامر، استنسِخ مستودع GitHub في دليل Flutter-codelabs:
$ git clone https://github.com/flutter/codelabs.git flutter-codelabs
بعد استنساخ المستودع، يمكنك العثور على رمز الدرس التطبيقي حول الترميز في الدليل flutter-codelabs/homescreen_codelab. يحتوي هذا الدليل على رمز مشروع مكتمل لكل خطوة في الدرس التطبيقي حول الترميز.
فتح تطبيق إجراء التفعيل
افتح الدليل flutter-codelabs/homescreen_codelab/step_03
في بيئة التطوير المتكاملة (IDE) التي تفضّلها.
تثبيت الحِزم
تمت إضافة جميع الحزم المطلوبة إلى ملف pubspec.yaml للمشروع. لاسترداد تبعيات المشروع، شغِّل الأمر التالي:
$ flutter pub get
3- إضافة تطبيق مصغّر أساسي إلى الشاشة الرئيسية
أولاً، عليك إضافة تطبيق الشاشة الرئيسية المصغّر باستخدام أدوات النظام الأساسي الأصلية.
إنشاء تطبيق مصغّر أساسي في الشاشة الرئيسية لنظام التشغيل iOS
إنّ إدراج إضافة تطبيق إلى تطبيق Flutter لنظام التشغيل iOS يشبه إدراج إضافة تطبيق إلى تطبيق SwiftUI أو UIKit:
- شغِّل
open ios/Runner.xcworkspace
في نافذة طرفية من دليل مشروع Flutter. يمكنك بدلاً من ذلك النقر بزر الماوس الأيمن على مجلد ios من VSCode واختيار فتح في Xcode. يؤدي ذلك إلى فتح مساحة عمل Xcode التلقائية في مشروع Flutter. - اختَر ملف ← جديد ← هدف من القائمة. يؤدي هذا إلى إضافة هدف جديد إلى المشروع.
- ستظهر قائمة بالنماذج. اختَر إضافة الأداة.
- اكتب "NewsWidgets" في مربّع اسم المنتج لهذه الأداة امسح كلاً من مربعي الاختيار تضمين النشاط المباشر وتضمين نية الإعداد.
فحص نموذج الرمز
عند إضافة هدف جديد، ينشئ Xcode نموذج رمز استنادًا إلى النموذج الذي اخترته. للمزيد من المعلومات عن الرمز الذي تم إنشاؤه وWidgetKit، يمكنك الاطّلاع على مستندات إضافة تطبيق Apple.
تصحيح الأخطاء واختبار نموذج التطبيق المصغّر
- عليك أولاً تعديل إعدادات تطبيق Flutter عليك إجراء ذلك عند إضافة حِزم جديدة في تطبيق Flutter والتخطيط لتنفيذ هدف في المشروع من Xcode. لتعديل إعدادات تطبيقك، شغِّل الأمر التالي في دليل تطبيق Flutter:
$ flutter build ios --config-only
- انقر على Runner لعرض قائمة بالأهداف. اختَر استهداف الأداة الذي أنشأته للتوّ، وهو NewsWidgets، وانقر على تشغيل. شغِّل هدف الأداة من Xcode عند تغيير رمز التطبيق المصغّر لنظام التشغيل iOS.
- من المفترض أن يعرض المحاكي أو شاشة الجهاز التطبيق المصغّر الأساسي للشاشة الرئيسية. وإذا لم يظهر لك، يمكنك إضافته إلى الشاشة. انقر مع الاستمرار على الشاشة الرئيسية، ثم انقر على + في أعلى يمين الشاشة.
- ابحث عن اسم التطبيق. ابحث عن "التطبيقات المصغّرة للشاشة الرئيسية" في هذا الدرس التطبيقي.
- بعد إضافة تطبيق الشاشة الرئيسية المصغّر، من المفترض أن يعرض نصًا بسيطًا يوضّح الوقت.
إنشاء تطبيق Android المصغّر الأساسي
- لإضافة تطبيق مصغّر إلى الشاشة الرئيسية في نظام Android، افتح ملف إصدار المشروع في "استوديو Android". يمكنك العثور على هذا الملف على الرابط android/build.gradle. يمكنك بدلاً من ذلك النقر بزر الماوس الأيمن على مجلد android من VSCode واختيار فتح في "استوديو Android".
- بعد إنشاء المشروع، حدِّد موقع دليل التطبيق في أعلى يمين الشاشة. أضِف تطبيق الشاشة الرئيسية الجديد إلى هذا الدليل. انقر بزر الماوس الأيمن على الدليل، واختر جديد -> الأداة -> أداة التطبيق:
- يعرض "استوديو Android" نموذجًا جديدًا. أضِف المعلومات الأساسية عن التطبيق المصغّر للشاشة الرئيسية، بما في ذلك اسم الفئة وموضعها وحجمها ولغة المصدر.
في هذا الدرس التطبيقي حول الترميز، اضبط القيم التالية:
- المربع اسم الفئة في NewsWidget
- القائمة المنسدلة الحد الأدنى للعرض (الخلايا) إلى 3
- القائمة المنسدلة الحد الأدنى للارتفاع (الخلايا) إلى 3
فحص نموذج الرمز
عند إرسال النموذج، ينشئ "استوديو Android" عدة ملفات ويعدّلها. يتم إدراج التغييرات ذات الصلة بهذا الدرس التطبيقي في الجدول أدناه.
الإجراء | الملف المستهدف | تغيير |
تعديل |
| إضافة مستلم جديد يقوم بتسجيل NewsWidget. |
إنشاء |
| تحديد واجهة المستخدم المصغّر للشاشة الرئيسية. |
إنشاء |
| تحدد إعدادات التطبيق المصغّر للشاشة الرئيسية. يمكنك تعديل أبعاد الأداة أو اسمها في هذا الملف. |
إنشاء |
| يحتوي على رمز Kotlin لإضافة وظائف إلى تطبيق الشاشة الرئيسية المصغّر. |
يمكنك العثور على مزيد من التفاصيل حول هذه الملفات في هذا الدرس التطبيقي حول الترميز.
تصحيح الأخطاء واختبار نموذج التطبيق المصغّر
الآن، شغِّل تطبيقك وشاهد تطبيق الشاشة الرئيسية المصغّر. بعد إنشاء التطبيق، انتقِل إلى شاشة اختيار التطبيقات على جهاز Android واضغط مع الاستمرار على رمز مشروع Flutter هذا. اختَر التطبيقات المصغّرة من القائمة المنبثقة.
يعرض جهاز Android أو المحاكي تطبيق الشاشة الرئيسية التلقائي لنظام التشغيل Android.
4. إرسال البيانات من تطبيق Flutter إلى التطبيق المصغّر على الشاشة الرئيسية
يمكنك تخصيص تطبيق الشاشة الرئيسية المصغّر الذي أنشأته. يجب تحديث تطبيق الشاشة الرئيسية المصغّر لعرض عنوان رئيسي وملخص لمقالة إخبارية. تعرِض لقطة الشاشة التالية مثالاً على تطبيق "الشاشة الرئيسية" المصغّر الذي يعرض عنوانًا وملخّصًا.
لتمرير البيانات بين التطبيق وأداة الشاشة الرئيسية، ستحتاج إلى كتابة Dart ورمز أصلي. يقسم هذا القسم هذه العملية إلى ثلاثة أجزاء:
- كتابة رمز Dart في تطبيق Flutter يمكن استخدامه على كل من نظامَي التشغيل Android وiOS.
- إضافة وظائف iOS الأصلية
- إضافة وظائف Android الأصلية
استخدام مجموعات تطبيقات iOS
لمشاركة البيانات بين تطبيق رئيسي على iOS وإضافة أداة، يجب أن ينتمي كلا الهدفَين إلى مجموعة التطبيقات نفسها. لمزيد من المعلومات حول مجموعات التطبيقات، يمكنك الاطّلاع على مستندات مجموعة تطبيقات Apple.
تعديل معرِّف الحزمة:
في Xcode، انتقِل إلى إعدادات هدفك. في مستند توقيع الإمكانات، تحقَّق من ضبط معرِّف الفريق والحزمة.
أضِف مجموعة التطبيقات إلى كل من استهداف Runner وهدف NewsWidgetExtension في Xcode:
اختَر + القدرة -> مجموعات التطبيقات وإضافة مجموعة تطبيقات جديدة. كرِّر هذه الخطوات لكل من هدف Runner (التطبيق الرئيسي) وهدف الأداة.
إضافة رمز Dart
يمكن لكل من تطبيقات iOS وAndroid مشاركة البيانات مع تطبيق Flutter بعدة طرق مختلفة.للتواصل مع هذه التطبيقات، يمكنك الاستفادة من متجر key/value
المحلي للجهاز. يتصل iOS بهذا المتجر UserDefaults
، ويتصل Android بهذا المتجر SharedPreferences
. تضم home_Widgetet واجهات برمجة التطبيقات هذه لتبسيط حفظ البيانات على أي من النظامين الأساسيين وتمكين أدوات الشاشة الرئيسية من سحب البيانات المحدَّثة.
يتم الحصول على بيانات العنوان والوصف من ملف news_data.dart
. يحتوي هذا الملف على بيانات وهمية وفئة بيانات 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,
});
}
تعديل قيم العنوان والوصف
لإضافة وظيفة تحديث تطبيق الشاشة الرئيسية المصغّر من تطبيق Flutter، انتقِل إلى ملف lib/home_screen.dart
. استبدل محتويات الملف بالتعليمة البرمجية التالية. بعد ذلك، استبدِل <YOUR APP GROUP>
بمعرِّف مجموعة التطبيقات.
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);
},
),
);
},
);
},
));
}
}
تحفظ الدالة updateHeadline
أزواج المفتاح/القيمة في مساحة التخزين المحلية على جهازك. يحتفظ المفتاح headline_title
بقيمة newHeadline.title
. يحتفظ المفتاح headline_description
بقيمة newHeadline.description
. وتعمل هذه الدالة أيضًا على إعلام النظام الأساسي الأصلي بأنّه يمكن استرداد البيانات الجديدة المتعلّقة بالتطبيقات المصغّرة على الشاشة الرئيسية وعرضها.
تعديل الدالةfloatActionButton
يمكنك استدعاء الدالة updateHeadline
عند الضغط على floatingActionButton
كما هو موضح:
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'),
),
...
مع هذا التغيير، عندما يضغط المستخدم على زر تعديل العنوان من صفحة مقالة، يتم تعديل تفاصيل تطبيق "الشاشة الرئيسية".
تعديل رمز iOS لعرض بيانات المقالة
لتحديث تطبيق الشاشة الرئيسية المصغّر لنظام التشغيل iOS، يمكنك استخدام Xcode.
افتح ملف NewsWidgets.swift
في Xcode:
ضبط TimelineEntry
.
استبدِل بنية SimpleEntry
بالرمز التالي:
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
}
تحدّد بنية NewsArticleEntry
هذه البيانات الواردة التي سيتم نقلها إلى تطبيق الشاشة الرئيسية المصغّر عند تعديلها. يتطلّب النوع TimelineEntry
مَعلمة تاريخ.لمزيد من المعلومات حول بروتوكول TimelineEntry
، راجِع مستندات ScheduleEntry الخاصة بشركة Apple.
تعديل View
التي تعرض المحتوى
عدِّل تطبيق الشاشة الرئيسية المصغّر لعرض عنوان المقالة الإخبارية ووصفها بدلاً من التاريخ. لعرض النص في SwiftUI، استخدِم طريقة العرض Text
. لتكديس طرق العرض فوق بعضها في SwiftUI، استخدِم طريقة العرض VStack
.
استبدِل طريقة عرض NewsWidgetEntryView
التي تم إنشاؤها بالرمز التالي:
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)
}
}
}
تعديل مزوِّد الخدمة لإعلام تطبيق الشاشة الرئيسية المصغّر بوقت التحديث وطريقة التحديث
استبدِل Provider
الحالية بالرمز التالي. بعد ذلك، استبدِل معرّف مجموعة التطبيقات بـ <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)
}
}
}
يتوافق Provider
في الرمز السابق مع TimelineProvider
. تعتمد ميزة "Provider
" على ثلاث طرق مختلفة:
- تنشئ الطريقة
placeholder
إدخال عنصر نائب عندما يعاين المستخدم تطبيق "الشاشة الرئيسية" المصغّر لأول مرة.
- تقرأ الطريقة
getSnapshot
البيانات من الإعدادات التلقائية للمستخدم وتنشئ الإدخال للوقت الحالي. - تعرض الطريقة
getTimeline
إدخالات المخطط الزمني. ويساعد ذلك في تحديد نقاط زمنية يمكن توقّعها لتعديل المحتوى. يستخدم هذا الدرس التطبيقي حول الترميز دالة getSnapshot لمعرفة الحالة الحالية. تطلب الطريقة.atEnd
من التطبيق المصغّر للشاشة الرئيسية إعادة تحميل البيانات بعد مرور الوقت الحالي.
إضافة تعليق إلى NewsWidgets_Previews
ليس استخدام المعاينات خارج نطاق هذا الدرس التطبيقي حول الترميز. لمزيد من التفاصيل حول معاينة التطبيقات المصغّرة للشاشة الرئيسية في SwiftUI، اطّلِع على مستندات Apple حول تصحيح الأخطاء في التطبيقات المصغّرة.
احفظ كل الملفات وأعِد تشغيل التطبيق وهدف الأداة.
شغِّل الأهداف مرة أخرى للتحقّق من عمل التطبيق وأداة "الشاشة الرئيسية".
- اختَر مخطط التطبيق في Xcode لتشغيل استهداف التطبيق.
- اختَر مخطط الإضافة في Xcode لتشغيل استهداف الإضافة.
- انتقِل إلى صفحة مقالة في التطبيق.
- انقر على الزر لتعديل العنوان. من المفترض أن يتم أيضًا تعديل العنوان الرئيسي في تطبيق "الشاشة الرئيسية" المصغّر.
تحديث رمز Android
أضِف ملف XML المصغّر للشاشة الرئيسية.
في "استوديو Android"، عدِّل الملفات التي تم إنشاؤها في الخطوة السابقة.وافتح ملف res/layout/news_widget.xml
. وهي تحدد بنية تطبيقك المصغّر للشاشة الرئيسية وتصميمه. انقر على رمز في أعلى يسار الصفحة واستبدِل محتوى هذا الملف بالرمز التالي:
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>
يحدد ملف XML هذا طريقتي عرض للنص، أحدهما للعنوان الرئيسي للمقالة والآخر لوصف المقالة. تحدد طرق العرض النصية هذه أيضًا النمط. ستعود إلى هذا الملف خلال هذا الدرس التطبيقي حول الترميز.
تحديث وظيفة NewsWidget
افتح ملف رمز مصدر Kotlin NewsWidget.kt
. يحتوي هذا الملف على فئة تم إنشاؤها باسم "NewsWidget
" تعمل على توسيع فئة AppWidgetProvider
.
تتضمن الفئة NewsWidget
ثلاث طرق من فئتها الفائقة. ستعدّل طريقة onUpdate
. يستدعي Android هذه الطريقة للأدوات على فترات زمنية ثابتة.
استبدِل محتوى ملف NewsWidget.kt
بالرمز التالي:
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)
}
}
}
عند استدعاء onUpdate
، يحصل Android الآن على أحدث القيم من مساحة التخزين المحلية باستخدام طريقة the widgetData.getString()
، ثم يطلب setTextViewText
لتغيير النص المعروض على تطبيق الشاشة الرئيسية.
اختبار التعديلات
اختبِر التطبيق للتأكّد من تحديث أدوات الشاشة الرئيسية بالبيانات الجديدة. لتعديل البيانات، استخدِم تعديل الشاشة الرئيسية FloatingActionButton
في صفحات المقالات. من المفترض أن يتم تعديل تطبيق الشاشة الرئيسية المصغّر بعنوان المقالة.
5- استخدام الخطوط المخصّصة في تطبيق Flutter في التطبيق المصغّر على "الشاشة الرئيسية" على نظام iOS
لقد ضبطت حتى الآن تطبيق Flutter المصغّر على الشاشة الرئيسية لقراءة البيانات التي يوفّرها تطبيق Flutter. يتضمّن تطبيق Flutter خطًا مخصّصًا قد ترغب في استخدامه في تطبيق "الشاشة الرئيسية". يمكنك استخدام الخط المخصّص في تطبيق الشاشة الرئيسية على جهاز iOS. لا تتوفّر ميزة استخدام الخطوط المخصّصة في التطبيقات المصغّرة على الشاشة الرئيسية على Android.
تعديل الرمز البرمجي لنظام التشغيل iOS
يخزِّن Flutter أصوله في الحزمة الرئيسية لتطبيقات iOS. يمكنك الوصول إلى مواد العرض في هذه الحزمة من خلال رمز التطبيق المصغّر للشاشة الرئيسية.
في هيكل NewsWidgetsEntryView في ملف NewsWidgets.swift، أجرِ التغييرات التالية.
أنشِئ دالة مساعدة للحصول على المسار إلى دليل مواد العرض في 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
}
...
}
سجِّل الخط باستخدام عنوان URL لملف الخط المخصّص.
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)
}
...
}
عدِّل طريقة عرض نص العنوان لاستخدام الخط المخصّص.
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)
}
}
...
}
عند تشغيل تطبيق الشاشة الرئيسية المصغّر، فإنه يستخدم الآن الخط المخصّص للعنوان كما هو موضّح في الصورة التالية:
6- عرض تطبيقات Flutter المصغّرة كصورة
في هذا القسم، ستظهر لك رسم بياني من تطبيق Flutter كأداة على الشاشة الرئيسية.
يقدّم هذا التطبيق المصغّر تحدّيًا أكبر من النص الذي عرضته على الشاشة الرئيسية. من الأسهل بكثير عرض الرسم البياني Flutter كصورة بدلاً من محاولة إعادة إنشائه باستخدام مكوّنات واجهة المستخدم الأصلية.
يمكنك ترميز تطبيق الشاشة الرئيسية المصغّر لعرض الرسم البياني Flutter كملف PNG. ويمكن لأداة الشاشة الرئيسية عرض تلك الصورة.
كتابة رمز Dart
على جانب Dart، أضِف الطريقة renderFlutterWidget
من الحزمة home_Widgetet. تستخدم هذه الطريقة أداة واسم ملف ومفتاحًا. ويعرض صورة من تطبيق Flutter المصغّر ويحفظها في حاوية مشترَكة. أدخِل اسم الصورة في الرمز الخاص بك وتأكَّد من إمكانية وصول تطبيق "الشاشة الرئيسية" المصغّر إلى الحاوية. يحفظ key
مسار الملف بالكامل كسلسلة في مساحة التخزين المحلية على الجهاز. يسمح هذا لأداة الشاشة الرئيسية بالعثور على الملف في حالة تغيير الاسم في رمز Dart.
في هذا الدرس التطبيقي حول الترميز، تمثّل الفئة LineChart
في ملف lib/article_screen.dart
الرسم البياني. تقوم طريقة الإنشاء الخاصة بها بعرض CustomPainter الذي يرسم هذا المخطط على الشاشة.
لتنفيذ هذه الميزة، افتح ملف lib/article_screen.dart
. قم باستيراد حزمة home_Widget. بعد ذلك، استبدِل الرمز في الفئة _ArticleScreenState
بالرمز التالي:
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!),
],
),
);
}
}
يُجري هذا المثال ثلاثة تغييرات على الفئة _ArticleScreenState
.
إنشاء GlobalKey
يحصل GlobalKey
على سياق التطبيق المصغّر، وهو مطلوب لمعرفة حجم هذه الأداة .
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
// New: add this GlobalKey
final _globalKey = GlobalKey();
...
}
إضافة مسار imagePath
تخزِّن السمة imagePath
موقع الصورة الجغرافي الذي يتم فيه عرض تطبيق Flutter المصغّر.
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
...
// New: add this imagePath
String? imagePath;
...
}
إضافة المفتاح إلى التطبيق المصغّر لعرضه
تحتوي _globalKey
على تطبيق Flutter المصغّر الذي يتم عرضه في الصورة. في هذه الحالة، يكون تطبيق Flutter المصغّر هو المركز الذي يحتوي على LineChart
.
lib/article_screen.dart
class _ArticleScreenState extends State<ArticleScreen> {
...
Center(
// New: Add this key
key: _globalKey,
child: const LineChart(),
),
...
}
- حفظ الأداة كصورة
يتم استدعاء طريقة renderFlutterWidget
عندما ينقر المستخدم على floatingActionButton
. تحفظ الطريقة ملف PNG الناتج "كلقطة شاشة". إلى دليل الحاوية المشتركة. تحفظ الطريقة أيضًا المسار الكامل للصورة كمفتاح اسم الملف في مساحة تخزين الجهاز.
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);
},
...
}
تعديل رمز iOS
في iOS، يجب تحديث الرمز للحصول على مسار الملف من مساحة التخزين وعرض الملف كصورة باستخدام SwiftUI.
افتح ملف NewsWidgets.swift
لإجراء التغييرات التالية:
إضافة filename
وdisplaySize
إلى البنية NewsArticleEntry
تتضمن السمة filename
السلسلة التي تمثّل المسار إلى ملف الصورة. تحتفظ السمة displaySize
بحجم تطبيق الشاشة الرئيسية المصغّر على جهاز المستخدم. مصدر حجم التطبيق المصغّر للشاشة الرئيسية هو context
.
ios/NewsWidgets/NewsWidgets.swift
struct NewsArticleEntry: TimelineEntry {
...
// New: add the filename and displaySize.
let filename: String
let displaySize: CGSize
}
تعديل الدالة في "placeholder
"
يمكنك تضمين العنصر النائب filename
و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)
}
عليك الحصول على اسم الملف من "userDefaults
" في getSnapshot.
يؤدي ذلك إلى ضبط المتغيّر filename
على قيمة filename
في مساحة التخزين 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"
...
)
إنشاء ChartImage لعرض الصورة من مسار
تنشئ طريقة العرض ChartImage
صورة من محتوى الملف الذي تم إنشاؤه على جانب Dart. ومن هنا، يمكنك ضبط الحجم على 50% من الإطار.
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())
}
...
}
استخدام ChartImage في نص NewsWidgetsEntryView
أضِف طريقة عرض ChartImage إلى نص NewsWidgetsEntryView لعرض ChartImage في التطبيق المصغّر على الشاشة الرئيسية.
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
}
اختبار التغييرات
لاختبار التغييرات، أعِد تشغيل كلٍّ من تطبيق Flutter المستهدف (Runner) وهدف الإضافة من Xcode. للاطّلاع على الصورة، انتقِل إلى إحدى صفحات المقالات في التطبيق واضغط على الزرّ لتحديث تطبيق "الشاشة الرئيسية" المصغّر.
تحديث رمز Android
يعمل رمز Android بالطريقة نفسها التي يعمل بها رمز iOS.
- افتح ملف
android/app/res/layout/news_widget.xml
. يحتوي على عناصر واجهة المستخدم في تطبيق الشاشة الرئيسية المصغّر. استبدل محتوياتها بالرمز التالي:
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>
يضيف هذا الرمز الجديد صورة إلى تطبيق الشاشة الرئيسية، والتي تعرض (في الوقت الحالي) رمز نجمة عامًا. استبدل رمز النجمة هذا بالصورة التي حفظتها في رمز Dart.
- افتح ملف
NewsWidget.kt
. استبدل محتواها بالرمز التالي:
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)
}
}
}
يحفظ رمز Dart هذا لقطة شاشة في مساحة التخزين المحلية باستخدام المفتاح filename
. ويحصل أيضًا على المسار الكامل للصورة وينشئ كائن File
منها. إذا كانت الصورة موجودة، يستبدل رمز Dart الصورة في أداة الشاشة الرئيسية بالصورة الجديدة.
- أعِد تحميل التطبيق وانتقِل إلى شاشة المقالة. انقر على تعديل الشاشة الرئيسية. تعرض أداة الشاشة الرئيسية الرسم البياني.
7. الخطوات التالية
تهانينا
تهانينا، لقد نجحت في إنشاء تطبيقات مصغّرة للشاشة الرئيسية لتطبيقات Flutter لنظامي التشغيل iOS وAndroid.
إضافة روابط تؤدي إلى المحتوى في تطبيق Flutter
قد تحتاج إلى توجيه المستخدم إلى صفحة معيّنة في تطبيقك، حسب الموضع الذي ينقر عليه المستخدم. على سبيل المثال، في تطبيق الأخبار من هذا الدرس التطبيقي حول الترميز، قد ترغب في أن يرى المستخدم المقالة الإخبارية للعنوان الرئيسي المعروض.
لا تندرج هذه الميزة ضمن نطاق هذا الدرس التطبيقي حول الترميز. يمكنك العثور على أمثلة على استخدام ساحة المشاركات التي توفِّرها حزمة home_Widget من أجل تحديد عمليات تشغيل التطبيقات من التطبيقات المصغّرة على الشاشة الرئيسية وإرسال الرسائل من تطبيق الشاشة الرئيسية من خلال عنوان URL. لمزيد من المعلومات، يُرجى الاطّلاع على مستندات الربط بصفحة معيّنة على docs.flutter.dev.
تحديث التطبيق المصغّر في الخلفية
لقد شغّلت تحديثًا لتطبيق "الشاشة الرئيسية" المصغّر باستخدام زر في هذا الدرس التطبيقي حول الترميز. على الرغم من أنّ هذا الأمر معقول للاختبار، إلا أنك قد ترغب في تحديث تطبيق الشاشة الرئيسية المصغّر في الخلفية في رمز الإنتاج. يمكنك استخدام المكوّن الإضافي Workmanager لإنشاء مهام في الخلفية لتحديث الموارد التي يحتاجها تطبيق "الشاشة الرئيسية" المصغّر. لمزيد من المعلومات، راجع قسم تحديث الخلفية في حزمة home_Widgetet.
بالنسبة إلى نظام التشغيل iOS، يمكنك أيضًا جعل أداة الشاشة الرئيسية تطلب الشبكة لتحديث واجهة المستخدم الخاصة بها. للتحكّم في شروط هذا الطلب أو معدّل تكراره، استخدِم "المخطّط الزمني". لمزيد من المعلومات عن استخدام "المخطّط الزمني"، اطّلِع على المقالة تحديث الأداة باستمرار" من Apple. التوثيق.