Liquid Glass Easy

التكيّف

زجاج يقرأ ما خلفه فيغيّر رأيه.

للشريط العائم فوق صورة مشكلة واحدة عسيرة: الصورة. فالأيقونات البيضاء تختفي فوق شاطئ، والسوداء تختفي فوق سماء ليلية. والتكيّف هو الجواب الذي يستخدمه iOS — يتبدّل لون الزجاج ولون المحتوى بين لوحتين تبعاً لكون الخلفية خلف تلك العدسة بعينها داكنة أو فاتحة، ويتحرك كلاهما عند كل انقلاب.

Dart
LiquidGlassLens(
  style: const LiquidGlassStyle(
    adaptivity: LiquidGlassAdaptivity(),
  ),
  child: const Icon(Icons.favorite),   // follows the verdict
)

يُلَفّ الابن بـ IconTheme وDefaultTextStyle يحملان لون المحتوى النشط، فتتكيّف أي Icon أو Text لا تثبّت لونها بنفسها.

لا بدّ لشيء أن يقرأ البكسلات#

ذلك الإعداد وحده يقرّر ما يُفعل بالحكم، لا من أين يأتي الحكم. ولا يقرأ أحدٌ البكسلات حتى يفتح شيءٌ المعايِن، والذي يفعل ذلك اثنان لا ثالث لهما — هيكل أو عارض:

Dart
// 1 — the scaffold opens it for you
LiquidGlassScaffold(
  adaptivity: const LiquidGlassScaffoldAdaptivity(),
  body: myPage,
  bottomBar: myTabBar,        // chrome adapts, OS bar icons follow
)

// 2 — a view, for glass you place yourself
LiquidGlassView(
  adaptiveSampling: const LiquidGlassAdaptiveSampling(),
  backgroundWidget: const MyPhoto(),
  child: myGlassUI,
)

// 3 — neither: it still runs, it just cannot look
LiquidGlassLens(
  style: const LiquidGlassStyle(adaptivity: LiquidGlassAdaptivity()),
  child: const Icon(Icons.favorite),
)   // → falls back to the app theme's brightness, and says so once

الثلاثة كلها صحيحة. داخل الهيكل، adaptivity هو المفتاح ولا يملك حفيدٌ قلبَه — فشريط تبويبات يحمل style.adaptivity خاصاً به لن يبدأ التقاطات على هيكل لم يطلبها. وداخل العارض، adaptiveSampling هو المفتاح. وبلا هذا ولا ذاك، يظل كل سطح متكيّف يحلّ حكمه ويرسم لوحةً؛ غير أنه يحلّه من أسفل السلسلة أدناه، وهو ما يعني عملياً لوحةً واحدة إلى الأبد. وتطبع الحزمة تحذيراً واحداً يسمّي السبب، فتكون الصفحة المتجمّدة على اللوحة الخطأ قابلة للتشخيص لا لغزاً.

الطرق الثلاث لاستخدامه#

  • في الزمن الحقيقي، في كل مكان. ضع LiquidGlassScaffold مع adaptivity، أو LiquidGlassView مع adaptiveSampling، فوق الصفحة. عندها تحكم كل عدسة متكيّفة تحته على البكسلات التي خلف نفسها وتنقلب وحدها، حيّاً، كلما انزلقت الخلفية أو تغيّرت — ولا توصّل شيئاً لكل عدسة.
  • عدسة واحدة، بطريقتها. امنح تلك العدسة style.adaptivity خاصاً بها فتحتفظ بالحكم المشترك لكنها تستعمل لوحاتك وعتباتك وduration الخاص بك.
  • إطلاقاً. يُخرج LiquidGlassAdaptivity.none على سطحٍ ذلك السطحَ من الأمر كلياً، ولو كان داخل منطقة أو هيكل يكيّف كل ما عداه: فيبقي appearance.color العادي ولا تُركَّب لوحة على ابنه.
  • مثبّت، بلا معاينة إطلاقاً. permanentBrightness حين تعرف سلفاً ما خلف الزجاج وأنه لن يتغيّر — عدسة على خلفية صنعتها أنت، أو فوق صورة شحنتها مع التطبيق. وهو ليس وضع التطبيق الفاتح/الداكن: بل هو اختيارك أنت أيَّ اللوحتين ترتديها هذه العدسة، بشكل دائم. وهذا وحده لا يحتاج هيكلاً ولا عارضاً ولا معايِناً — فلا أحد يقرأ البكسلات من أجله، لأن الجواب أُعطي سلفاً.
Dart
// pinned: this lens is always on the dark palette, and never samples
LiquidGlassLens(
  style: const LiquidGlassStyle(
    adaptivity: LiquidGlassAdaptivity(
      permanentBrightness: Brightness.dark,
    ),
  ),
  child: const Icon(Icons.favorite),
)

من أين يأتي الحكم#

من أين يأتي الحكم#

بترتيب الأولوية، ويفوز أول من يجيب:

  • permanentBrightness — حكم يدوي لا يتنازل، وهو الوحيد الذي يوقف الالتقاطات لذلك السطح. استخدمه حين تعرف سلفاً ما خلف الزجاج، أو لتقود اللوحة من حالتك أنت.
  • `link` متبوع — على المستهلك، يعني الرابط دائماً اتبعه.
  • `LiquidGlassAdaptiveArea` المحيطة — يتبعها الأحفاد بلا حاجة إلى رابط.
  • معاينته الخاصة، حين يجلس السطح تحت معايِن مفتوح: يأخذ العارض التقاطاً ثانياً ضئيلاً لخلفيته، ويحوّل البكسلات إلى إضاءة CIE L* الإدراكية، ويصنّف حدود ذلك السطح نفسه بتنعيم وتباطؤ حدّي.
  • initialBrightnessتخمين لا حكم. يصمد حتى تنتج المعاينة شيئاً حقيقياً ثم يتنازل: فالتخمين المطابق يعني ألا شيء يتحرك عند الدخول، والخاطئ ينتقل متحركاً إلى الحقيقة.
  • brightnessFallback — أسفل السلسلة، لسطح لا يستطيع قراءة البكسلات إطلاقاً. يقرأ appTheme (الافتراضي) Theme.of(context).brightness، ويقرأ platform قيمة MediaQuery.platformBrightness، أي مفتاح النظام الذي لا يؤثر فيه MaterialApp.themeMode.

اللوحات#

الحقلالافتراضيما يفعله
glassColorOnDark0x26000000لون الزجاج حين تكون الخلفية خلف العدسة داكنة.
contentColorOnDarkwhiteلون الأيقونات والنص فوق خلفية داكنة.
glassColorOnLight0x3DFFFFFFلون الزجاج فوق خلفية فاتحة.
contentColorOnLight0xFF1C1C1Eلون الأيقونات والنص فوق خلفية فاتحة.
duration300msمدة حركة الانقلاب.
initialBrightnessnullما يُفترض قبل وصول الحكم الأول — تخمينك لِما يُركَّب العنصر فوقه.
continuousGlassColorfalseيُنسِل اللون عبر المدى كله بدل التبديل بين الطرفين.

حكم واحد لعنقود كامل#

خمس عدسات متكيّفة في صف تعاين كلٌّ رقعتها، وفوق صورة مزدحمة قد تختلف — تنقلب واحدة ولا تنقلب جارتها، فيرتجف الصف. وLiquidGlassAdaptiveArea يعالج ذلك: منطقة غير مرئية لا ترسم شيئاً، تعاين حدود نفسها مرة واحدة، وتسلّم الحكم الواحد لكل حفيد متكيّف. منطقة واحدة وجواب واحد وانقلابات متزامنة.

Dart
LiquidGlassAdaptiveArea(
  adaptivity: const LiquidGlassAdaptivity(),
  systemChrome: LiquidGlassSystemChrome.statusBar,
  child: Row(children: myGlassActions),
)

الحفيد الذي لا تكيّف له يرث لوحات المنطقة كاملةً. والحفيد الذي له تكيّفه يحتفظ بلوحاته لكنه يتبع حكم المنطقة — إلا إن حمل permanentBrightness صريحاً، لأن اليدوي يفوز دائماً، أو حمل link خاصاً به، لأن القناة الصريحة تغلب المحيطة.

بعض التابعين لا يمكن أن يكونوا في شجرة المنطقة: فرع آخر من الشجرة، أو مكوّن له خط تصيير خاص به — مثل LiquidGlassTabBar في وضع الحبّة الزجاجية. وLiquidGlassAdaptivityLink هو القناة بينهما. على المنطقة يعني الرابط النشر، وعلى السطح يعني الاتّباع.

Dart
final link = LiquidGlassAdaptivityLink();

LiquidGlassAdaptiveArea(                         // PUBLISHER
  adaptivity: myPalettes.copyWith(link: link),
  child: MyHeader(),
)

LiquidGlassLens(                                 // FOLLOWER, anywhere
  style: LiquidGlassStyle(
    adaptivity: myPalettes.copyWith(link: link),
  ),
  child: myAction,
)

استخدم ناشراً واحداً لكل رابط. والتابع لا يعاين أبداً — بل يعكس، فينقلب في الإطار نفسه — فامنح الناشرَ والتابعين duration نفسه لتسير الانتقالات متزامنة. والرابط ValueNotifier<Brightness?> عادي، فيمكن لتطبيقك أن يقرأه، أو أن يقود مجموعةً كاملة بضبط value بنفسه؛ ويحمل أيضاً مُخطِر lightness ينساب منه التابعون الذين يشغّلون continuousGlassColor. والمنطقة التي لا رابط لها تعمل كما هي — تسكّ رابطاً خاصاً لشجرتها.

التثبيت، والتكيّف عند الإشارة#

معاينةُ كل إطار من اندفاعة تمرير عملٌ مهدور، وانقلابُ اللوحات في وسط التمرير قد يبدو صاخباً. وLiquidGlassAdaptivityController يوقف مجموعةً كاملة ويتيح لك نظرةً واحدة عند إشارتك — عند استقرار التمرير أو دخول صفحة. وadaptOnce() يمنح كل عنصر يحمل المتحكم نظرةً واحدة بالضبط: يعاين، وينتقل متحركاً إلى النتيجة، ثم يتجمّد ثانية، ويظل enabled خاملاً طوال ذلك. ويبدّل enable() وdisable() التكيّفَ المستمر.

Dart
final adaptCtrl = LiquidGlassAdaptivityController(enabled: false);

NotificationListener<ScrollEndNotification>(
  onNotification: (_) { adaptCtrl.adaptOnce(); return false; },
  child: myFeed,
)

الأيقونات والنصوص المجرّدة#

تركّب العدسة اللون المتكيّف لابنها وحده، فالعنوان الرئيس الجالس مباشرة على صورة لا سبيل له إلى المتابعة. وLiquidGlassAdaptiveContent يسدّ تلك الثغرة — آلة الحكم نفسها، وIconTheme وDefaultTextStyle المتحركان نفساهما، بلا زجاج.

Dart
LiquidGlassAdaptiveContent(
  adaptivity: const LiquidGlassAdaptivity(),
  child: Text('Reykjavík', style: TextStyle(fontSize: 34)),
)

وإن تركت adaptivity فارغاً ورث إعداد المنطقة المحيطة كاملاً. أما المحتوى الذي يتجاهل IconTheme وDefaultTextStyle — كـSvgPicture أو CustomPaint أو هالة يجب أن تنقلب مع الحبر الذي تحتها — فيأخذ `builder` بدلاً من ذلك، يناولك اللون والحكم مباشرة:

Dart
LiquidGlassAdaptiveContent(
  builder: (context, color, brightness) => SvgPicture.asset(
    'assets/logo.svg',
    colorFilter: ColorFilter.mode(color, BlendMode.srcIn),
  ),
)

التعليقات

التعليقات هي مناقشات GitHub — يمكنك الرد من أيٍّ من المكانين.