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

इस पेज पर, Jetpack Compose में वैल्यू के आधार पर ऐनिमेशन बनाने का तरीका बताया गया है. इसमें उन एपीआई पर फ़ोकस किया गया है जो मौजूदा और टारगेट स्टेट के आधार पर वैल्यू को ऐनिमेट करते हैं.

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

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

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

var enabled by remember { mutableStateOf(true) }

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

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

डिफ़ॉल्ट रूप से, Compose, animate*AsState फ़ंक्शन Float, Color, Dp, Size, Offset, Rect, Int, IntOffset, और IntSize के लिए उपलब्ध कराता है. 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* एक्सटेंशन फ़ंक्शन में से किसी एक का इस्तेमाल किया जा सकता है. हर स्टेट के लिए टारगेट वैल्यू तय करें. updateTransition से ट्रांज़िशन स्टेट अपडेट होने पर, ये animate* फ़ंक्शन एक ऐनिमेशन वैल्यू दिखाते हैं. यह वैल्यू, ऐनिमेशन के दौरान हर फ़्रेम में अपडेट होती है.

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

ज़रूरत पड़ने पर, transitionSpec पैरामीटर पास करके, ट्रांज़िशन स्टेट में होने वाले बदलावों के हर कॉम्बिनेशन के लिए, अलग-अलग AnimationSpec तय किया जा सकता है. ज़्यादा जानकारी के लिए, 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 के एक्सटेंशन फ़ंक्शन के तौर पर उपलब्ध हैं. targetState के लिए Transition.AnimatedVisibility और Transition.AnimatedContent, Transition से लिया जाता है. साथ ही, Transition's targetState बदलने पर, ज़रूरत के हिसाब से एंटर, एक्ज़िट, और sizeTransform ऐनिमेशन ट्रिगर होते हैं. इन एक्सटेंशन फ़ंक्शन की मदद से, AnimatedVisibility/AnimatedContent में मौजूद सभी एंटर, एक्ज़िट, और sizeTransform ऐनिमेशन को Transition में शामिल किया जा सकता है. इन एक्सटेंशन फ़ंक्शन की मदद से, AnimatedVisibility/AnimatedContent की स्टेट में होने वाले बदलाव को बाहर से देखा जा सकता है. AnimatedVisibility के इस वर्शन में, बूलियन visible पैरामीटर के बजाय, एक लैम्डा लिया जाता है. यह लैम्डा, पैरंट ट्रांज़िशन की टारगेट स्टेट को बूलियन में बदलता है.

ज़्यादा जानकारी के लिए, 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")
            }
        }
    }
}

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

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

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

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, animateFloat या animateValue की मदद से चाइल्ड ऐनिमेशन जोड़े जा सकते हैं. ऐनिमेशन की खास जानकारी तय करने के लिए, 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 के साथ सीधे इंटरैक्ट नहीं करते. हालांकि, हाई-लेवल एपीआई की मदद से, इसकी कुछ सुविधाओं को अपनी पसंद के मुताबिक बनाया जा सकता है. ऐनिमेशन को अपनी पसंद के मुताबिक बनाना और AnimationVector और AnimationSpec के बारे में ज़्यादा जानकारी के लिए, देखें.

लो-लेवल ऐनिमेशन एपीआई के बीच संबंध
पहली इमेज. लो-लेवल ऐनिमेशन एपीआई के बीच संबंध.

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

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

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 में ऐनिमेट होता है. बूलियन वैल्यू में होने वाले किसी भी बाद के बदलाव से, दूसरे रंग के लिए ऐनिमेशन शुरू हो जाता है. अगर वैल्यू में बदलाव होने पर कोई ऐनिमेशन चल रहा है, तो Compose उस ऐनिमेशन को रद्द कर देता है. साथ ही, नया ऐनिमेशन, मौजूदा स्नैपशॉट वैल्यू से मौजूदा वेलोसिटी के साथ शुरू होता है.

यह Animatable एपीआई, पिछले सेक्शन में बताए गए animate*AsState को लागू करने का तरीका है. Animatable का सीधे तौर पर इस्तेमाल करने से, कई तरीकों से बेहतर कंट्रोल मिलता है:

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

ज़्यादा जानकारी के लिए, जेस्चर और ऐनिमेशन देखें.

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

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

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

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 से सेट की गई वैल्यू से शुरू होती है और समय के साथ धीरे-धीरे कम होती जाती है.