onMeasure(): چیست، حالت‌های MeasureSpec و بازتعریف متد

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

onMeasure() — یک متد protected از کلاس android.view.View است که توسط سیستم اندروید برای تعیین ابعاد View فراخوانی می‌شود. سیستم دو شیء MeasureSpec را به متد ارسال می‌کند که هر کدام شامل حالت اندازه‌گیری (EXACTLY، AT_MOST یا UNSPECIFIED) و اندازه پیشنهادی توسط ظرف والد است. بر اساس Android Developers Documentation (2026)، بازتعریف onMeasure با پردازش صحیح MeasureSpec یک مرحله اجباری برای همه Viewها و ViewGroupهای سفارشی است که نیاز به کنترل دقیق بر ابعاد دارند.

نکات کلیدی

  • onMeasure(int widthMeasureSpec, int heightMeasureSpec) — متد View برای اندازه‌گیری ابعاد با ارسال MeasureSpec از والد
  • MeasureSpec — یک مقدار 32 بیتی که حالت اندازه‌گیری (UNSPECIFIED, EXACTLY, AT_MOST) و اندازه را کدگذاری می‌کند
  • setMeasuredDimension(int w, int h) — فراخوانی اجباری درون onMeasure که ابعاد نهایی View را ثبت می‌کند
  • الگوریتم دو مرحله‌ای اندازه‌گیری: والد فرزندان را اندازه می‌گیرد، سپس فرزندان ابعاد خود را گزارش می‌دهند و والد تصمیم نهایی را می‌گیرد
  • measureChildWithMargins — متد کمکی برای اندازه‌گیری Viewهای فرزند در ViewGroupهای سفارشی

onMeasure() چیست؟

onMeasure(int widthMeasureSpec, int heightMeasureSpec) — این متد کلاس View است که سیستم اندروید برای تعیین عرض و ارتفاع نمایش فراخوانی می‌کند. توسعه‌دهنده این متد را بازتعریف می‌کند تا مشخص کند View بر اساس محدودیت‌های ارسال شده در MeasureSpec چه اندازه‌ای باید داشته باشد. بدون بازتعریف صحیح onMeasure، View سفارشی ممکن است به درستی نمایش داده نشود یا اصلاً نمایش داده نشود.

سیستم onMeasure را در مرحله measure چرخه حیات View فراخوانی می‌کند که قبل از مراحل layout (onLayout) و draw (onDraw) قرار دارد. اگر View onMeasure را بازتعریف نکند، از پیاده‌سازی سوپرکلاس استفاده می‌شود که ابعاد پیش‌فرض را بر اساس background drawable یا layout_params تنظیم می‌کند. فراخوانی super.onMeasure(widthMeasureSpec, heightMeasureSpec) فقط برای زیرکلاس‌های استاندارد View مانند TextView یا ImageView کار می‌کند.

نیاز کلیدی برای onMeasure — فراخوانی setMeasuredDimension(int, int) باید حتماً در انتهای متد وجود داشته باشد. اگر این فراخوانی وجود نداشته باشد، سیستم استثنای IllegalStateException را با پیامی مبنی بر اینکه View ابعاد اندازه‌گیری شده را ثبت نکرده است، تولید می‌کند. ابعاد نهایی پس از اتمام مرحله measure از طریق getterهای getMeasuredWidth() و getMeasuredHeight() در دسترس می‌شوند.

حالت‌های MeasureSpec: سه مقدار کلیدی

MeasureSpec — یک عدد صحیح 32 بیتی است که 2 بیت بالایی حالت اندازه‌گیری و 30 بیت پایینی اندازه را کدگذاری می‌کنند. حالت مشخص می‌کند که View تا چه حد در انتخاب اندازه خود آزاد است. اندروید سه حالت ارائه می‌دهد: EXACTLY، AT_MOST و UNSPECIFIED. هر حالت منطق پردازش متفاوتی را در onMeasure دیکته می‌کند.

حالت MeasureSpecمقداررفتار
EXACTLYوالد اندازه دقیق را مشخص کرده استView باید دقیقاً در اندازه ارسال شده قرار گیرد، اگر نمی‌خواهد از مرزها خارج شود
AT_MOSTوالد حداکثر اندازه را تعیین کرده استView می‌تواند هر اندازه‌ای از 0 تا حداکثر ارسال شده را انتخاب کند
UNSPECIFIEDوالد محدودیتی اعمال نمی‌کندView می‌تواند هر اندازه دلخواهی را بدون محدودیت upper انتخاب کند

برای استخراج حالت و اندازه از MeasureSpec از متدهای استاتیک کلاس MeasureSpec استفاده می‌شود: MeasureSpec.getMode(int) یکی از سه حالت را برمی‌گرداند (EXACTLY, AT_MOST, UNSPECIFIED)، و MeasureSpec.getSize(int) اندازه عددی را بر حسب پیکسل برمی‌گرداند. برای ایجاد MeasureSpec شخصی از MeasureSpec.makeMeasureSpec(int size, int mode) استفاده می‌شود. این سه متد همه سناریوهای کار با ابعاد در onMeasure را پوشش می‌دهند.

منطق معمول پردازش MeasureSpec

الگوی استاندارد پردازش MeasureSpec: اگر حالت EXACTLY است — از اندازه ارسال شده به عنوان نهایی استفاده کنید؛ اگر AT_MOST است — حداقل اندازه دلخواه (محتوای View) و حداکثر ارسال شده را انتخاب کنید؛ اگر UNSPECIFIED است — از اندازه دلخواه View بدون محدودیت استفاده کنید. این الگو رفتار صحیح را تحت هر محدودیت والدی تضمین می‌کند.

kotlin
override fun onMeasure(widthMeasureSpec: Int,
                       heightMeasureSpec: Int) {
    val desiredWidth = 200
    val desiredHeight = 100

    val widthMode = MeasureSpec.getMode(widthMeasureSpec)
    val widthSize = MeasureSpec.getSize(widthMeasureSpec)
    val heightMode = MeasureSpec.getMode(heightMeasureSpec)
    val heightSize = MeasureSpec.getSize(heightMeasureSpec)

    val width = when (widthMode) {
        MeasureSpec.EXACTLY -> widthSize
        MeasureSpec.AT_MOST -> minOf(desiredWidth, widthSize)
        else -> desiredWidth
    }
    val height = when (heightMode) {
        MeasureSpec.EXACTLY -> heightSize
        MeasureSpec.AT_MOST -> minOf(desiredHeight, heightSize)
        else -> desiredHeight
    }
    setMeasuredDimension(width, height)
}

استفاده از resolveSize

برای ساده‌سازی منطق استاندارد، اندروید متد resolveSizeAndState را ارائه می‌دهد که اندازه دلخواه، MeasureSpec را می‌گیرد و اندازه نهایی را با حالت صحیح برمی‌گرداند. این متد الگوی توصیف شده در بالا را در یک خط کد پیاده‌سازی می‌کند. همچنین تابع resolveSize(int size, int measureSpec) در دسترس است که اندازه خالص را بدون بیت‌های حالت برمی‌گرداند.

الگوریتم اندازه‌گیری دو مرحله‌ای

اندروید از الگوریتم اندازه‌گیری دو مرحله‌ای (two-pass measure) استفاده می‌کند که تضمین می‌کند هر View در سلسله‌مراتب ابعاد صحیح را با در نظر گرفتن محدودیت‌های والد و ترجیحات عناصر فرزند دریافت می‌کند. در مرحله اول، والد MeasureSpec را با محدودیت‌ها به Viewهای فرزند ارسال می‌کند و Viewهای فرزند ابعاد دلخواه خود را محاسبه می‌کنند. در مرحله دوم، والد تصمیم نهایی را درباره ابعاد می‌گیرد.

برای ViewGroup فرآیند اندازه‌گیری پیچیده‌تر است: والد باید ابتدا همه عناصر فرزند خود را اندازه‌گیری کند، سپس اندازه خود را بر اساس ابعاد آنها تعیین کند. فراخوانی measureChildren(int widthMeasureSpec, int heightMeasureSpec) روی همه Viewهای فرزند پیمایش می‌کند و برای هر کدام measure(child, childWidthSpec, childHeightSpec) را فراخوانی می‌کند. پس از اندازه‌گیری همه عناصر فرزند، ViewGroup setMeasuredDimension را با ابعاد خود فراخوانی می‌کند.

نکته مهم: متد measure (عمومی، final) قابل بازتعریف نیست — در عوض onMeasure بازتعریف می‌شود. این تضمین می‌کند که سیستم می‌تواند اقدامات خدماتی را قبل و بعد از فراخوانی onMeasure انجام دهد، مانند بررسی تغییر ابعاد و محاسبه ناحیه dirty برای ترسیم بعدی. اگر View ابعاد ثابتی دارد، بازتعریف onMeasure ممکن است لازم نباشد.

پرچم MEASURED_SIZE_STATE

MeasureSpec نه تنها اندازه و حالت، بلکه بیت‌های حالت (state bits) را نیز شامل می‌شود که از طریق MeasureSpec.getMode() قابل دسترسی هستند. پس از فراخوانی setMeasuredDimension، حالت بخشی از ابعاد اندازه‌گیری شده View می‌شود و می‌تواند از طریق getMeasuredState() بررسی شود. این در ScrollView و سایر ظروف قابل پیمایش برای انتقال صحیح محدودیت‌ها به فرزندان استفاده می‌شود.

مثال بازتعریف onMeasure در Kotlin

یک مثال عملی از ایجاد View سفارشی با بازتعریف onMeasure برای نمایش مربعی را در نظر بگیرید. کلاس SquareView از View ارث‌بری می‌کند و تضمین می‌کند که عرض و ارتفاع بدون توجه به MeasureSpecهای ارسال شده همیشه برابر هستند. در onMeasure ضلع کوچکتر تعیین می‌شود و اندازه مربعی تنظیم می‌گردد.

kotlin
class SquareView(context: Context)
    : View(context) {

    override fun onMeasure(widthMeasureSpec: Int,
                       heightMeasureSpec: Int) {
        val widthSize =
            MeasureSpec.getSize(widthMeasureSpec)
        val heightSize =
            MeasureSpec.getSize(heightMeasureSpec)
        val size = minOf(widthSize, heightSize)
        setMeasuredDimension(size, size)
    }
}

ViewGroup سفارشی با اندازه‌گیری عناصر فرزند

ViewGroup به منطق پیچیده‌تری در onMeasure نیاز دارد، زیرا ابتدا عناصر فرزند اندازه‌گیری می‌شوند، سپس اندازه خود ViewGroup تعیین می‌گردد. مثال CascadeLayout عناصر فرزند را به صورت آبشاری با فاصله مرتب می‌کند. پس از اندازه‌گیری همه عناصر فرزند از طریق measureChildWithMargins، عرض و ارتفاع کل محاسبه می‌شوند.

kotlin
class CascadeLayout(context: Context)
    : ViewGroup(context) {

    private val cascadeOffset = 40

    override fun onMeasure(widthMeasureSpec: Int,
                       heightMeasureSpec: Int) {
        var maxWidth = 0
        var totalHeight = 0
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            measureChildWithMargins(child,
                widthMeasureSpec,
                cascadeOffset * i,
                heightMeasureSpec, 0)
            maxWidth = maxOf(maxWidth,
                child.measuredWidth +
                cascadeOffset * i)
            totalHeight += child.measuredHeight
        }
        setMeasuredDimension(
            resolveSize(maxWidth, widthMeasureSpec),
            resolveSize(totalHeight, heightMeasureSpec))
    }

    override fun generateLayoutParams(attrs: AttributeSet?)
        : LayoutParams = MarginLayoutParams(context, attrs)

    override fun onLayout(changed: Boolean,
                       l: Int, t: Int,
                       r: Int, b: Int) {
        var top = t
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            val left = l + cascadeOffset * i
            child.layout(left, top,
                left + child.measuredWidth,
                top + child.measuredHeight)
            top += child.measuredHeight
        }
    }
}

اشتباهات رایج در بازتعریف onMeasure

عدم فراخوانی setMeasuredDimension — رایج‌ترین اشتباه است. اگر توسعه‌دهنده onMeasure را بازتعریف کرده باشد اما setMeasuredDimension را فراخوانی نکرده باشد، برنامه با IllegalStateException سقوط می‌کند. این به خصوص زمانی رخ می‌دهد که در متد شاخه‌های شرطی وجود داشته باشد و در یکی از شاخه‌ها فراخوانی وجود نداشته باشد. هر شاخه کد در onMeasure باید با فراخوانی setMeasuredDimension پایان یابد.

نادیده گرفتن حالت AT_MOST — دومین اشتباه رایج. اگر View در حالت AT_MOST همیشه از اندازه ارسال شده به جای محاسبه بر اساس محتوا استفاده کند، ظرف والد نمی‌تواند فضا را به درستی توزیع کند. مثلاً TextView در AT_MOST باید عرض متن را محاسبه کرده و از حداقل عرض دلخواه و ارسال شده استفاده کند. نادیده گرفتن AT_MOST باعث می‌شود View حتی با محتوای کوچک تمام فضای موجود را اشغال کند.

ایجاد اشیاء درون onMeasure — یک اشتباه کلاسیک عملکردی. از آنجایی که onMeasure ممکن است多次 فراخوانی شود (در هر درخواست layout)، ایجاد اشیاء (Paint, Rect, String) درون این متد حافظه را آلوده کرده و جمع‌آوری زباله را تحریک می‌کند. همه اشیاء باید یک بار در سازنده View ایجاد شوند و در onMeasure فقط منطق محاسبه ابعاد اجرا شود. همین قانون برای onDraw و onLayout نیز صدق می‌کند.

اندازه‌گیری Viewهای فرزند در ViewGroup

measureChildWithMargins — یک متد protected از ViewGroup است که یک View فرزند را با در نظر گرفتن MarginLayoutParams آن اندازه‌گیری می‌کند. این متد MeasureSpec والد و جابجایی‌های انباشته شده در عرض و ارتفاع را می‌گیرد. به طور خودکار MeasureSpec را برای View فرزند با کم کردن paddingهای والد و margin فرزند تصحیح می‌کند و MeasureSpec تصحیح شده را به child.measure() ارسال می‌کند.

برای منطق اندازه‌گیری پیشرفته، ViewGroup می‌تواند measureChild(View child, int parentWidthSpec, int parentHeightSpec) را بازتعریف کند یا مستقیماً با MeasureSpec برای هر عنصر فرزند کار کند. مثلاً LinearLayout در onMeasure روی همه Viewهای فرزند پیمایش می‌کند، هر کدام را با در نظر گرفتن layout_weight آن اندازه می‌گیرد و فضای باقیمانده را به نسبت توزیع می‌کند. این رویکرد امکان پیاده‌سازی الگوریتم‌های چیدمان دلخواه را فراهم می‌کند.

ذخیره نتایج اندازه‌گیری از طریق مکانیزم کش اندازه‌گیری (measure cache) از طریق پرچم setMeasureWithLargestChildEnabled در برخی ViewGroupها در دسترس است. اما در بیشتر موارد onMeasure با هر تغییر layout دوباره فراخوانی می‌شود و کش اعمال نمی‌شود. در ViewGroupهای سفارشی توصیه می‌شود محاسبات را در onMeasure به حداقل برسانید نه اینکه به کش اعتماد کنید.

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

آیا بازتعریف onMeasure برای View سفارشی الزامی است؟

بله، اگر View سفارشی مستقیماً از کلاس View ارث‌بری کند. اگر ارث‌بری از TextView، ImageView یا Button با ابعاد استاندارد آنها باشد، onMeasure نیازی به بازتعریف ندارد. برای ViewGroup بازتعریف onMeasure همیشه الزامی است — در غیر این صورت عناصر فرزند به درستی اندازه‌گیری نخواهند شد.

اگر setMeasuredDimension فراخوانی نشود چه اتفاقی می‌افتد؟

سیستم اندروید استثنای IllegalStateException را با متن «The View did not call setMeasuredDimension» پرتاب می‌کند. این استثنا در متد measure() پس از اتمام onMeasure رخ می‌دهد اگر ابعاد نهایی صفر باقی مانده باشند. استثنا برنامه را خراب می‌کند اگر از طریق try-catch مدیریت نشود.

تفاوت getWidth و getMeasuredWidth چیست؟

getMeasuredWidth() اندازه تنظیم شده در onMeasure (مرحله اندازه‌گیری) را برمی‌گرداند. getWidth() اندازه واقعی را برمی‌گرداند که View در onLayout پس از در نظر گرفتن همه تصحیحات موقعیت‌یابی دریافت کرده است. برای بیشتر Viewها این مقادیر یکسان هستند، اما در ViewGroupهای سفارشی ممکن است متفاوت باشند.

آیا می‌توان در onMeasure انیمیشن‌ها یا وضعیت View را تغییر داد؟

خیر، onMeasure صرفاً برای محاسبه ابعاد طراحی شده است. تغییر وضعیت، راه‌اندازی انیمیشن‌ها، کار با شبکه یا به‌روزرسانی داده‌ها در این متد معماری اندروید را نقض می‌کند و می‌تواند منجر به فراخوانی‌های بازگشتی measure شود، زیرا تغییر وضعیت ممکن است requestLayout را فعال کند.

onMeasure چگونه با ConstraintLayout تعامل دارد؟

ConstraintLayout به طور مستقل اندازه‌گیری عناصر فرزند را بر اساس محدودیت‌های داده شده مدیریت می‌کند. اگر View سفارشی درون ConstraintLayout onMeasure را بازتعریف می‌کند، باید MeasureSpec ارسال شده از ConstraintLayout را به درستی پردازش کند، در غیر این صورت محدودیت‌ها ممکن است کار نکنند. ConstraintLayout از الگوریتم دو مرحله‌ای با WidgetContainer خود برای محاسبه استفاده می‌کند.

خلاصه

  • onMeasure() — متد View برای تعیین ابعاد که توسط سیستم اندروید در مرحله measure فراخوانی می‌شود
  • MeasureSpec حالت اندازه‌گیری (EXACTLY, AT_MOST, UNSPECIFIED) و اندازه ارسالی از والد را کدگذاری می‌کند
  • setMeasuredDimension — فراخوانی اجباری در انتهای onMeasure که ابعاد نهایی را ثبت می‌کند
  • resolveSize — متد کمکی که منطق استاندارد پردازش MeasureSpec را در یک خط پیاده‌سازی می‌کند
  • الگوریتم دو مرحله‌ای اندازه‌گیری صحیح را در سلسله‌مراتب والد-عناصر فرزند تضمین می‌کند
  • measureChildWithMargins برای اندازه‌گیری Viewهای فرزند در ViewGroupهای سفارشی با در نظر گرفتن فاصله‌ها استفاده می‌شود
  • ایجاد اشیاء درون onMeasure به دلیل خطر GC و افت فریم به شدت توصیه نمی‌شود

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

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

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

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