Compose اصلاحکنندههای زیادی را برای رفتارهای رایج در خارج از جعبه فراهم میکند، اما میتوانید اصلاحکنندههای سفارشی خود را نیز ایجاد کنید.
اصلاح کننده ها چندین بخش دارند:
- یک کارخانه اصلاح کننده
- این یک تابع افزونه در
Modifier
است که یک API اصطلاحی برای اصلاح کننده شما ارائه می دهد و به اصلاح کننده ها اجازه می دهد به راحتی به هم زنجیر شوند. کارخانه اصلاحکننده عناصر اصلاحکنندهای را که توسط 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 یک اصلاح کننده سفارشی ایجاد کنید تا مقادیر را به یک اصلاح کننده موجود منتقل کنید. این به عنوان یک کارخانه اصلاح کننده ترکیبی شناخته می شود.
استفاده از کارخانه اصلاحکننده قابل ترکیب برای ایجاد یک اصلاحکننده همچنین امکان استفاده از APIهای آهنگسازی سطح بالاتر، مانند animate*AsState
و سایر APIهای انیمیشن با پشتیبانی از حالت 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 است. این همان API است که 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
پیادهسازی میکند، که به آن اجازه میدهد روش ترسیم را لغو کند.
انواع موجود به شرح زیر است:
گره | استفاده | لینک نمونه |
یک | ||
یک | ||
پیاده سازی این رابط به | ||
یک | ||
یک | ||
یک | ||
یک | ||
یک | ||
| ||
یک این می تواند برای ترکیب چندین پیاده سازی گره در یک مورد مفید باشد. | ||
به کلاس های |
هنگامی که به روز رسانی در عنصر مربوطه آنها فراخوانی می شود، گره ها به طور خودکار باطل می شوند. از آنجایی که مثال ما یک 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
باید روش های زیر را نادیده بگیرند:
-
create
: این تابعی است که گره اصلاح کننده شما را نمونه سازی می کند. هنگامی که اصلاح کننده شما برای اولین بار اعمال می شود، برای ایجاد گره فراخوانی می شود. معمولاً این به منزله ساختن گره و پیکربندی آن با پارامترهایی است که به کارخانه اصلاح کننده منتقل شده است. -
update
: این تابع زمانی فراخوانی می شود که این اصلاح کننده در همان نقطه ای که این گره از قبل وجود دارد ارائه شود، اما یک ویژگی تغییر کرده است. این با متدequals
کلاس تعیین می شود. گره اصلاح کننده ای که قبلا ایجاد شده بود به عنوان پارامتر به فراخوانیupdate
ارسال می شود. در این مرحله، باید ویژگی های گره ها را به روز کنید تا با پارامترهای به روز شده مطابقت داشته باشد. توانایی گرهها برای استفاده مجدد از این طریق، کلید دستاوردهای عملکردی است کهModifier.Node
به ارمغان میآورد. بنابراین، شما باید به جای ایجاد یک گره جدید در روشupdate
گره موجود را به روز کنید. در مثال دایره ما، رنگ گره به روز می شود.
علاوه بر این، پیادهسازیهای ModifierNodeElement
نیز نیاز به پیادهسازی equals
و hashCode
دارند. update
تنها زمانی فراخوانی می شود که مقایسه برابر با عنصر قبلی 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
نسبت به اصلاحکنندههایی که بهتازگی با یک کارخانه ترکیبسازی ایجاد شدهاند این است که میتوانند با استفاده از currentValueOf
، مقدار ترکیب را از جایی که اصلاحکننده در درخت UI شما استفاده میشود، بخوانند.
با این حال، نمونه های گره اصلاح کننده به طور خودکار تغییرات حالت را مشاهده نمی کنند. برای واکنش خودکار به تغییر محلی ترکیب، میتوانید مقدار فعلی آن را در یک محدوده بخوانید:
-
DrawModifierNode
:ContentDrawScope
-
LayoutModifierNode
:MeasureScope
&IntrinsicMeasureScope
-
SemanticsModifierNode
:SemanticsPropertyReceiver
این مثال مقدار 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 API استفاده کنید. به عنوان مثال، این قطعه CircleNode
از بالا تغییر می دهد تا به طور مکرر محو شود:
class CircleNode(var color: Color) : Modifier.Node(), DrawModifierNode { private val alpha = Animatable(1f) override fun ContentDrawScope.draw() { drawCircle(color = color, alpha = alpha.value) drawContent() } override fun onAttach() { 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
مربوطه آنها را بهروزرسانی میکند، باطل میشوند. گاهی اوقات، در یک اصلاحکننده پیچیدهتر، ممکن است بخواهید از این رفتار انصراف دهید تا کنترل دقیقتری روی زمانی که اصلاحکننده شما فازها را باطل میکند، داشته باشید.
این می تواند به ویژه مفید باشد اگر اصلاح کننده سفارشی شما هم طرح و هم ترسیم را اصلاح کند. انصراف از باطلسازی خودکار به شما امکان میدهد فقط زمانی که ویژگیهای مربوط به ترسیم، مانند color
، تغییر و عدم بیاعتبار کردن طرحبندی، ترسیم را باطل کنید. این می تواند عملکرد اصلاح کننده شما را بهبود بخشد.
یک مثال فرضی از آن در زیر با یک اصلاح کننده نشان داده شده است که دارای 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) } } }