نوشتن اصلاح‌کننده‌ها

اصلاح‌کننده‌ها به شما امکان می‌دهند یک composable را تزئین یا تقویت کنید. اصلاح‌کننده‌ها به شما امکان می‌دهند این نوع کارها را انجام دهید:

  • تغییر اندازه، چیدمان، رفتار، و ظاهر ترکیب‌پذیر
  • افزودن اطلاعات، مثل برچسب‌های دسترس‌پذیری
  • پردازش ورودی کاربر
  • تعامل‌های سطح بالا اضافه کنید، مثلاً عنصری را کلیک‌کردنی، پیمایش‌کردنی، کشیدنی، یا بزرگ‌کردنی کنید

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

@Composable
private fun Greeting(name: String) {
    Column(modifier = Modifier.padding(24.dp)) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

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

می‌توانید این توابع را به‌هم زنجیر کنید تا آن‌ها را ترکیب کنید:

@Composable
private fun Greeting(name: String) {
    Column(
        modifier = Modifier
            .padding(24.dp)
            .fillMaxWidth()
    ) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

پس‌زمینه رنگی پشت نوشتار اکنون کل عرض دستگاه را می‌پوشاند.

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

  • padding فضایی را در اطراف عنصر قرار می‌دهد.
  • fillMaxWidth باعث می‌شود که composable حداکثر عرضی را که از والد خود دریافت کرده است پر کند.

بهترین روش این است که همه composableهای شما یک پارامتر modifier را بپذیرند و آن اصلاح‌کننده را به اولین فرزند خود که UI را منتشر می‌کند، منتقل کنند. این کار باعث می‌شود کد شما بیشتر قابل‌استفاده مجدد باشد و رفتار آن قابل‌پیش‌بینی‌تر و شهودی‌تر شود. برای اطلاعات بیشتر، به دستورالعمل‌های Compose API، عناصر پارامتر اصلاح‌کننده را می‌پذیرند و رعایت می‌کنند مراجعه کنید.

ترتیب اصلاح‌گرها مهم است

ترتیب توابع اصلاح‌کننده مهم است. ازآنجایی‌که هر تابع تغییراتی در Modifier برگردانده‌شده توسط تابع قبلی ایجاد می‌کند، ترتیب بر نتیجه نهایی تأثیر می‌گذارد. بیایید مثالی از این را ببینیم:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

کل ناحیه، ازجمله حاشیه اطراف لبه‌ها، به کلیک‌ها پاسخ می‌دهد

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

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .padding(padding)
            .clickable(onClick = onClick)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

حاشیه اطراف لبه چیدمان دیگر به کلیک‌ها پاسخ نمی‌دهد

اصلاح‌کننده‌های داخلی

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

‫padding و size

به‌طور پیش‌فرض، طرح‌بندی‌های ارائه‌شده در Compose فرزندان خود را می‌پیچند. بااین‌حال، می‌توانید بااستفاده از اصلاح‌کننده size اندازه را تنظیم کنید:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(/*...*/)
        Column { /*...*/ }
    }
}

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

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.requiredSize(150.dp)
        )
        Column { /*...*/ }
    }
}

تصویر فرزند بزرگ‌تر از محدودیت‌های تصویر والد است

در این مثال، حتی با تنظیم height والد روی 100.dp، ارتفاع Image برابر با 150.dp خواهد بود، زیرا اصلاح‌کننده requiredSize اولویت دارد.

اگر می‌خواهید چیدمان فرزند کل ارتفاع دردسترس مجاز توسط والد را پر کند، اصلاح‌کننده fillMaxHeight را اضافه کنید (Compose همچنین fillMaxSize و fillMaxWidth را ارائه می‌دهد):

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.fillMaxHeight()
        )
        Column { /*...*/ }
    }
}

ارتفاع تصویر به‌اندازه والد آن است

برای افزودن حاشیه داخلی به اطراف عنصر، اصلاح‌کننده padding را تنظیم کنید. پارامترهای start و end از padding از جهت چیدمان پیروی می‌کنند. برای افزودن حاشیه داخلی به لبه چپ یا راست بدون توجه به جهت چیدمان، از absolutePadding با پارامترهای left و right استفاده کنید.

اگر می‌خواهید لایه داخلی را بالای خط مبنای نوشتار اضافه کنید تا به فاصله مشخصی از بالای چیدمان تا خط مبنا برسید، از اصلاح‌کننده paddingFromBaseline استفاده کنید:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(
                text = artist.name,
                modifier = Modifier.paddingFromBaseline(top = 50.dp)
            )
            Text(artist.lastSeenOnline)
        }
    }
}

نوشتار با حاشیه داخلی در بالای آن

آفست

برای قرار دادن چیدمان نسبت به موقعیت اصلی آن، اصلاح‌کننده offset را اضافه کنید و آفست را در محور x و y تنظیم کنید. انحراف‌ها می‌توانند مثبت و همچنین غیرمثبت باشند. تفاوت بین padding و offset این است که افزودن offset به یک ترکیب‌پذیر، اندازه‌های آن را تغییر نمی‌دهد:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(artist.name)
            Text(
                text = artist.lastSeenOnline,
                modifier = Modifier.offset(x = 4.dp)
            )
        }
    }
}

نوشتار به سمت راست محفظه والد منتقل شد

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

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

ایمنی محدوده در Compose

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

برای مثال، اگر می‌خواهید فرزند را به‌اندازه والد Box کنید بدون اینکه بر اندازه Box تأثیر بگذارد، از اصلاح‌کننده matchParentSize استفاده کنید. matchParentSize فقط در BoxScope دردسترس است. بنابراین، فقط می‌توان از آن برای فرزند در Box ولی استفاده کرد.

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

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

matchParentSize در Box

همان‌طور که در بالا ذکر شد، اگر می‌خواهید چیدمان فرزند هم‌اندازه چیدمان والد Box باشد بدون اینکه بر اندازه Box تأثیر بگذارد، از اصلاح‌کننده matchParentSize استفاده کنید.

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

در مثال زیر، فرزند Spacer اندازه خود را از والد Box می‌گیرد، که آن هم اندازه خود را از بزرگ‌ترین فرزندان می‌گیرد، ArtistCard در این مورد.

@Composable
fun MatchParentSizeComposable() {
    Box {
        Spacer(
            Modifier
                .matchParentSize()
                .background(Color.LightGray)
        )
        ArtistCard()
    }
}

پس‌زمینه خاکستری که ظرفش را پر می‌کند

اگر به‌جای matchParentSize از fillMaxSize استفاده شود، Spacer همه فضای دردسترس مجاز برای والد را می‌گیرد و به‌نوبه خود باعث می‌شود والد گسترش یابد و همه فضای دردسترس را پر کند.

پس‌زمینه خاکستری که صفحه را پر می‌کند

‫weight در «Row» و «Column»

همان‌طور که در بخش قبلی درباره حاشیه داخلی و اندازه دیدید، به‌طور پیش‌فرض، اندازه ترکیب‌پذیر توسط محتوایی که دربرمی‌گیرد تعریف می‌شود. می‌توانید اندازه ترکیب‌پذیر را طوری تنظیم کنید که در والد خود انعطاف‌پذیر باشد. برای این کار، از «اصلاح‌کننده» weight استفاده کنید که فقط در RowScope و ColumnScope دردسترس است.

بیایید یک Row را درنظر بگیریم که حاوی دو ترکیب‌پذیر Box است. جعبه اول دوبرابر weight جعبه دوم را دارد، بنابراین عرض آن دوبرابر است. ازآنجاکه عرض Row برابر با 210.dp است، عرض Box اول برابر با 140.dp است، و عرض دومی برابر با 70.dp است:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.fillMaxWidth()
    ) {
        Image(
            /*...*/
            modifier = Modifier.weight(2f)
        )
        Column(
            modifier = Modifier.weight(1f)
        ) {
            /*...*/
        }
    }
}

پهنای تصویر دو برابر پهنای نوشتار است

استخراج و استفاده مجدد از اصلاح‌کننده‌ها

چند اصلاح‌گر را می‌توان به‌صورت زنجیره‌ای به هم متصل کرد تا یک composable را تزئین یا تقویت کرد. این زنجیره ازطریق میانای Modifier ایجاد می‌شود که نشان‌دهنده فهرست مرتب و تغییرناپذیر از Modifier.Elements تکی است.

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

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

  • وقتی بازترکیب برای ترکیب‌شدنی‌هایی که از اصلاح‌کننده‌ها استفاده می‌کنند اتفاق می‌افتد، تخصیص مجدد اصلاح‌کننده‌ها تکرار نخواهد شد
  • Modifier chains could potentially be very long and complex, so reusing the same instance of a chain can alleviate the workload Compose runtime needs to do when comparing them
  • این استخراج باعث تمیزی، سازگاری، و قابلیت نگهداری کد در سراسر پایگاه کد می‌شود

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

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

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

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

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

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // Creation and allocation of this modifier will happen on every frame of the animation!
        modifier = Modifier
            .padding(12.dp)
            .background(Color.Gray),
        animatedState = animatedState
    )
}

درعوض، می‌توانید همان نمونه اصلاح‌کننده را ایجاد، استخراج، و مجدداً استفاده کنید و آن را به‌صورت زیر به composable ارسال کنید:

// Now, the allocation of the modifier happens here:
val reusableModifier = Modifier
    .padding(12.dp)
    .background(Color.Gray)

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // No allocation, as we're just reusing the same instance
        modifier = reusableModifier,
        animatedState = animatedState
    )
}

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

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

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

@Composable
fun AuthorField() {
    HeaderText(
        // ...
        modifier = reusableModifier
    )
    SubtitleText(
        // ...
        modifier = reusableModifier
    )
}

این امر به‌ویژه زمانی‌که با چیدمان‌های Lazy ترکیب شود می‌تواند مفید باشد. در بیشتر موارد، می‌خواهید همه مواردتان (که احتمالاً تعدادشان زیاد است) توصیف‌گرهای کاملاً یکسانی داشته باشند:

val reusableItemModifier = Modifier
    .padding(bottom = 12.dp)
    .size(216.dp)
    .clip(CircleShape)

@Composable
private fun AuthorList(authors: List<Author>) {
    LazyColumn {
        items(authors) {
            AsyncImage(
                // ...
                modifier = reusableItemModifier,
            )
        }
    }
}

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

هنگام کار با اصلاح‌کننده‌هایی که به composable خاصی محدود شده‌اند، می‌توانید آن‌ها را به بالاترین سطح ممکن استخراج کنید و در جاهای مناسب دوباره استفاده کنید:

Column(/*...*/) {
    val reusableItemModifier = Modifier
        .padding(bottom = 12.dp)
        // Align Modifier.Element requires a ColumnScope
        .align(Alignment.CenterHorizontally)
        .weight(1f)
    Text1(
        modifier = reusableItemModifier,
        // ...
    )
    Text2(
        modifier = reusableItemModifier
        // ...
    )
    // ...
}

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

Column(modifier = Modifier.fillMaxWidth()) {
    // Weight modifier is scoped to the Column composable
    val reusableItemModifier = Modifier.weight(1f)

    // Weight will be properly assigned here since this Text is a direct child of Column
    Text1(
        modifier = reusableItemModifier
        // ...
    )

    Box {
        Text2(
            // Weight won't do anything here since the Text composable is not a direct child of Column
            modifier = reusableItemModifier
            // ...
        )
    }
}

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

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

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

// Append to your reusableModifier
reusableModifier.clickable { /*...*/ }

// Append your reusableModifier
otherModifier.then(reusableModifier)

فقط به‌خاطر داشته باشید که ترتیب اصلاح‌کننده‌ها مهم است!

بیشتر بدانید

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

برای تمرین بیشتر درباره نحوه استفاده از اصلاح‌کننده‌ها، می‌توانید به Basic layouts in Compose codelab مراجعه کنید یا به Now in Android repository مراجعه کنید.

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