معیارهای خط (Line Metrics) در فلاتر – به زبان ساده
در گذشته بسیاری از ابزارهای سطح پایین برای کار با متن در فلاتر در لایه نیتیو از دید توسعهدهنده پنهان بودند. اما در نسخههای اخیر بسیاری از موارد مرتبط با Line Metrics در Dart افشا شدهاند. در این مقاله به بررسی مفهوم Line Metrics در فلاتر میپردازیم.
Line Metrics چیست؟
در فلاتر یک کلاس جدید به نام «معیارهای خط» (Line Metrics) (+) وجود دارد که اطلاعاتی در مورد خطوط منفرد یک پاراگراف که لیآوتبندی شده است ارائه میکند.
این کلاس با حذف سازنده و توضیحات به صورت زیر است:
1class LineMetrics {
2 final bool hardBreak;
3 final double ascent;
4 final double descent;
5 final double unscaledAscent;
6 final double height;
7 final double width;
8 final double left;
9 final double baseline;
10 final int lineNumber;
11}
در کلاس فوق:
- hard break: تعیین میکند آیا خط با یک کاراکتر خط جدید مانند \n پایان یافته یا نه.
- Ascent: فاصله از خط مبنا تا بالای خط را تعیین میکند.
- Descent: فاصله از خط مبنا تا انتهای خط را تعیین میکند.
- unscaled ascent: همان accent با نادیده گرفتن مضرب TextStyle.height است.
- height: فاصله از انتها تا بالای خط را تعیین میکند.
- width: فاصله از سمت چپ تا سمت راست خط را تعیین میکند.
- Baseline: خط مبنا یا خطی که متن روی آن قرار میگیرد، از بالای پاراگراف و نه بالای خط اندازهگیری میشود.
- line number: عدد خط جاری در پاراگراف که از اندیس صفر آغاز میشود.
چگونه Line Metrics را به دست آوریم؟
فهرستی از معیارهای خط را میتوانید با استفاده از کلاس TextPainter به دست آورید:
1List<LineMetrics> lines = textPainter.computeLineMetrics();
همچنین میتوانید از کلاس Paragraph استفاده کنید. در واقع کلاس TextPainter نیز در پسزمینه این را فرا میخواند:
1List<LineMetrics> lines = paragraph.computeLineMetrics();
بررسی یک مثال
فرض کنید رشتهای مانند زیر داریم:
My text line.\nThis line wraps to the next.\nAnother line.
همچنین تصور کنید محدودیت عرض لیآوت را نیز به قدر کافی کوچک تعیین کردهایم تا یک soft-wrap روی طولانیترین خط اعمال شود و بدین ترتیب نتیجه زیر حاصل شود:
باکسهای قرمز و خطوط آبی بر اساس مقادیر LineMetrics ترسیم شدهاند. خطوط آبی، خط مبنا (Baseline) هستند. دادههای خام هر یک از چهار خط به صورت زیر است:
hardBreak = true ascent = 27.83203125 descent = 7.32421875 unscaledAscent = 27.83203125 height = 35.0 width = 160.35546875 left = 0.0 baseline = 27.67578125 lineNumber = 0 hardBreak = false ascent = 27.83203125 descent = 7.32421875 unscaledAscent = 27.83203125 height = 35.0 width = 284.78125 left = 0.0 baseline = 62.67578125 lineNumber = 1 hardBreak = true ascent = 27.83203125 descent = 7.32421875 unscaledAscent = 27.83203125 height = 35.0 width = 64.78515625 left = 0.0 baseline = 97.67578125 lineNumber = 2 hardBreak = true ascent = 27.83203125 descent = 7.32421875 unscaledAscent = 27.83203125 height = 35.0 width = 167.4921875 left = 0.0 baseline = 132.67578125 lineNumber = 3
معیارهای خط چه اهمیتی دارند؟
ابتدا باید اشاره کنیم که کلاس LineMetrics همه دادههای معیار را برای هر خط، در یک جا گردآوری کرده است. محاسبه این موارد در گذشته بسیار دشوار بود. دوم این که داشتن یک فهرست از معیارهای خط موجب شده که یافتن تعداد خطوط بسیار آسان شود:
1List<ui.LineMetrics> lines = textPainter.computeLineMetrics();
2int numberOfLines = lines.length;
در گذشته تنها گزینه ما دریافت فهرستی از TextBoxes بود، اما زمانی که متنهای دوطرفه وجود داشته باشند، اتفاقات پیچیدهای رخ میداد. در این حالت امکان محاسبه تعداد باکسها وجود نداشت، زیرا باکسها بهصورت زیر ظاهر میشدند:
اما زمانی که از کلاس LineMetrics استفاده کنیم نتیجه زیر به دست میآید:
محدودیتهای LineMetrics
استفاده از کلاس LineMetrics قطعاً برای کسانی که میخواهند کارهای سطح پایینی در مورد رندر کردن متن انجام دهند، مسیر مناسبی محسوب میشود. با این حل باید به نکات زیر توجه داشته باشید:
- LineMetrics همچنان تصور میکند که متن دارای لیآوت افقی استاندارد LTR یا RTL در یک باکس مستطیلی است. اگر از لیآوت های سفارشی استفاده میکنید که هر کلمه به صورت یک پاراگراف جداگانه رندر میشود، کلاس LineMetrics نمیتواند کمک چندانی به شما کمک کند.
- در کلاس LineMetrics دریافت متن واقعی هر خط کار دشواری است، گرچه استفاده از آن به چنین منظوری نیز چندان متداول نیست. در این موارد باید از ترکیبی از معیارها و فراخوانی TextPainter.getPositionForOffset استفاده کنید.
البته موارد فوق مشکلات بزرگی محسوب نمیشوند و در حالت کلی استفاده از این کلاس بسیار مفید است.
تیم فلاتر از تمایل توسعهدهندگان برای داشتن ابزاری مانند یک line breaker و امکان اندازهگیری و رسم متن به شیوهای کارآمد آگاه هستند. با این حال در این موارد باید ابتدا بحث و بررسی بیشتری صورت بگیرد.
کد تکمیلی
اگر میخواهید با کلاس LineMetrics بیشتر کار کنید، میتوانید کد زیر را در یک پروژه جدید فلاتر کپی کنید. توجه داشته باشید که باید از نسخهای از فلاتر استفاده کنید که شامل LineMetrics باشد.
1import 'package:flutter/material.dart';
2import 'dart:ui' as ui;
3void main() => runApp(MyApp());
4class MyApp extends StatelessWidget {
5 @override
6 Widget build(BuildContext context) {
7 return MaterialApp(
8 home: Scaffold(
9 body: HomeWidget(),
10 ),
11 );
12 }
13}
14class HomeWidget extends StatelessWidget {
15 @override
16 Widget build(BuildContext context) {
17 return Center(
18 child: CustomPaint(
19 size: Size(300, 300),
20 painter: MyPainter(),
21 ),
22 );
23 }
24}
25class MyPainter extends CustomPainter {
26 final text = 'My text line.\nThis line wraps to the next.\nAnother line.';
27 //final text = 'My text line.\nThis كلمة makes more boxes.\nAnother line.';
28final bluePaint = Paint()
29 ..color = Colors.blue
30 ..style = PaintingStyle.stroke
31 ..strokeWidth = 1;
32final redPaint = Paint()
33 ..color = Colors.red
34 ..style = PaintingStyle.stroke
35 ..strokeWidth = 1;
36@override
37 void paint(Canvas canvas, Size size) {
38 final textStyle = TextStyle(
39 color: Colors.black,
40 fontSize: 30,
41 );
42 final textSpan = TextSpan(
43 text: text,
44 style: textStyle,
45 );
46 final textPainter = TextPainter(
47 text: textSpan,
48 textDirection: TextDirection.ltr,
49 );
50 textPainter.layout(
51 minWidth: 0,
52 maxWidth: size.width,
53 );
54 final offset = Offset(0, 0);
55 textPainter.paint(canvas, offset);
56List<ui.LineMetrics> lines = textPainter.computeLineMetrics();
57for (ui.LineMetrics line in lines) {
58 final baseline = line.baseline;
59 final left = line.left;
60 final top = line.baseline - line.ascent;
61 final right = left + line.width;
62 final bottom = line.baseline + line.descent;
63 final rect = Rect.fromLTRB(left, top, right, bottom);
64canvas.drawLine(
65 Offset(left, baseline),
66 Offset(right, baseline),
67 bluePaint,
68 );
69 canvas.drawRect(rect, redPaint);
70 }
71 }
72@override
73 bool shouldRepaint(CustomPainter old) {
74 return false;
75 }
76}
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی
- ساخت اپلیکیشن فلاتر ToDo با SQLite — از صفر تا صد
- مجموعه آموزشهای دروس علوم و مهندسی کامپیوتر
- ایجاد سرویس در اپلیکیشن فلاتر — از صفر تا صد
- گوگل فلاتر (Flutter) از صفر تا صد — ساخت اپلیکیشن به کمک ویجت
==