স্টাইলে অবস্থা এবং অ্যানিমেশন

Styles API hovered , focused , এবং pressed মতো ইন্টারঅ্যাকশন অবস্থায় UI পরিবর্তনগুলি পরিচালনা করার জন্য একটি ঘোষণামূলক এবং সুবিন্যস্ত পদ্ধতি প্রদান করে। এই API এর সাহায্যে, আপনি মডিফায়ার ব্যবহার করার সময় সাধারণত প্রয়োজনীয় বয়লারপ্লেট কোড উল্লেখযোগ্যভাবে হ্রাস করতে পারেন।

প্রতিক্রিয়াশীল স্টাইলিং সহজতর করার জন্য, StyleState একটি স্থিতিশীল, পঠনযোগ্য ইন্টারফেস হিসেবে কাজ করে যা একটি উপাদানের সক্রিয় অবস্থা (যেমন এর সক্রিয়, চাপা, বা ফোকাসড অবস্থা) ট্র্যাক করে। একটি StyleScope এর মধ্যে, আপনি আপনার Style সংজ্ঞাগুলিতে সরাসরি শর্তসাপেক্ষ যুক্তি প্রয়োগ করতে state সম্পত্তির মাধ্যমে এটি অ্যাক্সেস করতে পারেন।

অবস্থা-ভিত্তিক মিথস্ক্রিয়া: ঘোরানো, ফোকাস করা, চাপা, নির্বাচিত, সক্রিয়, টগল করা

সাধারণ মিথস্ক্রিয়ার জন্য স্টাইলগুলিতে অন্তর্নির্মিত সমর্থন রয়েছে:

  • চাপা
  • ঝুলন্ত
  • নির্বাচিত
  • সক্ষম করা হয়েছে
  • টগল করা হয়েছে

কাস্টম স্টেট সাপোর্ট করাও সম্ভব। আরও তথ্যের জন্য কাস্টম স্টেট স্টাইলিং উইথ স্টাইলস্টেট বিভাগটি দেখুন।

স্টাইল প্যারামিটার ব্যবহার করে ইন্টারঅ্যাকশন অবস্থা পরিচালনা করুন

নিম্নলিখিত উদাহরণটি ইন্টারঅ্যাকশন অবস্থার প্রতিক্রিয়ায় 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 শুরু করুন।
  • interactionSource প্রদান করে styleState আরম্ভ করুন। নিশ্চিত করুন যে styleState এর সক্রিয় অবস্থা প্রদান করা সক্রিয় প্যারামিটারের মান প্রতিফলিত করে।
  • focusable এবং clickable মডিফায়ারগুলিতে interactionSource বরাদ্দ করুন। অবশেষে, মডিফায়ারের styleable প্যারামিটারে styleState প্রয়োগ করুন।

@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 স্টেট ব্যবহার করতে পারেন:

@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 API-এর মতো। নিচের উদাহরণটি স্টেট ফোকাসে পরিবর্তিত হলে 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 API অ্যানিমেশন কার্ভের সময়কাল বা আকৃতি পরিবর্তন করার জন্য একটি 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 কম্পোজেবলের বোতামগুলির জন্য আলাদা স্টাইলিং থাকতে পারে। আপনার নিজস্ব কাস্টম স্টেট তৈরি এবং ব্যবহার করতে এই পদক্ষেপগুলি অনুসরণ করুন:

  1. কাস্টম কী সংজ্ঞায়িত করুন
  2. StyleState এক্সটেনশন তৈরি করুন
  3. কাস্টম স্টেটের লিঙ্ক

কাস্টম কী সংজ্ঞায়িত করুন

একটি কাস্টম স্টেট-ভিত্তিক স্টাইল তৈরি করতে, প্রথমে একটি StyleStateKey তৈরি করুন এবং ডিফল্ট স্টেট মানটি পাস করুন। যখন অ্যাপটি চালু হয়, তখন মিডিয়া প্লেয়ারটি Stopped অবস্থায় থাকে, তাই এটি এইভাবে শুরু করা হয়:

enum class PlayerState {
    Stopped,
    Playing,
    Paused
}

val playerStateKey = StyleStateKey(PlayerState.Stopped)

StyleState এক্সটেনশন ফাংশন তৈরি করুন

বর্তমান playState জিজ্ঞাসা করার জন্য StyleState এ একটি এক্সটেনশন ফাংশন সংজ্ঞায়িত করুন। তারপর, playStateKey তে আপনার কাস্টম স্টেট পাস করে, নির্দিষ্ট স্টেট সহ একটি ল্যাম্বডা এবং স্টাইল দিয়ে StyleScope এ এক্সটেনশন ফাংশন তৈরি করুন।

// 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 কে ইনকামিং স্টেটের সমান সেট করো। মডিফায়ারের styleable ফাংশনে styleState পাস করো।

ল্যাম্বডা style মধ্যে, আপনি পূর্বে সংজ্ঞায়িত এক্সটেনশন ফাংশন ব্যবহার করে কাস্টম স্টেটগুলির জন্য স্টেট ভিত্তিক স্টাইলিং প্রয়োগ করতে পারেন।

এই উদাহরণের জন্য নিম্নলিখিত কোডটি সম্পূর্ণ স্নিপেট:

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)
}