تقدّم Styles API طريقة تصريحية ومبسّطة لإدارة تغييرات واجهة المستخدم أثناء حالات التفاعل، مثل hovered وfocused وpressed. باستخدام واجهة برمجة التطبيقات هذه، يمكنك تقليل رمز النص النموذجي المطلوب عادةً عند استخدام المعدِّلات بشكل كبير.
لتسهيل التنسيق التفاعلي، تعمل StyleState كواجهة ثابتة للقراءة فقط تتتبّع الحالة النشطة لأحد العناصر (مثل حالته المفعّلة أو المضغوطة أو المحدّدة). ضمن StyleScope، يمكنك الوصول إلى هذا من خلال السمة state لتنفيذ منطق شرطي مباشرةً في تعريفات الأنماط.
التفاعل المستند إلى الحالة: التمرير، والتركيز، والضغط، والاختيار، والتفعيل، والتبديل
تتضمّن الأنماط ميزة مدمجة تتيح التفاعلات الشائعة:
- مفعَّلة
- تم التمرير
- تمّ اختياره
- الأجهزة المفعّلة
- تم التبديل
يمكنك أيضًا توفير حالات مخصّصة. راجِع قسم تخصيص نمط الحالة باستخدام 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تعكس قيمة المَعلمة enabled المقدَّمة. - عيِّن
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 = remember(interactionSource) { MutableStyleState(interactionSource) } styleState.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 من الأسود إلى الأزرق عند تغيير الحالة إلى "في وضع التركيز":
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 spec:
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)) { ///.. } }
ضمن دالة style lambda، يمكنك تطبيق نمط مستند إلى الحالة للحالات المخصّصة،
باستخدام دوال الإضافة التي تم تحديدها سابقًا.
@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) }