عنوان مناسب بالإنجليزية يتجاوز مساحته بالألمانية. وسعر يظهر كاملًا في هاتف وينكسر إلى سطرين في هاتف أصغر. ونص زر يُقطع عندما يكبّر المستخدم خط النظام. الجواب الشائع في Flutter هو widget يصغّر النص حتى يناسب مكانه، ولسنوات كان هذا الـ widget هو auto_size_text.
يصف الـ README الخاص بـ auto_fit_text أين تتعطل تلك الطريقة اليوم: textScaleFactor التي صارت deprecated، وقياس يعامل كل نص كأنه من اليسار إلى اليمين، وانهيار مع WidgetSpan، ومجموعات ترتعش، وتخطيطات مثل IntrinsicHeight لا يعمل فيها الـ widget. auto_fit_text بديل بنفس شكل الواجهة (API) يعالج هذه الحالات.
ماذا يقدّم
يراعي العرض والارتفاع وmaxLines. يجد أكبر حجم خط يناسب به النصُّ المساحة المتاحة، بين minFontSize وحجم الخط في الـ style.
بحث ثنائي مع تخزين مؤقت. يبحث بين الأحجام المرشّحة بالبحث الثنائي (binary search) ويحفظ النتائج في cache لكل قيود (constraints). وإذا كان النص مناسبًا من البداية، يكفي قياس واحد.
معالجة صحيحة لـ TextScaler. أحجام الخط منطقية. والـ TextScaler المحيط (أو المعطى) يُطبَّق عند القياس وعند الرسم معًا، فيعمل التكبير غير الخطي في Android 14. وtextScaleFactor القديمة غير مستعملة.
يقيس بنفس مدخلات Text. الـ Directionality المحيط، ولغة Localizations، وDefaultTextStyle (الـ style وmaxLines وtextAlign وsoftWrap وoverflow وtextWidthBasis وtextHeightBehavior)، وDefaultTextHeightBehavior، وMediaQuery.boldText.
نص قابل للتحديد. يعمل داخل SelectionArea، والمُنشئ AutoFitText.selectable يبني SelectableText مستقلًّا.
النص الغني (rich text). AutoFitText.rich يصغّر كل الـ spans بالمعامل نفسه، ويرث DefaultTextStyle، ولا ينهار مع WidgetSpan.
المجموعات. AutoFitTextGroup يجعل عدة نصوص تتشارك أصغر حجم ناتج. كل عضو يُقاس حسب قيوده هو فقط، فتستقر المجموعة بعد إطار إضافي واحد على الأكثر ولا تدخل أبدًا في حلقة إعادة بناء.
قراءة الحجم. onFontSizeResolved يعطيك الحجم المعروض بعد الإطار. واستدعاء setState داخله آمن.
التخطيطات الذاتية (intrinsic). يدعم الأحجام الذاتية و dry layout و dry baselines، فيعمل داخل IntrinsicHeight وIntrinsicWidth وعناوين Chip.
الحالات الحدّية. يعالج الصناديق ذات الحجم الصفري والنصوص الفارغة. والصندوق غير المحدود في الاتجاهين يُطلق assertion في وضع debug يشرح الحل.
طريقة الاستعمال
أضِفه من GitHub:
dependencies:
auto_fit_text:
git: https://github.com/AbdeldjalilChougui/auto_fit_text
import 'package:auto_fit_text/auto_fit_text.dart';
الحالة الأساسية:
SizedBox(
width: 200,
height: 60,
child: AutoFitText(
'The quick brown fox jumps over the lazy dog',
style: const TextStyle(fontSize: 40),
maxLines: 2,
minFontSize: 10,
overflowReplacement: const Text('…'),
onFontSizeResolved: (size) => debugPrint('fitted at $size'),
),
)
حجم الخط يبدأ من حجم الخط في الـ style (بسقف maxFontSize) وينزل إلى minFontSize بخطوات قدرها stepGranularity. وللاختيار من أحجام ثابتة بدل ذلك:
const AutoFitText(
'Supercalifragilisticexpialidocious',
presetFontSizes: [32, 24, 16],
maxLines: 1,
)
لمشاركة حجم واحد بين عدة widgets، احتفظ بمجموعة في حقل داخل State:
final group = AutoFitTextGroup();
Row(
children: [
Expanded(child: AutoFitText('OK', group: group, maxLines: 1)),
Expanded(child: AutoFitText('Remind me later', group: group, maxLines: 1)),
],
)
من دون المجموعة، يبقى "OK" كبيرًا بينما يصغر "Remind me later"، فيظهر الزرّان المتجاوران غير متناسقين.
النص الغني، حيث يصغر كل span بالمعامل نفسه:
const AutoFitText.rich(
TextSpan(
text: 'Total: ',
children: [
TextSpan(
text: '1 250,00 DZD',
style: TextStyle(fontSize: 40, fontWeight: FontWeight.bold),
),
],
),
style: TextStyle(fontSize: 24),
maxLines: 1,
)
النص القابل للتحديد:
const SelectionArea(child: AutoFitText('Select me', maxLines: 1));
// or, standalone:
const AutoFitText.selectable('Select me', maxLines: 1);
يحتاج الـ widget عرضًا أو ارتفاعًا محدودًا. داخل Row أو Column، ضعه في Expanded أو Flexible. ومجلد example/ فيه تطبيق معرض جاهز للتشغيل.
الانتقال من auto_size_text
في أغلب الحالات تغيّر اسم الـ widget والمجموعة فقط: AutoSizeText يصبح AutoFitText، وAutoSizeText.rich يصبح AutoFitText.rich، وAutoSizeGroup يصبح AutoFitTextGroup. المعاملات المسمّاة هي نفسها. وفي الـ README الجدول الكامل؛ وهذه هي الفروق:
-
textScaleFactor: 1.5تصبحtextScaler: TextScaler.linear(1.5)، والـ scalers غير الخطية مدعومة. -
minFontSizeلم يعد يجب أن تكون من مضاعفاتstepGranularity، وstepGranularityتقبل أي قيمة موجبة. -
presetFontSizesتُقبل بأي ترتيب. -
wrapWordsتستعمل قواعد كسر الأسطر في محرك النص، فالفراغ غير القابل للكسر (non-breaking space) يُبقي الكلمات معًا. -
.richتقبل أيInlineSpan. - معاملات جديدة:
textWidthBasisوtextHeightBehaviorوselectionColorوonFontSizeResolved، والمُنشئAutoFitText.selectable.
وبعض تغييرات السلوك تستحق المراجعة بعد تغيير الاسم:
- النص يُقاس بالـ
Directionalityالمحيط وبـTextAlign.start. ويذكر الـ README أنauto_size_text3.0.0 كان يقيس كل نص كأنه من اليسار إلى اليمين ومحاذًى إلى اليسار. -
AutoFitText.richيقيس بعد دمجDefaultTextStyleفي الـ style الجذر، كما يرسمهText.rich. - يمكن ضبط
overflowوoverflowReplacementمعًا. - إذا كان العرض والارتفاع كلاهما غير محدود، ترمي نسخ debug الآن assertion يشرح الحل. ونسخ release تبقى ترسم النص بحجمه الكامل.
ويسرد الـ README أيضًا، بأرقامها، الـ issues المفتوحة في متتبّع auto_size_text التي تعالجها هذه الحزمة بطريقة مختلفة: التخطيطات الذاتية والـ chips، وانهيار WidgetSpan، والنص القابل للتحديد، وtextScaleFactor القديمة، وارتعاش المجموعات وحلقات إعادة البناء، وقراءة حجم الخط، وغياب TextHeightBehavior وTextWidthBasis، وتجاوز النص مع MediaQuery.boldText، وكائنات TextPainter التي لم يكن يُتخلَّص منها، وقيود wrapWords وminFontSize. لكل واحدة اختبار، ما عدا التخلص من TextPainter فهو معالَج في الكود.
ما يجب معرفته
-
البحث يفترض نصًّا "منتظمًا". البحث الثنائي يفترض أن الخط الأصغر لا يحتاج أبدًا مساحة أكبر من الخط الأكبر. هذا صحيح للنص العادي. الخطوط غير المألوفة أو بعض قيم
StrutStyleقد تخالفه. -
WidgetSpanيُقدَّر تقديرًا. لا يمكن قياسه قبل التخطيط، فيُقاس كمربع ضلعه حجم الخط. والـ widgets ذات الحجم المختلف قد تجعل الملاءمة غير دقيقة. - المجموعات تحتاج إطارًا واحدًا. المجموعة تستقر في الإطار الذي يلي أول تخطيط لأعضائها، فقد يُظهر الإطار الأول كل عضو بحجمه الخاص.
-
التصغير فقط. الملاءمة تبدأ من حجم الخط في الـ style. لملء صندوق كبير، أعطِ
fontSizeكبيرًا في الـ style. -
مكان المؤشّر.
AutoFitText.selectableيفترض عرض المؤشّر الافتراضي فيSelectableText، وهو 2 px، عند حجز مكان له. -
إذا لم يناسب أي حجم. إن لم يكفِ حتى
minFontSize، يُقصّ النص حسبoverflow، إلا إذا ضُبطoverflowReplacement. -
ما تم اختباره. الاختبارات تستعمل خط اختبارات Flutter، وكل حرف فيه مربع بحجم em واحد. الخطوط الحقيقية تمر بنفس مسارات
TextPainter، لكن الملاءمة لم تُراجَع بصور golden على الأجهزة.
لماذا بنيته
تصغير النص ليناسب مكانه يبدو مشكلة محلولة، وهو كذلك تقريبًا في تطبيق من اليسار إلى اليمين بحجم الخط الافتراضي. لكن الثغرات التي يسردها الـ README تظهر بالضبط في التطبيقات التي تحتاج هذا الـ widget أكثر من غيرها.
الواجهة العربية من اليمين إلى اليسار، وقياسها كأنها من اليسار إلى اليمين يعطي جوابًا مختلفًا عمّا يُرسم. والمستخدم الذي يكبّر خط النظام في Android 14 يحصل على تكبير غير خطي، لا يصفه معامل تكبير واحد. وإعداد الخط الغامق في خيارات الوصول يجعل النص أعرض مما قيس. والسعر داخل بطاقة غالبًا نص غني، وصف الأزرار يحتاج حجمًا مشتركًا واحدًا لا يرتعش.
أردت widget يقيس بنفس المدخلات التي يرسم بها Text، حتى يكون ما قيس هو ما يظهر. وأردت أن يكون الانتقال رخيصًا للكود الموجود، ولهذا يتبع شكل الواجهة وأسماء المعاملات ما في auto_size_text، ويشرح الـ README كل فرق.
الكود
الكود، وجدول الانتقال، وتطبيق المعرض، والاختبارات موجودة على GitHub: https://github.com/AbdeldjalilChougui/auto_fit_text. الرخصة MIT، والـ issues والـ pull requests مرحّب بها.
نُشر في الأصل على abdeldjalilchougui.biz.
Top comments (0)