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 در سایت فراخوانی کارخانه اصلاحکننده حل میشوند
When creating a custom modifier using a composable modifier factory, composition locals take the value from the composition tree where they are created, not used. این کار میتواند به نتایج غیرمنتظره منجر شود. برای مثال، نمونه اصلاحکننده محلی ترکیببندی را که قبلاً ذکر شد درنظر بگیرید که بااستفاده از تابع ترکیبپذیر کمی متفاوت پیادهسازی شده است:
@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 یک میانای برنامهسازی کاربردی سطح پایینتر برای ایجاد اصلاحکنندهها در 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 |
استفاده |
پیوند نمونه |
|
||
|
||
با پیادهسازی این میانجی، |
||
|
||
|
||
|
||
|
||
|
||
|
||
این میتواند برای ترکیب کردن چندین پیادهسازی گره در یک گره مفید باشد. |
||
به کلاسهای |
وقتی بهروزرسانی در عنصر مربوطه فراخوانی میشود، گرهها بهطور خودکار باطل میشوند. چون مثال ما 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 will only get called if an equals comparison
with the previous element returns false.
مثال قبلی از کلاس داده برای دستیابی به این هدف استفاده میکند. از این روشها برای بررسی اینکه آیا گره نیاز به بهروزرسانی دارد یا نه استفاده میشود. اگر عنصر شما ویژگیهایی دارد که
در بهروزرسانی شدن یا نشدن گره نقشی ندارند، یا میخواهید بهدلایل سازگاری دودویی از
کلاسهای داده استفاده نکنید، میتوانید بهصورت دستی
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، در اینجا برخیاز موقعیتهای رایجی که ممکن است با آنها مواجه شوید آورده شده است.
پارامترهای صفر
اگر اصلاحکننده شما پارامتری نداشته باشد، هرگز نیازی به بهروزرسانی ندارد و، علاوهبراین، نیازی نیست کلاس داده باشد. در زیر نمونهای از پیادهسازی اصلاحکنندهای آورده شده است که مقدار ثابتی از فاصلهگذاری را به یک 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 بخوانند.
اما نمونههای گره اصلاحکننده بهطور خودکار تغییرات وضعیت را مشاهده نمیکنند. برای واکنش خودکار به تغییر CompositionLocal، میتوانید مقدار فعلی آن را در یک محدوده بخوانید:
DrawModifierNode:ContentDrawScopeLayoutModifierNode:MeasureScopeوIntrinsicMeasureScopeSemanticsModifierNode:SemanticsPropertyReceiver
این مثال مقدار LocalContentColor را برای ترسیم پسزمینه
براساس رنگ آن مشاهده میکند. As ContentDrawScope does observe snapshot changes, this
automatically redraws when the value of LocalContentColor changes:
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 APIs را فراهم میکند. برای مثال، این قطعه کد،
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 مربوطه بهروزرسانی میشوند. برای اصلاحکنندههای پیچیده، ممکن است بخواهید از این رفتار انصراف دهید تا کنترل دقیقتری بر زمان نامعتبر شدن فازها توسط اصلاحکننده خود داشته باشید.
این بهویژه زمانی مفید است که اصلاحکننده سفارشی شما هم چیدمان و هم
ترسیم را اصلاح کند. انصراف از باطلسازی خودکار به شما امکان میدهد که فقط زمانیکه ویژگیهای مربوط به ترسیم، مانند 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) } } }