TextMeasurer: چیست و اندازه‌گیری متن در Compose چگونه کار می‌کند

نویسنده: IT Sectr منتشر شده: 2026-07-25 زمان مطالعه: 8 دقیقه

TextMeasurer — APIای از Jetpack Compose است که برای اندازه‌گیری ابعاد متن قبل از رندر واقعی روی صفحه طراحی شده است. برخلاف رویکرد کلاسیک از طریق Paint.measureText در Android View، TextMeasurer روشی بومی Compose برای به‌دست‌آوردن عرض، ارتفاع و معیارهای خطوط با در نظر گرفتن سبک و محدودیت‌ها ارائه می‌دهد. طبق مستندات Android Developers Documentation (2025)، TextMeasurer به‌طور فعال در کامپوننت‌های Layout سفارشی، عنوان‌های متحرک و عناصر UI تطبیقی استفاده می‌شود، جایی که اندازه متن بر موقعیت‌یابی سایر عناصر تأثیر می‌گذارد.

نکات اصلی

  • TextMeasurer — API Compose برای اندازه‌گیری متن قبل از رندر.
  • متود measure() TextLayoutResult را با معیارهای خطوط و کاراکترها برمی‌گرداند.
  • API سبک، فونت، اندازه و محدودیت‌های کانتینر را بر حسب پیکسل در نظر می‌گیرد.
  • TextMeasurer از اندازه‌گیری یک خط و متن چندخطی پشتیبانی می‌کند.
  • نتیجه اندازه‌گیری برای چیدمان سفارشی و انیمیشن متن استفاده می‌شود.

TextMeasurer در Jetpack Compose چیست؟

TextMeasurer — یک رابط تابعی از بسته androidx.compose.ui.text است که امکان اندازه‌گیری متن در Compose را بدون رندر مستقیم روی صفحه فراهم می‌کند. این بخشی از موتور متنی Compose است و در سطح Paragraph API کار می‌کند که از Skia برای رندر استفاده می‌کند.

هدف اصلی TextMeasurer اندازه‌گیری اولیه است. در Compose، اندازه متن معمولاً پس از قرار گرفتن Composable در Layout تعیین می‌شود. اما سناریوهایی وجود دارد که باید عرض متن را قبل از ترکیب‌بندی بدانید: مثلاً برای تصمیم‌گیری درباره جا شدن خط در کانتینر، یا محاسبه اندازه دکمه بر اساس طول متن.

TextMeasurer این مشکل را با ارائه متود measure() حل می‌کند که TextMeasureRequest (متن، سبک، محدودیت‌ها) را دریافت کرده و TextLayoutResult (معیارها، خطوط، کاراکترها) را برمی‌گرداند. این به توسعه‌دهنده امکان می‌دهد تمام داده‌های مربوط به متن را به صورت همزمان و بدون تأخیر یک فریم دریافت کند.

زمانی از TextMeasurer استفاده کنید که اندازه متن بر اندازه کامپوننت والد تأثیر می‌گذارد، هنگام ایجاد انتقال‌های متنی متحرک، یا هنگام پیاده‌سازی Layoutهای سفارشی که متن در کنار عناصر دیگر قرار دارد.

TextMeasurer چگونه کار می‌کند

API TextMeasurer از دو موجودیت کلیدی تشکیل شده است: rememberTextMeasurer() (ایجاد نمونه) و measure()(شروع اندازه‌گیری). نمونه یک بار برای کل چرخه حیات کامپوننت ایجاد شده و برای همه اندازه‌گیری‌ها مجدداً استفاده می‌شود.

kotlin
val textMeasurer = rememberTextMeasurer()

val result = textMeasurer.measure(
    text = "Hello, Compose!",
    style = MaterialTheme.typography.bodyLarge,
    constraints = Constraints(maxWidth = 200, maxHeight = 50)
)

// عرض نتیجه، ارتفاع نتیجه، تعداد خطوط

پارامتر constraints حداکثر عرض و ارتفاعی را تعیین می‌کند که متن باید در آن جا شود. اگر متن در عرض جا نشود، به خط بعدی منتقل می‌شود. نتیجه اندازه‌گیری شامل TextLayoutResult است که در آن می‌توان تعداد خطوط، موقعیت هر کاراکتر، عرض هر خط و اندازه کل متن را به‌دست آورد.

TextMeasurer به صورت همزمان در رشته UI کار می‌کند و باعث بازترکیب‌بندی نمی‌شود. با این حال، اندازه‌گیری مکرر متن با سبک‌های مختلف می‌تواند بار ایجاد کند — بنابراین نمونه از طریق remember ایجاد شده و مجدداً استفاده می‌شود.

اندازه‌گیری یک خط متن

ساده‌ترین سناریو — اندازه‌گیری عرض متن تک‌خطی برای تعیین اندازه کانتینر والد. مثلاً برای دکمه‌ای که عرض آن به طول برچسب بستگی دارد.

kotlin
val textMeasurer = rememberTextMeasurer()
var label by remember { mutableStateOf("Submit") }

val textResult = remember(label) {
    textMeasurer.measure(
        text = label,
        style = MaterialTheme.typography.labelLarge,
        constraints = Constraints(maxWidth = 1000, maxHeight = 100)
    )
}

Box(
    modifier = Modifier
        .width(textResult.size.width.dp)
        .height(40.dp)
        .background(Color.Blue)
        .clickable { onClick() }
) {
    Text(text = label, style = MaterialTheme.typography.labelLarge)
}

در این مثال، remember(label) تضمین می‌کند که اندازه‌گیری فقط هنگام تغییر متن راه‌اندازی مجدد می‌شود. Constraints با maxWidth بزرگ (1000) تنظیم شده تا از انتقال خط جلوگیری شود — محدودیت واقعی برابر عرض متن خواهد بود. عرض Box برابر عرض اندازه‌گیری‌شده متن تنظیم می‌شود که اثر «دکمه متناسب با اندازه متن» را ایجاد می‌کند.

توجه: عرض حاصل از نتیجه باید از طریق .dp به dp تبدیل شود، زیرا TextMeasurer اندازه‌ها را بر حسب پیکسل برمی‌گرداند، در حالی که Modifier.width منتظر Dp است. برای دقت از density و LocalDensity.current استفاده کنید.

اندازه‌گیری متن چندخطی

برای متن چندخطی، TextMeasurer نه تنها ارتفاع کل، بلکه تعداد خطوط و موقعیت هر کاراکتر را در داخل پاراگراف تعیین می‌کند. این برای ایجاد فیلدهای متنی سفارشی، چت‌ها یا ویرایشگرها ضروری است.

ویژگی‌های اصلی TextLayoutResult برای متن چندخطی: lineCount (تعداد خطوط)، getLineTop(index) (مختصات Y شروع خط)، getLineBottom(index) (مختصات Y پایان)، getLineWidth(index) (عرض خط). با ترکیب آنها می‌توان عناصر رابط را دقیقاً نسبت به خطوط متن قرار داد.

kotlin
val textMeasurer = rememberTextMeasurer()
val result = textMeasurer.measure(
    text = "Long multiline text that needs to be measured before layout",
    style = MaterialTheme.typography.bodyMedium,
    constraints = Constraints(maxWidth = 150, maxHeight = 300)
)

val linesCount = result.lineCount
val firstLineWidth = result.getLineWidth(0)
val totalHeight = result.size.height

متود getLineWidth(lineIndex) عرض خط مشخص‌شده را بر حسب پیکسل برمی‌گرداند. اگر خط به مرز کانتینر نرسد، ممکن است از maxWidth کمتر باشد. با دانستن عرض هر خط، می‌توان اثر justified را به صورت دستی پیاده‌سازی کرد یا عناصر inline را در کنار خطوط کوتاه قرار داد.

برای به‌دست‌آوردن موقعیت یک کاراکتر خاص از getBoundingBox(offset) استفاده کنید که Rect با مختصات left، top، right، bottom برمی‌گرداند. این برای قرار دادن مکان‌نما در فیلدهای متنی سفارشی یا برجسته‌سازی محدوده متن با انیمیشن مفید است.

TextMeasurer در کامپوننت‌های سفارشی

قوی‌ترین سناریوی استفاده از TextMeasurer — کامپوننت‌های Layout سفارشی است، جایی که متن در کنار عناصر دیگر قرار دارد و اندازه‌های آنها به هم وابسته است. مثلاً ویجت «آیکون + متن» که در آن آیکون باید نسبت به اولین خط متن وسط‌چین شود، نه کل بلوک.

در Row استاندارد، آیکون بر اساس ارتفاع کل Row وسط‌چین می‌شود که وقتی متن 3 خط و آیکون یک خط است، غیرطبیعی به نظر می‌رسد. با TextMeasurer می‌توان اولین خط را اندازه‌گیری کرد و آیکون را دقیقاً نسبت به آن تراز کرد.

kotlin
val textMeasurer = rememberTextMeasurer()

Layout(
    content = {
        Icon(imageVector = Icons.Default.Star, contentDescription = null)
        Text(text = "متن چندخطی اینجا")
    },
    measurePolicy = { measurables, constraints ->
        val textMeasurable = measurables[1]
        val textPlaceable = textMeasurable.measure(constraints)
        val iconPlaceable = measurables[0].measure(constraints)

        val firstLineHeight = textMeasurer.measure(
            text = "Sample",
            style = MaterialTheme.typography.bodyMedium,
            constraints = Constraints(maxWidth = 2000, maxHeight = 100)
        ).size.height

        layout(width = textPlaceable.width + iconPlaceable.width,
               height = maxOf(textPlaceable.height, iconPlaceable.height)) {
            iconPlaceable.placeRelative(0, (firstLineHeight - iconPlaceable.height) / 2)
            textPlaceable.placeRelative(iconPlaceable.width, 0)
        }
    }
)

در این مثال، اندازه‌گیری اولین خط از طریق TextMeasurer موقعیت آیکون را تعیین می‌کند. بدون TextMeasurer، آیکون بر اساس تمام ارتفاع متن چندخطی وسط‌چین می‌شد که از نظر بصری نادرست است. این الگو به طور گسترده در چت‌ها، لیست مخاطبین و کارت‌های اطلاعاتی استفاده می‌شود.

نکات عملکرد

TextMeasurer — API همزمانی است که در رشته UI کار می‌کند. با استفاده بیش از حد، می‌تواند باعث افت فریم شود، به خصوص هنگام اندازه‌گیری حجم زیاد متن در دستگاه‌های ضعیف.

قانون اول — نتیجه را کش کنید. همیشه فراخوانی measure() را با وابستگی به متن و سبک در remember بپیچید. هرگز measure() را داخل CompositionLocal یا در مسیرهای داغ بازترکیب فراخوانی نکنید.

قانون دوم — maxWidth و maxHeight را محدود کنید. اگر maxWidth = Constraints.Infinity تنظیم کنید، TextMeasurer نمی‌تواند خطوط را منتقل کند و متن از صفحه خارج می‌شود. همیشه محدودیت‌های واقع‌گرایانه تنظیم کنید، به خصوص برای متن چندخطی.

قانون سوم — از اندازه‌گیری مکرر با پارامترهای یکسان خودداری کنید. اگر متن و سبک تغییر نکرده‌اند، نتیجه measure() یکسان خواهد بود. اندازه‌گیری را با Deferred یا LaunchedEffect برای سناریوهای ناهمزمان ترکیب کنید.

سوالات متداول

TextMeasurer در Compose چه تفاوتی با onSizeChanged دارد؟

TextMeasurer متن را قبل از رندر اندازه‌گیری می‌کند، در حالی که onSizeChanged بعد از آن. TextMeasurer زمانی نیاز است که اندازه متن بر layout والد تأثیر می‌گذارد؛ onSizeChanged برای منطق واکنشی پس از ترکیب مناسب است.

آیا می‌توان SpannableString را از طریق TextMeasurer اندازه‌گیری کرد؟

بله، TextMeasurer.measure() AnnotatedString را می‌پذیرد که شامل SpannableString با سبک‌های inline، لینک‌ها و رنگ‌ها است. همه spanها در اندازه‌گیری در نظر گرفته می‌شوند.

چرا TextMeasurer اندازه را بر حسب پیکسل برمی‌گرداند نه dp؟

TextMeasurer در سطح رندر Skia کار می‌کند، جایی که همه اندازه‌ها بر حسب پیکسل هستند. از طریق LocalDensity.current تبدیل کنید: density.run { width.toDp() }.

چگونه متن را قبل از بارگذاری فونت اندازه‌گیری کنیم؟

TextMeasurer تا بارگذاری فونت سفارشی از FontFamily.Default استفاده می‌کند. برای اندازه‌گیری دقیق، بارگذاری را از طریق FontResource منتظر بمانید و از async/await استفاده کنید.

آیا TextMeasurer بر عملکرد لیست‌های LazyColumn تأثیر می‌گذارد؟

اندازه‌گیری متن در هر عنصر LazyColumn می‌تواند FPS را در دستگاه‌های ضعیف کاهش دهد. بهینه‌سازی: نتیجه را از طریق remember کش کنید و اندازه متن را محدود کنید.

خلاصه

  • TextMeasurer — API Compose برای اندازه‌گیری متن قبل از رندر، TextLayoutResult را برمی‌گرداند.
  • متود measure() متن، سبک و Constraints را بر حسب پیکسل دریافت می‌کند.
  • نمونه از طریق rememberTextMeasurer() ایجاد شده و مجدداً استفاده می‌شود.
  • برای متن تک‌خطی، نتیجه برای عرض تطبیقی کانتینر استفاده می‌شود.
  • برای متن چندخطی — lineCount, getLineWidth() و getBoundingBox() تصویر کاملی ارائه می‌دهند.
  • در Layoutهای سفارشی، TextMeasurer امکان تراز کردن عناصر را نسبت به اولین خط متن فراهم می‌کند.
  • نتیجه را از طریق remember کش کنید و از اندازه‌گیری در مسیرهای داغ بازترکیب خودداری کنید.

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید