اصلاح کننده های سفارشی ایجاد کنید

Compose اصلاح‌کننده‌های زیادی را برای رفتارهای رایج، از همان ابتدا ارائه می‌دهد، اما شما می‌توانید اصلاح‌کننده‌های سفارشی خودتان را نیز ایجاد کنید.

اصلاح‌کننده‌ها چندین بخش دارند:

  • یک کارخانه اصلاح کننده
    • این یک تابع افزونه (extension function) روی Modifier است که یک API اصطلاحی برای modifier شما فراهم می‌کند و امکان اتصال زنجیره‌ای modifierها به یکدیگر را فراهم می‌کند. modifier factory عناصر modifier مورد استفاده Compose برای تغییر رابط کاربری شما را تولید می‌کند.
  • یک عنصر اصلاح‌کننده
    • اینجاست که می‌توانید رفتار اصلاح‌کننده خود را پیاده‌سازی کنید.

بسته به عملکرد مورد نیاز، روش‌های متعددی برای پیاده‌سازی یک اصلاح‌کننده سفارشی وجود دارد. اغلب، ساده‌ترین راه برای پیاده‌سازی یک اصلاح‌کننده سفارشی، پیاده‌سازی یک کارخانه اصلاح‌کننده سفارشی است که سایر کارخانه‌های اصلاح‌کننده از قبل تعریف‌شده را ترکیب می‌کند. اگر به رفتار سفارشی بیشتری نیاز دارید، عنصر اصلاح‌کننده را با استفاده از APIهای Modifier.Node پیاده‌سازی کنید که سطح پایین‌تری دارند اما انعطاف‌پذیری بیشتری ارائه می‌دهند.

اصلاح‌کننده‌های موجود را به هم زنجیر کنید

اغلب می‌توان با استفاده از اصلاح‌کننده‌های موجود، اصلاح‌کننده‌های سفارشی ایجاد کرد. برای مثال، Modifier.clip() با استفاده از اصلاح‌کننده graphicsLayer پیاده‌سازی می‌شود. این استراتژی از عناصر اصلاح‌کننده موجود استفاده می‌کند و شما کارخانه اصلاح‌کننده سفارشی خود را ارائه می‌دهید.

قبل از پیاده‌سازی اصلاح‌کننده‌ی سفارشی خودتان، ببینید آیا می‌توانید از همان استراتژی استفاده کنید یا خیر.

fun Modifier.clip(shape: Shape) = graphicsLayer(shape = shape, clip = true)

یا اگر متوجه شدید که اغلب گروه یکسانی از توصیف‌کننده‌ها را تکرار می‌کنید، می‌توانید آنها را در توصیف‌کننده‌ی خودتان قرار دهید:

fun Modifier.myBackground(color: Color) = padding(16.dp)
    .clip(RoundedCornerShape(8.dp))
    .background(color)

با استفاده از یک کارخانه اصلاح کننده قابل ترکیب، یک اصلاح کننده سفارشی ایجاد کنید

شما همچنین می‌توانید با استفاده از یک تابع composable یک اصلاح‌کننده سفارشی ایجاد کنید تا مقادیر را به یک اصلاح‌کننده موجود منتقل کند. این به عنوان یک کارخانه اصلاح‌کننده composable شناخته می‌شود.

استفاده از یک کارخانه اصلاح‌کننده قابل ترکیب برای ایجاد یک اصلاح‌کننده، به شما امکان می‌دهد از APIهای سطح بالاتر ترکیب، مانند animate*AsState و سایر APIهای انیمیشن پشتیبانی‌شده با حالت ترکیب، استفاده کنید. برای مثال، قطعه کد زیر اصلاح‌کننده‌ای را نشان می‌دهد که هنگام فعال/غیرفعال کردن، تغییر آلفا را متحرک می‌کند:

@Composable
fun Modifier.fade(enable: Boolean): Modifier {
    val alpha by animateFloatAsState(if (enable) 0.5f else 1.0f)
    return this then Modifier.graphicsLayer { this.alpha = alpha }
}

اگر اصلاح‌کننده سفارشی شما یک روش راحت برای ارائه مقادیر پیش‌فرض از CompositionLocal است، ساده‌ترین راه برای پیاده‌سازی این، استفاده از یک کارخانه اصلاح‌کننده قابل ترکیب است:

@Composable
fun Modifier.fadedBackground(): Modifier {
    val color = LocalContentColor.current
    return this then Modifier.background(color.copy(alpha = 0.5f))
}

این روش دارای نکات و ظرایفی است که در بخش‌های بعدی به تفصیل به آنها اشاره خواهد شد.

مقادیر CompositionLocal در محل فراخوانی کارخانه اصلاح‌کننده (modifier factory) حل می‌شوند.

هنگام ایجاد یک اصلاح‌کننده سفارشی با استفاده از یک کارخانه اصلاح‌کننده قابل ترکیب، توابع محلی ترکیب، مقدار را از درخت ترکیب که در آن ایجاد شده‌اند می‌گیرند، نه اینکه استفاده شوند. این می‌تواند منجر به نتایج غیرمنتظره‌ای شود. به عنوان مثال، مثال اصلاح‌کننده محلی ترکیب که قبلاً ذکر شد را در نظر بگیرید که با استفاده از یک تابع قابل ترکیب، کمی متفاوت پیاده‌سازی شده است:

@Composable
fun Modifier.myBackground(): Modifier {
    val color = LocalContentColor.current
    return this then Modifier.background(color.copy(alpha = 0.5f))
}

@Composable
fun MyScreen() {
    CompositionLocalProvider(LocalContentColor provides Color.Green) {
        // Background modifier created with green background
        val backgroundModifier = Modifier.myBackground()

        // LocalContentColor updated to red
        CompositionLocalProvider(LocalContentColor provides Color.Red) {

            // Box will have green background, not red as expected.
            Box(modifier = backgroundModifier)
        }
    }
}

اگر انتظار ندارید که اصلاح‌کننده شما اینگونه کار کند، به جای آن از یک Modifier.Node سفارشی استفاده کنید، زیرا فایل‌های محلی ترکیب به درستی در محل استفاده حل می‌شوند و می‌توانند با خیال راحت جابجا شوند.

اصلاح‌کننده‌های تابع قابل ترکیب هرگز نادیده گرفته نمی‌شوند

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

اصلاح‌کننده‌های تابع ترکیبی باید درون یک تابع ترکیبی فراخوانی شوند

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

val extractedModifier = Modifier.background(Color.Red) // Hoisted to save allocations

@Composable
fun Modifier.composableModifier(): Modifier {
    val color = LocalContentColor.current.copy(alpha = 0.5f)
    return this then Modifier.background(color)
}

@Composable
fun MyComposable() {
    val composedModifier = Modifier.composableModifier() // Cannot be extracted any higher
}

پیاده‌سازی رفتار اصلاح‌کننده سفارشی با استفاده از Modifier.Node

Modifier.Node یک API سطح پایین‌تر برای ایجاد اصلاح‌کننده‌ها در Compose است. این همان API است که Compose اصلاح‌کننده‌های خود را در آن پیاده‌سازی می‌کند و کارآمدترین روش برای ایجاد اصلاح‌کننده‌های سفارشی است.

پیاده‌سازی یک اصلاح‌کننده سفارشی با استفاده از Modifier.Node

سه بخش برای پیاده‌سازی یک اصلاح‌کننده سفارشی با استفاده از Modifier.Node وجود دارد:

  • یک پیاده‌سازی Modifier.Node که منطق و وضعیت اصلاح‌کننده‌ی شما را در خود نگه می‌دارد.
  • یک ModifierNodeElement که نمونه‌های گره اصلاح‌کننده را ایجاد و به‌روزرسانی می‌کند.
  • یک کارخانه اصلاح‌کننده اختیاری، همانطور که قبلاً توضیح داده شد.

کلاس‌های ModifierNodeElement بدون وضعیت (stateless) هستند و در هر تغییر ترکیب، نمونه‌های جدیدی به آن‌ها اختصاص داده می‌شود، در حالی که کلاس‌های Modifier.Node می‌توانند دارای وضعیت (stateful) باشند و در چندین تغییر ترکیب باقی بمانند و حتی می‌توانند دوباره استفاده شوند.

بخش زیر هر بخش را شرح می‌دهد و مثالی از ساخت یک اصلاح‌کننده سفارشی برای رسم دایره را نشان می‌دهد.

Modifier.Node

پیاده‌سازی Modifier.Node (در این مثال، CircleNode ) عملکرد اصلاح‌کننده‌ی سفارشی شما را پیاده‌سازی می‌کند.

// Modifier.Node
private class CircleNode(var color: Color) : DrawModifierNode, Modifier.Node() {
    override fun ContentDrawScope.draw() {
        drawCircle(color)
    }
}

در این مثال، دایره را با رنگی که به تابع اصلاح‌کننده ارسال شده است، رسم می‌کند.

یک گره، Modifier.Node و همچنین صفر یا چند نوع گره را پیاده‌سازی می‌کند. انواع گره‌های مختلفی بر اساس عملکردی که اصلاح‌کننده شما نیاز دارد، وجود دارد. مثال قبلی باید بتواند رسم کند، بنابراین DrawModifierNode پیاده‌سازی می‌کند که به آن اجازه می‌دهد متد رسم را نادیده بگیرد.

انواع موجود به شرح زیر است:

گره

کاربرد

لینک نمونه

LayoutModifierNode

یک Modifier.Node که نحوه‌ی اندازه‌گیری و چیدمان محتوای پیچیده‌شده‌اش را تغییر می‌دهد.

نمونه

DrawModifierNode

یک Modifier.Node که به فضای طرح‌بندی (layout) رسم می‌شود.

نمونه

CompositionLocalConsumerModifierNode

پیاده‌سازی این رابط به Modifier.Node شما اجازه می‌دهد تا فایل‌های محلی ترکیب را بخواند.

نمونه

SemanticsModifierNode

یک Modifier.Node که کلید/مقدار معنایی را برای استفاده در آزمایش، دسترسی‌پذیری و موارد استفاده مشابه اضافه می‌کند.

نمونه

PointerInputModifierNode

یک Modifier.Node که PointerInputChanges را دریافت می‌کند.

نمونه

ParentDataModifierNode

یک Modifier.Node که داده‌ها را برای طرح‌بندی والد فراهم می‌کند.

نمونه

LayoutAwareModifierNode

یک Modifier.Node که فراخوانی‌های onMeasured و onPlaced را دریافت می‌کند.

نمونه

GlobalPositionAwareModifierNode

یک Modifier.Node که یک فراخوانی onGloballyPositioned با مختصات نهایی LayoutCoordinates طرح‌بندی، زمانی که موقعیت سراسری محتوا ممکن است تغییر کرده باشد، دریافت می‌کند.

نمونه

ObserverModifierNode

Modifier.Node هایی که ObserverNode را پیاده‌سازی می‌کنند، می‌توانند پیاده‌سازی خود از onObservedReadsChanged را ارائه دهند که در پاسخ به تغییرات در اشیاء snapshot خوانده شده در یک بلوک observeReads فراخوانی می‌شود.

نمونه

DelegatingNode

یک Modifier.Node که قادر است کار را به سایر نمونه‌های Modifier.Node واگذار کند.

این می‌تواند برای ترکیب چندین پیاده‌سازی گره در یک پیاده‌سازی مفید باشد.

نمونه

TraversableNode

به کلاس‌های Modifier.Node اجازه می‌دهد تا درخت گره را برای کلاس‌های هم نوع یا برای یک کلید خاص، به بالا/پایین پیمایش کنند.

نمونه

گره‌ها به طور خودکار هنگام فراخوانی تابع به‌روزرسانی روی عنصر مربوطه، نامعتبر می‌شوند. از آنجا که مثال ما یک DrawModifierNode است، هر بار که تابع به‌روزرسانی روی عنصر فراخوانی شود، گره یک ترسیم مجدد را آغاز می‌کند و رنگ آن به درستی به‌روزرسانی می‌شود. همانطور که در بخش «خروج از اعتبارسنجی خودکار گره» توضیح داده شده است، می‌توان از نامعتبرسازی خودکار صرف نظر کرد.

ModifierNodeElement

یک ModifierNodeElement یک کلاس تغییرناپذیر است که داده‌ها را برای ایجاد یا به‌روزرسانی اصلاح‌کننده سفارشی شما در خود نگه می‌دارد:

// ModifierNodeElement
private data class CircleElement(val color: Color) : ModifierNodeElement<CircleNode>() {
    override fun create() = CircleNode(color)

    override fun update(node: CircleNode) {
        node.color = color
    }
}

پیاده‌سازی‌های ModifierNodeElement باید متدهای زیر را بازنویسی کنند:

  1. create : این تابعی است که گره اصلاح‌کننده شما را نمونه‌سازی می‌کند. این تابع برای ایجاد گره هنگام اولین اعمال اصلاح‌کننده شما فراخوانی می‌شود. معمولاً این کار به ساخت گره و پیکربندی آن با پارامترهایی که به کارخانه اصلاح‌کننده ارسال شده‌اند، منجر می‌شود.
  2. update : این تابع هر زمان که این اصلاح‌کننده در همان نقطه‌ای که این گره از قبل وجود دارد، اما یک ویژگی تغییر کرده است، ارائه شود، فراخوانی می‌شود. این توسط متد equals کلاس تعیین می‌شود. گره اصلاح‌کننده‌ای که قبلاً ایجاد شده است، به عنوان پارامتر به فراخوانی update ارسال می‌شود. در این مرحله، باید ویژگی‌های گره‌ها را به‌روزرسانی کنید تا با پارامترهای به‌روزرسانی‌شده مطابقت داشته باشند. قابلیت استفاده مجدد از گره‌ها به این روش، کلید افزایش عملکرد Modifier.Node است. بنابراین، باید گره موجود را به‌روزرسانی کنید، نه اینکه یک گره جدید در متد update ایجاد کنید. در مثال دایره ما، رنگ گره به‌روزرسانی می‌شود.

علاوه بر این، پیاده‌سازی‌های ModifierNodeElement نیز باید equals و hashCode پیاده‌سازی کنند. update فقط در صورتی فراخوانی می‌شود که مقایسه equals با عنصر قبلی، مقدار false را برگرداند.

مثال قبلی از یک کلاس داده برای دستیابی به این هدف استفاده می‌کند. این متدها برای بررسی اینکه آیا یک گره نیاز به به‌روزرسانی دارد یا خیر، استفاده می‌شوند. اگر عنصر شما دارای ویژگی‌هایی است که در نیاز به به‌روزرسانی یک گره نقشی ندارند، یا اگر می‌خواهید به دلایل سازگاری باینری از کلاس‌های داده اجتناب کنید، می‌توانید به صورت دستی equals و hashCode پیاده‌سازی کنید، به عنوان مثال، عنصر اصلاح‌کننده padding .

کارخانه اصلاح کننده

این سطح API عمومی اصلاح‌کننده شماست. اکثر پیاده‌سازی‌ها عنصر اصلاح‌کننده را ایجاد کرده و آن را به زنجیره اصلاح‌کننده اضافه می‌کنند:

// Modifier factory
fun Modifier.circle(color: Color) = this then CircleElement(color)

مثال کامل

این سه بخش در کنار هم قرار می‌گیرند تا اصلاح‌کننده سفارشی برای رسم دایره با استفاده از APIهای Modifier.Node ایجاد شود:

// Modifier factory
fun Modifier.circle(color: Color) = this then CircleElement(color)

// ModifierNodeElement
private data class CircleElement(val color: Color) : ModifierNodeElement<CircleNode>() {
    override fun create() = CircleNode(color)

    override fun update(node: CircleNode) {
        node.color = color
    }
}

// Modifier.Node
private class CircleNode(var color: Color) : DrawModifierNode, Modifier.Node() {
    override fun ContentDrawScope.draw() {
        drawCircle(color)
    }
}

موقعیت‌های رایج با استفاده از Modifier.Node

هنگام ایجاد اصلاح‌کننده‌های سفارشی با Modifier.Node ، در اینجا برخی از موقعیت‌های رایجی که ممکن است با آنها مواجه شوید، آورده شده است.

پارامترهای صفر

اگر اصلاح‌کننده شما هیچ پارامتری نداشته باشد، هرگز نیازی به به‌روزرسانی ندارد و علاوه بر این، نیازی به کلاس داده بودن هم ندارد. در ادامه، نمونه‌ای از پیاده‌سازی یک اصلاح‌کننده که مقدار ثابتی از padding را به یک composable اعمال می‌کند، آورده شده است:

fun Modifier.fixedPadding() = this then FixedPaddingElement

data object FixedPaddingElement : ModifierNodeElement<FixedPaddingNode>() {
    override fun create() = FixedPaddingNode()
    override fun update(node: FixedPaddingNode) {}
}

class FixedPaddingNode : LayoutModifierNode, Modifier.Node() {
    private val PADDING = 16.dp

    override fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints
    ): MeasureResult {
        val paddingPx = PADDING.roundToPx()
        val horizontal = paddingPx * 2
        val vertical = paddingPx * 2

        val placeable = measurable.measure(constraints.offset(-horizontal, -vertical))

        val width = constraints.constrainWidth(placeable.width + horizontal)
        val height = constraints.constrainHeight(placeable.height + vertical)
        return layout(width, height) {
            placeable.place(paddingPx, paddingPx)
        }
    }
}

ترکیب مرجع محلی

اصلاح‌کننده‌های Modifier.Node به طور خودکار تغییرات اشیاء حالت Compose، مانند CompositionLocal ، را مشاهده نمی‌کنند. مزیت اصلاح‌کننده‌های Modifier.Node نسبت به اصلاح‌کننده‌هایی که فقط با یک کارخانه composable ایجاد می‌شوند این است که می‌توانند مقدار ترکیب محلی را از جایی که اصلاح‌کننده در درخت رابط کاربری شما استفاده می‌شود، بخوانند، نه جایی که اصلاح‌کننده تخصیص داده شده است، با استفاده از currentValueOf .

با این حال، نمونه‌های گره اصلاح‌کننده به طور خودکار تغییرات حالت را مشاهده نمی‌کنند. برای واکنش خودکار به تغییر محلی یک ترکیب، می‌توانید مقدار فعلی آن را در داخل یک محدوده بخوانید:

این مثال مقدار LocalContentColor را برای رسم یک پس‌زمینه بر اساس رنگ آن مشاهده می‌کند. از آنجایی که ContentDrawScope تغییرات snapshot را مشاهده می‌کند، با تغییر مقدار LocalContentColor ، این تابع به طور خودکار دوباره ترسیم می‌شود:

class BackgroundColorConsumerNode :
    Modifier.Node(),
    DrawModifierNode,
    CompositionLocalConsumerModifierNode {
    override fun ContentDrawScope.draw() {
        val currentColor = currentValueOf(LocalContentColor)
        drawRect(color = currentColor)
        drawContent()
    }
}

برای واکنش به تغییرات حالت خارج از محدوده و به‌روزرسانی خودکار اصلاح‌کننده، از یک ObserverModifierNode استفاده کنید.

برای مثال، Modifier.scrollable از این تکنیک برای مشاهده تغییرات در LocalDensity استفاده می‌کند. یک مثال ساده در مثال زیر نشان داده شده است:

class ScrollableNode :
    Modifier.Node(),
    ObserverModifierNode,
    CompositionLocalConsumerModifierNode {

    // Place holder fling behavior, we'll initialize it when the density is available.
    val defaultFlingBehavior = DefaultFlingBehavior(splineBasedDecay(UnityDensity))

    override fun onAttach() {
        updateDefaultFlingBehavior()
        observeReads { currentValueOf(LocalDensity) } // monitor change in Density
    }

    override fun onObservedReadsChanged() {
        // if density changes, update the default fling behavior.
        updateDefaultFlingBehavior()
    }

    private fun updateDefaultFlingBehavior() {
        val density = currentValueOf(LocalDensity)
        defaultFlingBehavior.flingDecay = splineBasedDecay(density)
    }
}

متحرک‌سازی یک اصلاح‌کننده

پیاده‌سازی‌های Modifier.Node به یک coroutineScope دسترسی دارند. این امکان استفاده از APIهای Compose Animatable را فراهم می‌کند. برای مثال، این قطعه کد CircleNode نشان داده شده قبلی را طوری تغییر می‌دهد که مرتباً محو شود:

class CircleNode(var color: Color) : Modifier.Node(), DrawModifierNode {
    private lateinit var alpha: Animatable<Float, AnimationVector1D>

    override fun ContentDrawScope.draw() {
        drawCircle(color = color, alpha = alpha.value)
        drawContent()
    }

    override fun onAttach() {
        alpha = Animatable(1f)
        coroutineScope.launch {
            alpha.animateTo(
                0f,
                infiniteRepeatable(tween(1000), RepeatMode.Reverse)
            ) {
            }
        }
    }
}

اشتراک‌گذاری وضعیت بین اصلاح‌کننده‌ها با استفاده از واگذاری اختیار

اصلاح‌کننده‌های Modifier.Node می‌توانند به گره‌های دیگر محول شوند. موارد استفاده زیادی برای این کار وجود دارد، مانند استخراج پیاده‌سازی‌های مشترک در اصلاح‌کننده‌های مختلف، اما همچنین می‌توان از آن برای به اشتراک گذاشتن حالت مشترک در بین اصلاح‌کننده‌ها استفاده کرد.

برای مثال، یک پیاده‌سازی اولیه از یک گره اصلاح‌کننده قابل کلیک که داده‌های تعامل را به اشتراک می‌گذارد:

class ClickableNode : DelegatingNode() {
    val interactionData = InteractionData()
    val focusableNode = delegate(
        FocusableNode(interactionData)
    )
    val indicationNode = delegate(
        IndicationNode(interactionData)
    )
}

انصراف از اعتبارسنجی خودکار گره

گره‌های Modifier.Node به طور خودکار زمانی که فراخوانی‌های ModifierNodeElement مربوط به آنها به‌روزرسانی می‌شود، نامعتبر می‌شوند. برای اصلاح‌کننده‌های پیچیده، ممکن است بخواهید این رفتار را غیرفعال کنید تا کنترل دقیق‌تری بر زمانی که اصلاح‌کننده شما فازها را نامعتبر می‌کند، داشته باشید.

این امر به ویژه در صورتی مفید است که اصلاح‌کننده سفارشی شما هم layout و هم draw را تغییر دهد. غیرفعال کردن اعتبارسنجی خودکار به شما این امکان را می‌دهد که وقتی فقط ویژگی‌های مرتبط با draw، مانند color ، تغییر می‌کنند، draw را نامعتبر کنید. این کار از نامعتبر شدن layout جلوگیری می‌کند و می‌تواند عملکرد اصلاح‌کننده شما را بهبود بخشد.

یک مثال فرضی از این مورد در مثال زیر با یک اصلاح‌کننده که دارای ویژگی‌های color ، size و onClick lambda است، نشان داده شده است. این اصلاح‌کننده فقط موارد مورد نیاز را نامعتبر می‌کند و از هرگونه نامعتبرسازی غیرضروری صرف نظر می‌کند:

class SampleInvalidatingNode(
    var color: Color,
    var size: IntSize,
    var onClick: () -> Unit
) : DelegatingNode(), LayoutModifierNode, DrawModifierNode {
    override val shouldAutoInvalidate: Boolean
        get() = false

    private val clickableNode = delegate(
        ClickablePointerInputNode(onClick)
    )

    fun update(color: Color, size: IntSize, onClick: () -> Unit) {
        if (this.color != color) {
            this.color = color
            // Only invalidate draw when color changes
            invalidateDraw()
        }

        if (this.size != size) {
            this.size = size
            // Only invalidate layout when size changes
            invalidateMeasurement()
        }

        // If only onClick changes, we don't need to invalidate anything
        clickableNode.update(onClick)
    }

    override fun ContentDrawScope.draw() {
        drawRect(color)
    }

    override fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints
    ): MeasureResult {
        val size = constraints.constrain(size)
        val placeable = measurable.measure(constraints)
        return layout(size.width, size.height) {
            placeable.place(0, 0)
        }
    }
}