تقدّم Styles API طريقة إعلانية ومبسّطة لإدارة تغييرات واجهة المستخدم أثناء حالات التفاعل، مثل hovered وfocused وpressed. باستخدام واجهة برمجة التطبيقات هذه، يمكنك تقليل رمز النص النموذجي بشكل كبير، وهو الرمز المطلوب عادةً عند استخدام المعدِّلات.
لتسهيل تصميم العناصر التفاعلية، تعمل السمة StyleState كواجهة ثابتة للقراءة فقط تتتبّع الحالة النشطة لعنصر معيّن (مثل حالته المفعّلة أو المضغوطة أو التي تم التركيز عليها). ضمن StyleScope، يمكنك الوصول إلى هذه السمة من خلال السمة state لتنفيذ منطق شرطي مباشرةً في تعريفات النمط.
التفاعل المستند إلى الحالة: Hovered وfocused وpressed وselected وenabled وtoggled
تتضمّن الأنماط دعمًا مدمجًا للتفاعلات الشائعة:
- مضغوط
- تم التمرير على
- تم الاختيار.
- مفعّلة
- تم التبديل
من الممكن أيضًا دعم الحالات المخصّصة. راجِع قسم تصميم الحالات المخصّصة باستخدام StyleState لمزيد من المعلومات.
التعامل مع حالات التفاعل باستخدام مَعلمات النمط
يوضّح المثال التالي تعديل background وborderColor استجابةً لحالات التفاعل، وتحديدًا التبديل إلى اللون الأرجواني عند التمرير فوق العنصر والأزرق عند التركيز عليه:
@Preview @Composable private fun OpenButton() { BaseButton( style = outlinedButtonStyle then { background(Color.White) hovered { background(lightPurple) border(2.dp, lightPurple) } focused { background(lightBlue) } }, onClick = { }, content = { BaseText("Open in Studio", style = { contentColor(Color.Black) fontSize(26.sp) textAlign(TextAlign.Center) }) } ) }
يمكنك أيضًا إنشاء تعريفات حالات متداخلة. على سبيل المثال، يمكنك تحديد نمط معيّن عندما يتم الضغط على زر والتمرير فوقه في الوقت نفسه:
@Composable private fun OpenButton_CombinedStates() { BaseButton( style = outlinedButtonStyle then { background(Color.White) hovered { // light purple background(lightPurple) pressed { // When running on a device that can hover, whilst hovering and then pressing the button this would be invoked background(lightOrange) } } pressed { // when running on a device without a mouse attached, this would be invoked as you wouldn't be in a hovered state only background(lightRed) } focused { background(lightBlue) } }, onClick = { }, content = { BaseText("Open in Studio", style = { contentColor(Color.Black) fontSize(26.sp) textAlign(TextAlign.Center) }) } ) }
العناصر القابلة للإنشاء المخصّصة باستخدام Modifier.styleable
عند إنشاء مكوّنات styleable خاصة بك، يجب ربط interactionSource بـ styleState. بعد ذلك، مرِّر هذه الحالة إلى Modifier.styleable لاستخدامها.
لنفترض أنّ نظام التصميم يتضمّن GradientButton. قد تريد إنشاء LoginButton يرث من GradientButton، ولكن يغيّر ألوانه أثناء التفاعلات، مثل الضغط عليه.
- لتفعيل تعديلات نمط
interactionSource، ضِّمنinteractionSourceكمَعلمة ضمن العنصر القابل للإنشاء. استخدِم المَعلمة المقدَّمة أو، إذا لم يتم تقديم مَعلمة، ابدأMutableInteractionSourceجديدًا. - ابدأ
styleStateمن خلال تقديمinteractionSource. تأكَّد من أنّ حالةstyleStateالمفعّلة تعكس قيمة المَعلمة المفعّلة المقدَّمة. - عيِّن
interactionSourceللمعدِّلَينfocusableوclickable. أخيرًا، طبِّقstyleStateعلى المَعلمةstyleableللمعدِّل.
@Composable private fun GradientButton( onClick: () -> Unit, modifier: Modifier = Modifier, style: Style = Style, enabled: Boolean = true, interactionSource: MutableInteractionSource? = null, content: @Composable RowScope.() -> Unit, ) { val interactionSource = interactionSource ?: remember { MutableInteractionSource() } val styleState = rememberUpdatedStyleState(interactionSource) { it.isEnabled = enabled } Row( modifier = modifier .clickable( onClick = onClick, enabled = enabled, interactionSource = interactionSource, indication = null, ) .styleable(styleState, baseGradientButtonStyle then style), content = content, ) }
يمكنك الآن استخدام حالة interactionSource لتعديل النمط باستخدام الخيارات pressed وfocused وhovered داخل كتلة النمط:
@Preview @Composable fun LoginButton() { val loginButtonStyle = Style { pressed { background( Brush.linearGradient( listOf(Color.Magenta, Color.Red) ) ) } } GradientButton(onClick = { // Login logic }, style = loginButtonStyle) { BaseText("Login") } }
interactionSourceتحريك تغييرات النمط
تتضمّن تغييرات حالة الأنماط دعمًا مدمجًا للرسوم المتحركة. يمكنك تضمين السمة الجديدة ضمن أي كتلة لتغيير الحالة باستخدام animate لإضافة رسوم متحركة تلقائيًا بين الحالات المختلفة. يشبه ذلك واجهات برمجة التطبيقات animate*AsState. تحرّك المثال التالي borderColor من الأسود إلى الأزرق عندما تتغيّر الحالة إلى focused:
val animatingStyle = Style { externalPadding(48.dp) border(3.dp, Color.Black) background(Color.White) size(100.dp) pressed { animate { borderColor(Color.Magenta) background(Color(0xFFB39DDB)) } } } @Preview @Composable private fun AnimatingStyleChanges() { val interactionSource = remember { MutableInteractionSource() } val styleState = remember(interactionSource) { MutableStyleState(interactionSource) } Box(modifier = Modifier .clickable( interactionSource, enabled = true, indication = null, onClick = { } ) .styleable(styleState, animatingStyle)) { } }
تقبل واجهة برمجة التطبيقات animate السمة animationSpec لتغيير مدة منحنى الحركة أو شكله. تحرّك المثال التالي حجم المربّع باستخدام السمة spring:
val animatingStyleSpec = Style { externalPadding(48.dp) border(3.dp, Color.Black) background(Color.White) size(100.dp) transformOrigin(TransformOrigin.Center) pressed { animate { borderColor(Color.Magenta) background(Color(0xFFB39DDB)) } animate(spring(dampingRatio = Spring.DampingRatioMediumBouncy)) { scale(1.2f) } } } @Preview(showBackground = true) @Composable fun AnimatingStyleChangesSpec() { val interactionSource = remember { MutableInteractionSource() } val styleState = remember(interactionSource) { MutableStyleState(interactionSource) } Box(modifier = Modifier .clickable( interactionSource, enabled = true, indication = null, onClick = { } ) .styleable(styleState, animatingStyleSpec)) }
تصميم الحالات المخصّصة باستخدام StyleState
استنادًا إلى حالة استخدام العنصر القابل للإنشاء، قد يكون لديك أنماط مختلفة تستند إلى حالات مخصّصة. على سبيل المثال، إذا كان لديك تطبيق وسائط، قد تريد أن يكون لديك تصميم مختلف لأزرار العنصر القابل للإنشاء MediaPlayer استنادًا إلى حالة تشغيل المشغّل. اتّبِع الخطوات التالية لإنشاء حالة مخصّصة واستخدامها:
- تحديد مفتاح مخصّص
- إنشاء إضافة
StyleState - الربط بالحالة المخصّصة
تحديد مفتاح مخصّص
لإنشاء نمط مخصّص يستند إلى الحالة، أنشئ أولاً
StyleStateKey ومرِّر قيمة الحالة التلقائية. عند تشغيل التطبيق، يكون مشغّل الوسائط في حالة Stopped، لذا يتم تهيئته بهذه الطريقة:
enum class PlayerState { Stopped, Playing, Paused } val playerStateKey = StyleStateKey(PlayerState.Stopped)
إنشاء دوال إضافة StyleState
حدِّد دالة إضافة على StyleState للاستعلام عن playState الحالي.
بعد ذلك، أنشئ دوال إضافة على StyleScope باستخدام حالاتك المخصّصة التي تمرِّر playStateKey وlambda مع الحالة المحدّدة والنمط.
// Extension Function on MutableStyleState to query and set the current playState var MutableStyleState.playerState get() = this[playerStateKey] set(value) { this[playerStateKey] = value } fun StyleScope.playerPlaying(value: Style) { state(playerStateKey, value, { key, state -> state[key] == PlayerState.Playing }) } fun StyleScope.playerPaused(value: Style) { state(playerStateKey, value, { key, state -> state[key] == PlayerState.Paused }) }
الربط بالحالة المخصّصة
حدِّد styleState في العنصر القابل للإنشاء واضبط styleState.playState
على الحالة الواردة. مرِّر styleState إلى الدالة styleable في المعدِّل.
@Composable fun MediaPlayer( url: String, modifier: Modifier = Modifier, style: Style = Style, state: PlayerState = remember { PlayerState.Paused } ) { // Hoist style state, set playstate as a parameter, val styleState = remember { MutableStyleState(null) } // Set equal to incoming state to link the two together styleState.playerState = state Box( modifier = modifier.styleable(styleState, style)) { ///.. } }
ضمن lambda style، يمكنك تطبيق تصميم يستند إلى الحالة للحالات المخصّصة، باستخدام دوال الإضافة التي تم تحديدها سابقًا.
@Composable fun StyleStateKeySample() { // Using the extension function to change the border color to green while playing val style = Style { borderColor(Color.Gray) playerPlaying { animate { borderColor(Color.Green) } } playerPaused { animate { borderColor(Color.Blue) } } } val styleState = remember { MutableStyleState(null) } styleState[playerStateKey] = PlayerState.Playing // Using the style in a composable that sets the state -> notice if you change the state parameter, the style changes. You can link this up to an ViewModel and change the state from there too. MediaPlayer(url = "https://example.com/media/video", style = style, state = PlayerState.Stopped) }
في ما يلي مقتطف الرمز الكامل لهذا المثال:
enum class PlayerState { Stopped, Playing, Paused } val playerStateKey = StyleStateKey<PlayerState>(PlayerState.Stopped) var MutableStyleState.playerState get() = this[playerStateKey] set(value) { this[playerStateKey] = value } fun StyleScope.playerPlaying(value: Style) { state(playerStateKey, value, { key, state -> state[key] == PlayerState.Playing }) } fun StyleScope.playerPaused(value: Style) { state(playerStateKey, value, { key, state -> state[key] == PlayerState.Paused }) } @Composable fun MediaPlayer( url: String, modifier: Modifier = Modifier, style: Style = Style, state: PlayerState = remember { PlayerState.Paused } ) { // Hoist style state, set playstate as a parameter, val styleState = remember { MutableStyleState(null) } // Set equal to incoming state to link the two together styleState.playerState = state Box( modifier = modifier.styleable(styleState, Style { size(100.dp) border(2.dp, Color.Red) }, style, )) { ///.. } } @Composable fun StyleStateKeySample() { // Using the extension function to change the border color to green while playing val style = Style { borderColor(Color.Gray) playerPlaying { animate { borderColor(Color.Green) } } playerPaused { animate { borderColor(Color.Blue) } } } val styleState = remember { MutableStyleState(null) } styleState[playerStateKey] = PlayerState.Playing // Using the style in a composable that sets the state -> notice if you change the state parameter, the style changes. You can link this up to an ViewModel and change the state from there too. MediaPlayer(url = "https://example.com/media/video", style = style, state = PlayerState.Stopped) }