ساختن اصلاح‌کننده‌های سفارشی

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

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

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

بسته به عملکرد موردنیاز، چندین روش برای پیاده‌سازی اصلاح‌گر سفارشی وجود دارد. اغلب، ساده‌ترین راه برای پیاده‌سازی یک اصلاح‌گر سفارشی این است که کارخانه اصلاح‌گر سفارشی را پیاده‌سازی کنید که کارخانه‌های اصلاح‌گر ازپیش تعریف‌شده دیگر را ترکیب می‌کند. اگر به رفتار سفارشی بیشتری نیاز دارید، عنصر اصلاح‌کننده را بااستفاده از میاناهای برنامه‌سازی کاربردی 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)

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

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

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

@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 مقدار در سایت تماس کارخانه اصلاح‌کننده حل‌وفصل می‌شود

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

@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 است. این همان میانای برنامه‌سازی کاربردی است که Compose اصلاح‌گرهای خود را در آن پیاده‌سازی می‌کند و بهترین روش برای ایجاد اصلاح‌گرهای سفارشی است.

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

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

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

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

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

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 را پیاده‌سازی می‌کند، که به آن امکان می‌دهد روش رسم را ملغی کند.

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

Node

استفاده

پیوند نمونه

LayoutModifierNode

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

نمونه

DrawModifierNode

Modifier.Node که در فضای چیدمان رسم می‌کند.

نمونه

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 ارائه دهند که در پاسخ به تغییرات در اشیای نمای لحظه‌ای خوانده‌شده در بلوک 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 و hashCode را به‌صورت دستی پیاده‌سازی کنید، برای مثال، عنصر اصلاح‌گر حاشیه.

کارخانه اصلاح‌گر

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

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

نمونه کامل

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

// 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، در اینجا برخی‌از موقعیت‌های معمولی که ممکن است با آن‌ها مواجه شوید آورده شده است.

بدون پارامتر

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

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 نسبت به اصلاح‌کننده‌هایی که فقط با کارخانه ترکیبی ایجاد می‌شوند این است که می‌توانند مقدار محلی ترکیب را از جایی که اصلاح‌کننده در درخت واسط کاربر استفاده می‌شود بخوانند، نه از جایی که اصلاح‌کننده تخصیص داده می‌شود، بااستفاده از currentValueOf.

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

این مثال مقدار LocalContentColor را برای رسم پس‌زمینه‌ای براساس رنگ آن مشاهده می‌کند. ازآنجایی‌که ContentDrawScope تغییرات لحظه‌ای را مشاهده می‌کند، این وقتی مقدار 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 دسترسی دارد. این کار امکان استفاده از میاناهای برنامه‌سازی کاربردی 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)
    )
}

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

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

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

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

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)
        }
    }
}