Liquid Glass Easy

المحركات والعارض

يقرأ Impeller الخلفية، ويحتاج Skia إلى التقاط. والعناصر نفسها.

يحتاج الانكسار إلى البكسلات التي خلف الزجاج. يستطيع Impeller أن يناول الشيدر الخلفيةَ الحية، ولا يستطيع Skia ذلك. هذا الفارق وحده هو القصة كلها، وLiquidGlassView هو الجواب عنه.

الإعدادما تفعله العدسة
Impeller — الافتراضي على iOS و Androidتكسر الخلفية الحية: كل ما رسمه تطبيقك خلفها. بلا عارض ولا عنصر خلفية ولا إعداد.
Skia داخل LiquidGlassViewتكسر backgroundWidget الملتقَط للعارض، أينما جلست العدسة داخل child الخاص به.
Skia بلا عارضتتراجع إلى سطح مصنفر — ضبابية ولون وحافة — وتسجّل تنبيه تنقيح واحداً.

LiquidGlassView#

العارض هو خط الالتقاط: يحوّل backgroundWidget إلى نسيج ثم يسلّم هذا النسيج لكل عدسة داخل child. قائمة معاملاته الكاملة مُولَّدة من الشيفرة في صفحة الواجهة، وهذه هي التي ستمدّ يدك إليها فعلاً.

المعاملالافتراضيما يتحكم به
backgroundWidgetالطبقة التي تُلتقط. العدسات تكسر هذه، لا child.
childnullواجهتك. أي شيء هنا يمكن أن يحمل عدسة، على أي عمق.
pixelRatio1.0دقة الالتقاط. ما دون 1 أرخص وأنعم، والزجاج يخفي قدراً مدهشاً من ذلك.
realTimeCapturetrueإعادة الالتقاط كل إطار. أطفئه لخلفية لا تتحرك.
useSynctrueيُبقي الالتقاط متزامناً مع الإطار قيد البناء. إطفاؤه أرخص وقد يتأخر إطاراً واحداً.
refreshRatedeviceRefreshRateيحدّ من تواتر الالتقاط، بمعزل عن معدّل الشاشة.
useImpellerBackdropnullيتجاوز الاكتشاف التلقائي للمحرك. اتركه فارغاً إلا إن كنت تختبر المسار الآخر عن قصد.
controllernullLiquidGlassViewController، من أجل captureOnce() — لقطة واحدة عند إشارتك — ولقيادة التكيّف يدوياً.
adaptiveSamplingnullعلى المحركين. يشغّل التقاط الإضاءة الصغير الذي يقرأه LiquidGlassAdaptivity. والفارغ يعني ألا معاينة إطلاقاً، فترتدّ الأسطح المتكيّفة إلى تخمين. والتقاط واحد يخدم كل سطح متكيّف في العارض، وهو مقيّد بالخمول: شاشة ساكنة لا تعاين شيئاً.
batchtrueعلى Impeller فقط. يضع عدسات child على قراءة واحدة مشتركة للخلفية، وعدسات backgroundWidget على أخرى. وهو خامل على مسار الالتقاط الذي يقرأ أصلاً مرة واحدة لكل عارض. انظر الحزمة.
regionCapturefalseيلتقط مستطيل كل عدسة بدل الخلفية كلها — مكسبٌ حين تجلس عدسة صغيرة على خلفية كبيرة، وخسارةٌ حين تنتشر عدسات كثيرة عليها. على مسار useSync في Skia فقط.

التقاط واحد أم كل إطار#

يرسم realTimeCapture: true الخلفيةَ في كل إطار. وهذا صحيح لقائمة تنساب أو فيديو أو تدرّج متحرك — أي شيء يتحرك حقاً. وهو هدر خالص لخلفية لا تتغيّر أبداً.

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

Dart
final controller = LiquidGlassViewController();

LiquidGlassView(
  controller: controller,
  backgroundWidget: const MyBackground(),
  realTimeCapture: false,
  child: const MyGlassUI(),
);

// Re-snapshot by hand after the background actually changes:
await controller.captureOnce();

يشغّل عرض هذه الصفحة خلفيةً لا تكفّ عن الانزلاق. اضبط الالتقاط على مجمَّد فيظل الزجاج يكسر أشرطةً تجاوزها الزمن بينما تمضي الأشرطة حوله — والفارق بينهما هو مقدار قِدَم اللقطة، وcapture once يغلقه.

متى يريد Impeller عارضاً رغم ذلك#

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

  • `adaptiveSampling` — وهو غير اختياري. يحكم LiquidGlassAdaptivity على الخلفية بقراءة بكسلاتها، وقراءة البكسلات تعني التقاطاً. ولا التقاط لدى Impeller: فهو لا يأخذ واحداً أبداً. فـLiquidGlassView مضبوطاً عليه adaptiveSampling هو الطريق ليجد الزجاج المتكيّف ما يحكم عليه على كلا المحركين — وبدونه يرتدّ كل سطح متكيّف إلى التخمين من سِمة التطبيق. وهو التقاط واحد ضئيل (بنسبة بكسل 0.05، ثماني مرات في الثانية) تتشاركه كل الأسطح المتكيّفة في العارض، ويتوقف تماماً ما دامت الشاشة ساكنة.
  • `batch` — خاص بـImpeller بطبعه. يحزم العارض عدساته افتراضياً، وهو توفير خالص على Impeller: فمسار الالتقاط يقرأ أصلاً مرة واحدة لكل عارض، بينما على Impeller تنسخ كل عدسة الخلفية لنفسها ما لم تقل الحزمة غير ذلك.
  • شجرة واحدة، محركان. لا يكلّف العارض على Impeller شيئاً وراء العنصر نفسه، فكتابة الصفحة داخله تعني أن الشيفرة ذاتها تعمل على الهاتف وعلى الويب بلا تفرّع.

ترجم الشيدرات قبل الإطار الأول#

هذا ليس عن الالتقاط، ولا عن المحرك أيضاً — بل ينطبق على الاثنين. فبرامج الزجاج تُترجَم مرة واحدة في كل تشغيل للتطبيق، على يد أول عدسة تُركَّب، والعدسة التي لم يصل برنامجها بعد ترسم بديلها المصنفر حتى يصل. وLiquidGlassShaders.ensureLoaded() في main() ينقل تلك الترجمة إلى ما قبل الإطار الأول.

Dart
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // Every program at once: the lens's, and the blender's.
  await LiquidGlassShaders.ensureLoaded();
  runApp(const MyApp());
}
  • للحالة العامة: useSync: true و pixelRatio: 0.81.0.
  • عندما يسبق الأداء: useSync: false و pixelRatio: 0.50.7.
  • خلفية ساكنة: realTimeCapture: false يتفوّق على كل المقابض الأخرى مجتمعة.

التعليقات

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