वैल्यू के हिसाब से ऐनिमेशन

animate*AsState की मदद से किसी एक वैल्यू को ऐनिमेट करना

animate*AsState फ़ंक्शन, Compose में मौजूद सबसे आसान ऐनिमेशन एपीआई हैं. इनकी मदद से, किसी एक वैल्यू को ऐनिमेट किया जा सकता है. आपको सिर्फ़ टारगेट वैल्यू (या आखिरी वैल्यू) देनी होती है और एपीआई, मौजूदा वैल्यू से तय की गई वैल्यू तक ऐनिमेशन शुरू कर देता है.

इस एपीआई का इस्तेमाल करके, ऐल्फ़ा को ऐनिमेट करने का उदाहरण यहां दिया गया है. टारगेट वैल्यू को animateFloatAsState में रैप करने पर, अल्फा वैल्यू अब दी गई वैल्यू (इस मामले में 1f या 0.5f) के बीच एक ऐनिमेशन वैल्यू बन जाती है.

var enabled by remember { mutableStateOf(true) }

val alpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha")
Box(
    Modifier
        .fillMaxSize()
        .graphicsLayer(alpha = alpha)
        .background(Color.Red)
)

ध्यान दें कि आपको किसी ऐनिमेशन क्लास का इंस्टेंस बनाने या रुकावट को मैनेज करने की ज़रूरत नहीं है. इसके तहत, कॉल साइट पर एक ऐनिमेशन ऑब्जेक्ट (यानी, Animatable इंस्टेंस) बनाया जाएगा और उसे याद रखा जाएगा. इसकी शुरुआती वैल्यू के तौर पर, पहली टारगेट वैल्यू का इस्तेमाल किया जाएगा. इसके बाद, जब भी इस कॉम्पोज़ेबल को कोई दूसरी टारगेट वैल्यू दी जाती है, तो उस वैल्यू के लिए ऐनिमेशन अपने-आप शुरू हो जाता है. अगर कोई ऐनिमेशन पहले से चल रहा है, तो ऐनिमेशन अपनी मौजूदा वैल्यू (और वेग) से शुरू होता है और टारगेट वैल्यू की ओर ऐनिमेट होता है. ऐनिमेशन के दौरान, यह कॉम्पोज़ेबल फिर से कॉम्पोज़ हो जाता है और हर फ़्रेम में ऐनिमेशन की अपडेट की गई वैल्यू दिखाता है.

Compose में, Float, Color, Dp, Size, Offset, Rect, Int, IntOffset, और IntSize के लिए animate*AsState फ़ंक्शन पहले से मौजूद होते हैं. किसी सामान्य टाइप के animateValueAsState के लिए TwoWayConverter देकर, अन्य डेटा टाइप के लिए आसानी से सहायता जोड़ी जा सकती है.

AnimationSpec की वैल्यू देकर, ऐनिमेशन की खास बातों को पसंद के मुताबिक बनाया जा सकता है. ज़्यादा जानकारी के लिए, AnimationSpec देखें.

ट्रांज़िशन के साथ एक साथ कई प्रॉपर्टी को ऐनिमेट करना

Transition, एक या उससे ज़्यादा ऐनिमेशन को अपने चाइल्ड के तौर पर मैनेज करता है और उन्हें एक साथ कई स्टेटस में चलाता है.

स्टेटस किसी भी तरह के डेटा टाइप के हो सकते हैं. कई मामलों में, टाइप की सुरक्षा को पक्का करने के लिए, कस्टम enum टाइप का इस्तेमाल किया जा सकता है. इस उदाहरण में ऐसा किया गया है:

enum class BoxState {
    Collapsed,
    Expanded
}

updateTransition, Transition का एक इंस्टेंस बनाता है और उसे याद रखता है. साथ ही, उसकी स्थिति को अपडेट करता है.

var currentState by remember { mutableStateOf(BoxState.Collapsed) }
val transition = updateTransition(currentState, label = "box state")

इसके बाद, इस ट्रांज़िशन में चाइल्ड ऐनिमेशन तय करने के लिए, animate* एक्सटेंशन फ़ंक्शन में से किसी एक का इस्तेमाल किया जा सकता है. हर राज्य के लिए टारगेट वैल्यू तय करें. ये animate* फ़ंक्शन, ऐनिमेशन की एक वैल्यू दिखाते हैं. यह वैल्यू, ऐनिमेशन के दौरान हर फ़्रेम में अपडेट होती है. ऐसा तब होता है, जब ट्रांज़िशन स्टेटस को updateTransition से अपडेट किया जाता है.

val rect by transition.animateRect(label = "rectangle") { state ->
    when (state) {
        BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f)
        BoxState.Expanded -> Rect(100f, 100f, 300f, 300f)
    }
}
val borderWidth by transition.animateDp(label = "border width") { state ->
    when (state) {
        BoxState.Collapsed -> 1.dp
        BoxState.Expanded -> 0.dp
    }
}

इसके अलावा, ट्रांज़िशन स्टेटस में हुए बदलावों के हर कॉम्बिनेशन के लिए, अलग AnimationSpec तय करने के लिए transitionSpec पैरामीटर पास किया जा सकता है. ज़्यादा जानकारी के लिए, AnimationSpec देखें.

val color by transition.animateColor(
    transitionSpec = {
        when {
            BoxState.Expanded isTransitioningTo BoxState.Collapsed ->
                spring(stiffness = 50f)

            else ->
                tween(durationMillis = 500)
        }
    }, label = "color"
) { state ->
    when (state) {
        BoxState.Collapsed -> MaterialTheme.colorScheme.primary
        BoxState.Expanded -> MaterialTheme.colorScheme.background
    }
}

जब ट्रांज़िशन टारगेट स्टेटस पर पहुंच जाता है, तो Transition.currentState Transition.targetState जैसा ही होगा. इसका इस्तेमाल, ट्रांज़िशन पूरा होने के सिग्नल के तौर पर किया जा सकता है.

कभी-कभी, हमें शुरुआती स्थिति को पहले टारगेट की स्थिति से अलग रखना होता है. ऐसा करने के लिए, हम MutableTransitionState के साथ updateTransition का इस्तेमाल कर सकते हैं. उदाहरण के लिए, इससे हमें कोड के कॉम्पोज़िशन में आते ही ऐनिमेशन शुरू करने की अनुमति मिलती है.

// Start in collapsed state and immediately animate to expanded
var currentState = remember { MutableTransitionState(BoxState.Collapsed) }
currentState.targetState = BoxState.Expanded
val transition = rememberTransition(currentState, label = "box state")
// ……

एक से ज़्यादा कॉम्पोज़ेबल फ़ंक्शन वाले ज़्यादा जटिल ट्रांज़िशन के लिए, चाइल्ड ट्रांज़िशन बनाने के लिए createChildTransition का इस्तेमाल किया जा सकता है. यह तकनीक, किसी जटिल कॉम्पोज़ेबल में कई सब-कॉम्पोनेंट के बीच समस्याओं को अलग करने के लिए काम की है. पैरंट ट्रांज़िशन, चाइल्ड ट्रांज़िशन में मौजूद सभी ऐनिमेशन वैल्यू के बारे में जानता होगा.

enum class DialerState { DialerMinimized, NumberPad }

@Composable
fun DialerButton(isVisibleTransition: Transition<Boolean>) {
    // `isVisibleTransition` spares the need for the content to know
    // about other DialerStates. Instead, the content can focus on
    // animating the state change between visible and not visible.
}

@Composable
fun NumberPad(isVisibleTransition: Transition<Boolean>) {
    // `isVisibleTransition` spares the need for the content to know
    // about other DialerStates. Instead, the content can focus on
    // animating the state change between visible and not visible.
}

@Composable
fun Dialer(dialerState: DialerState) {
    val transition = updateTransition(dialerState, label = "dialer state")
    Box {
        // Creates separate child transitions of Boolean type for NumberPad
        // and DialerButton for any content animation between visible and
        // not visible
        NumberPad(
            transition.createChildTransition {
                it == DialerState.NumberPad
            }
        )
        DialerButton(
            transition.createChildTransition {
                it == DialerState.DialerMinimized
            }
        )
    }
}

AnimatedVisibility और AnimatedContent के साथ ट्रांज़िशन का इस्तेमाल करना

AnimatedVisibility और AnimatedContent, Transition के एक्सटेंशन फ़ंक्शन के तौर पर उपलब्ध हैं. Transition.AnimatedVisibility और Transition.AnimatedContent के लिए targetState, Transition से लिया जाता है. साथ ही, Transition के targetState में बदलाव होने पर, ज़रूरत के हिसाब से एंटर/एग्ज़िट ट्रांज़िशन ट्रिगर किए जाते हैं. इन एक्सटेंशन फ़ंक्शन की मदद से, Transition में उन सभी एंटर/एग्ज़िट/साइज़ ट्रांसफ़ॉर्म ऐनिमेशन को होस्ट किया जा सकता है जो आम तौर पर AnimatedVisibility/AnimatedContent में मौजूद होते हैं. इन एक्सटेंशन फ़ंक्शन की मदद से, AnimatedVisibility/AnimatedContent के स्टेटस में हुए बदलाव को बाहर से देखा जा सकता है. visible पैरामीटर के बजाय, AnimatedVisibility का यह वर्शन एक lambda लेता है, जो पैरंट ट्रांज़िशन की टारगेट स्टेटस को बूलियन में बदलता है.

ज़्यादा जानकारी के लिए, AnimatedVisibility और AnimatedContent देखें.

var selected by remember { mutableStateOf(false) }
// Animates changes when `selected` is changed.
val transition = updateTransition(selected, label = "selected state")
val borderColor by transition.animateColor(label = "border color") { isSelected ->
    if (isSelected) Color.Magenta else Color.White
}
val elevation by transition.animateDp(label = "elevation") { isSelected ->
    if (isSelected) 10.dp else 2.dp
}
Surface(
    onClick = { selected = !selected },
    shape = RoundedCornerShape(8.dp),
    border = BorderStroke(2.dp, borderColor),
    shadowElevation = elevation
) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
    ) {
        Text(text = "Hello, world!")
        // AnimatedVisibility as a part of the transition.
        transition.AnimatedVisibility(
            visible = { targetSelected -> targetSelected },
            enter = expandVertically(),
            exit = shrinkVertically()
        ) {
            Text(text = "It is fine today.")
        }
        // AnimatedContent as a part of the transition.
        transition.AnimatedContent { targetState ->
            if (targetState) {
                Text(text = "Selected")
            } else {
                Icon(imageVector = Icons.Default.Phone, contentDescription = "Phone")
            }
        }
    }
}

किसी ट्रांज़िशन को कैप्सुलेट करना और उसे फिर से इस्तेमाल करने लायक बनाना

आसान इस्तेमाल के उदाहरणों के लिए, अपने यूज़र इंटरफ़ेस (यूआई) के उसी कॉम्पोज़ेबल में ट्रांज़िशन ऐनिमेशन तय करना एक सही विकल्प है. हालांकि, अगर आपको ऐनिमेशन वाली कई वैल्यू वाले किसी कॉम्पोनेंट पर काम करना है, तो हो सकता है कि आप ऐनिमेशन को लागू करने की प्रोसेस को, कॉम्पोज़ेबल यूज़र इंटरफ़ेस (यूआई) से अलग करना चाहें.

ऐसा करने के लिए, एक ऐसी क्लास बनाएं जिसमें सभी ऐनिमेशन वैल्यू और 'अपडेट' फ़ंक्शन हो. यह फ़ंक्शन, उस क्लास का इंस्टेंस दिखाता है. ट्रांज़िशन लागू करने की प्रोसेस को अलग फ़ंक्शन में निकाला जा सकता है. यह पैटर्न तब काम आता है, जब ऐनिमेशन लॉजिक को एक जगह पर इकट्ठा करना हो या जटिल ऐनिमेशन को फिर से इस्तेमाल किया जा सके.

enum class BoxState { Collapsed, Expanded }

@Composable
fun AnimatingBox(boxState: BoxState) {
    val transitionData = updateTransitionData(boxState)
    // UI tree
    Box(
        modifier = Modifier
            .background(transitionData.color)
            .size(transitionData.size)
    )
}

// Holds the animation values.
private class TransitionData(
    color: State<Color>,
    size: State<Dp>
) {
    val color by color
    val size by size
}

// Create a Transition and return its animation values.
@Composable
private fun updateTransitionData(boxState: BoxState): TransitionData {
    val transition = updateTransition(boxState, label = "box state")
    val color = transition.animateColor(label = "color") { state ->
        when (state) {
            BoxState.Collapsed -> Color.Gray
            BoxState.Expanded -> Color.Red
        }
    }
    val size = transition.animateDp(label = "size") { state ->
        when (state) {
            BoxState.Collapsed -> 64.dp
            BoxState.Expanded -> 128.dp
        }
    }
    return remember(transition) { TransitionData(color, size) }
}

rememberInfiniteTransition की मदद से, बार-बार चलने वाला ऐनिमेशन बनाना

InfiniteTransition में Transition जैसे एक या एक से ज़्यादा चाइल्ड ऐनिमेशन होते हैं. हालांकि, ये ऐनिमेशन कंपोज़िशन में शामिल होते ही चलने लगते हैं और हटाए जाने तक चलते रहते हैं. rememberInfiniteTransition की मदद से, InfiniteTransition का इंस्टेंस बनाया जा सकता है. बच्चों के ऐनिमेशन, animateColor, animatedFloat या animatedValue के साथ जोड़े जा सकते हैं. ऐनिमेशन की जानकारी देने के लिए, आपको infiniteRepeatable भी बताना होगा.

val infiniteTransition = rememberInfiniteTransition(label = "infinite")
val color by infiniteTransition.animateColor(
    initialValue = Color.Red,
    targetValue = Color.Green,
    animationSpec = infiniteRepeatable(
        animation = tween(1000, easing = LinearEasing),
        repeatMode = RepeatMode.Reverse
    ),
    label = "color"
)

Box(
    Modifier
        .fillMaxSize()
        .background(color)
)

लो-लेवल ऐनिमेशन एपीआई

पिछले सेक्शन में बताए गए सभी हाई-लेवल ऐनिमेशन एपीआई, लो-लेवल ऐनिमेशन एपीआई के फ़ाउंडेशन पर बनाए गए हैं.

animate*AsState फ़ंक्शन सबसे आसान एपीआई हैं. ये वैल्यू में हुए तुरंत बदलाव को ऐनिमेशन वैल्यू के तौर पर रेंडर करते हैं. यह Animatable के साथ काम करता है. यह एक वैल्यू को ऐनिमेट करने के लिए, कोरयूटी पर आधारित एपीआई है. updateTransition एक ट्रांज़िशन ऑब्जेक्ट बनाता है, जो ऐनिमेशन वाली कई वैल्यू को मैनेज कर सकता है और उन्हें स्टेटस में बदलाव के आधार पर चला सकता है. rememberInfiniteTransition भी इसी तरह का है. हालांकि, यह एक ऐसा ट्रांज़िशन बनाता है जो अनलिमिटेड तरीके से चलता रहता है. इसमें कई ऐनिमेशन मैनेज किए जा सकते हैं, जो अनलिमिटेड तरीके से चलते रहते हैं. Animatable को छोड़कर, ये सभी एपीआई कॉम्पोज़ेबल हैं. इसका मतलब है कि इन ऐनिमेशन को कॉम्पोज़िशन के बाहर बनाया जा सकता है.

ये सभी एपीआई, ज़्यादा बुनियादी Animation एपीआई पर आधारित हैं. ज़्यादातर ऐप्लिकेशन, Animation के साथ सीधे तौर पर इंटरैक्ट नहीं करेंगे. हालांकि, Animation को पसंद के मुताबिक बनाने की कुछ सुविधाएं, बेहतर लेवल के एपीआई के ज़रिए उपलब्ध हैं. AnimationVector और AnimationSpec के बारे में ज़्यादा जानकारी के लिए, ऐनिमेशन को पसंद के मुताबिक बनाना लेख पढ़ें.

अलग-अलग लो-लेवल ऐनिमेशन एपीआई के बीच के संबंध को दिखाने वाला डायग्राम

Animatable: कोरूटीन पर आधारित एक वैल्यू वाला ऐनिमेशन

Animatable एक वैल्यू होल्डर है, जो animateTo की मदद से बदली गई वैल्यू को ऐनिमेट कर सकता है. यह वह एपीआई है जो animate*AsState को लागू करने के लिए बैकअप लेता है. इससे यह पक्का होता है कि वैल्यू में लगातार बदलाव होता रहे और एक साथ दो ऐनिमेशन न चलें. इसका मतलब है कि वैल्यू में बदलाव होने पर, चल रहा ऐनिमेशन बंद हो जाएगा.

Animatable की कई सुविधाएं, animateTo के साथ-साथ, निलंबित करने के फ़ंक्शन के तौर पर उपलब्ध कराई गई हैं. इसका मतलब है कि उन्हें सही कोरयूटीन स्कोप में रैप करना होगा. उदाहरण के लिए, LaunchedEffect कॉम्पोज़ेबल का इस्तेमाल करके, सिर्फ़ तय की गई कुंजी वैल्यू के लिए स्कोप बनाया जा सकता है.

// Start out gray and animate to green/red based on `ok`
val color = remember { Animatable(Color.Gray) }
LaunchedEffect(ok) {
    color.animateTo(if (ok) Color.Green else Color.Red)
}
Box(
    Modifier
        .fillMaxSize()
        .background(color.value)
)

ऊपर दिए गए उदाहरण में, हमने Color.Gray की शुरुआती वैल्यू के साथ Animatable का एक इंस्टेंस बनाया और उसे याद रखा. बूलियन फ़्लैग ok की वैल्यू के आधार पर, रंग Color.Green या Color.Red में ऐनिमेट होता है. बूलियन वैल्यू में कोई भी बदलाव होने पर, दूसरे रंग में ऐनिमेशन शुरू हो जाता है. अगर वैल्यू बदलने के दौरान कोई ऐनिमेशन चल रहा है, तो ऐनिमेशन रद्द हो जाता है और नया ऐनिमेशन, मौजूदा स्नैपशॉट वैल्यू से मौजूदा वेग के साथ शुरू होता है.

यह ऐनिमेशन लागू करने का तरीका है, जो पिछले सेक्शन में बताए गए animate*AsState API का बैक अप लेता है. animate*AsState की तुलना में, Animatable का इस्तेमाल करने पर, हमें कई मामलों में बेहतर तरीके से कंट्रोल मिलता है. पहला, Animatable की शुरुआती वैल्यू, उसकी पहली टारगेट वैल्यू से अलग हो सकती है. उदाहरण के लिए, ऊपर दिए गए कोड के उदाहरण में, पहले एक स्लेटी रंग का बॉक्स दिखता है. इसके तुरंत बाद, वह हरे या लाल रंग में ऐनिमेशन के साथ दिखने लगता है. दूसरा, Animatable, कॉन्टेंट वैल्यू पर ज़्यादा ऑपरेशन उपलब्ध कराता है. जैसे, snapTo और animateDecay. snapTo मौजूदा वैल्यू को तुरंत टारगेट वैल्यू पर सेट करता है. यह तब काम आता है, जब ऐनिमेशन ही सटीक जानकारी का एकमात्र सोर्स न हो और उसे टच इवेंट जैसे अन्य स्टेटस के साथ सिंक करना पड़े. animateDecay, एक ऐसा ऐनिमेशन शुरू करता है जो दिए गए वेग से धीमा हो जाता है. यह फ़्लिंग व्यवहार लागू करने के लिए काम का है. ज़्यादा जानकारी के लिए, जेस्चर और ऐनिमेशन देखें.

Animatable, Float और Color के साथ काम करता है. हालांकि, TwoWayConverter का इस्तेमाल करके किसी भी डेटा टाइप का इस्तेमाल किया जा सकता है. ज़्यादा जानकारी के लिए, AnimationVector देखें.

AnimationSpec की मदद से, ऐनिमेशन की खास बातों को पसंद के मुताबिक बनाया जा सकता है. ज़्यादा जानकारी के लिए, AnimationSpec देखें.

Animation: मैन्युअल तरीके से कंट्रोल किया जाने वाला ऐनिमेशन

Animation, सबसे निचले लेवल का ऐनिमेशन एपीआई है. अब तक हमने जो ऐनिमेशन देखे हैं उनमें से ज़्यादातर, ऐनिमेशन के ऊपर बनाए गए हैं. Animation के दो सब-टाइप होते हैं: TargetBasedAnimation और DecayAnimation.

Animation का इस्तेमाल सिर्फ़ ऐनिमेशन के समय को मैन्युअल तरीके से कंट्रोल करने के लिए किया जाना चाहिए. Animation स्टेटलेस है और इसमें लाइफ़साइकल का कोई कॉन्सेप्ट नहीं है. यह ऐनिमेशन कैलकुलेशन इंजन के तौर पर काम करता है. इसका इस्तेमाल, उच्च लेवल के एपीआई करते हैं.

TargetBasedAnimation

अन्य एपीआई, इस्तेमाल के ज़्यादातर उदाहरणों को कवर करते हैं. हालांकि, TargetBasedAnimation का इस्तेमाल करके, ऐनिमेशन के चलने का समय खुद कंट्रोल किया जा सकता है. नीचे दिए गए उदाहरण में, withFrameNanos के दिए गए फ़्रेम टाइम के आधार पर, TargetAnimation के चलने का समय मैन्युअल तरीके से कंट्रोल किया जाता है.

val anim = remember {
    TargetBasedAnimation(
        animationSpec = tween(200),
        typeConverter = Float.VectorConverter,
        initialValue = 200f,
        targetValue = 1000f
    )
}
var playTime by remember { mutableLongStateOf(0L) }

LaunchedEffect(anim) {
    val startTime = withFrameNanos { it }

    do {
        playTime = withFrameNanos { it } - startTime
        val animationValue = anim.getValueFromNanos(playTime)
    } while (someCustomCondition())
}

DecayAnimation

TargetBasedAnimation के उलट, DecayAnimation के लिए targetValue की वैल्यू देना ज़रूरी नहीं है. इसके बजाय, यह initialVelocity और initialValue से सेट की गई शुरुआती शर्तों और दिए गए DecayAnimationSpec के आधार पर, अपने targetValue का हिसाब लगाता है.

डिके ऐनिमेशन का इस्तेमाल अक्सर फ़्लिंग जेस्चर के बाद किया जाता है, ताकि एलिमेंट धीरे-धीरे रुक जाएं. ऐनिमेशन की गति, initialVelocityVector से सेट की गई वैल्यू से शुरू होती है और समय के साथ धीमी हो जाती है.