মান-ভিত্তিক অ্যানিমেশন

এই পৃষ্ঠাটি জেটপ্যাক কম্পোজে মান-ভিত্তিক অ্যানিমেশন তৈরি করার পদ্ধতি বর্ণনা করে, যেখানে API গুলিকে কেন্দ্র করে মানগুলিকে তাদের বর্তমান এবং লক্ষ্য অবস্থার উপর ভিত্তি করে অ্যানিমেট করা হয়।

animate*AsState দিয়ে একটি একক মান অ্যানিমেট করুন

animate*AsState ফাংশনগুলি হল Compose-এ একটি একক মান অ্যানিমেট করার জন্য সহজবোধ্য অ্যানিমেশন API। আপনি কেবল লক্ষ্য মান (অথবা শেষ মান) প্রদান করেন এবং API বর্তমান মান থেকে নির্দিষ্ট মান পর্যন্ত অ্যানিমেশন শুরু করে।

নিচের উদাহরণটি এই API ব্যবহার করে আলফা অ্যানিমেট করে। animateFloatAsState এ টার্গেট মানটি মোড়ানোর মাধ্যমে, আলফা মানটি এখন প্রদত্ত মানগুলির মধ্যে একটি অ্যানিমেশন মান (এই ক্ষেত্রে 1f বা 0.5f )।

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 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 সাথে ট্রানজিশন ব্যবহার করুন

Transition এর এক্সটেনশন ফাংশন হিসেবে AnimatedVisibility এবং AnimatedContent পাওয়া যায়। Transition.AnimatedVisibility and Transition.AnimatedContent এর জন্য targetState Transition থেকে উদ্ভূত হয় এবং Transition এর targetState পরিবর্তন হলে প্রয়োজন অনুসারে enter, exit, এবং sizeTransform অ্যানিমেশন ট্রিগার করে। এই এক্সটেনশন ফাংশনগুলি আপনাকে সমস্ত enter, exit, এবং sizeTransform অ্যানিমেশনগুলিকে Transition এ উত্তোলন করতে দেয় যা অন্যথায় AnimatedVisibility / AnimatedContent এর অভ্যন্তরীণ হবে। এই এক্সটেনশন ফাংশনগুলির সাহায্যে, আপনি বাইরে থেকে AnimatedVisibility / AnimatedContent এর অবস্থার পরিবর্তন পর্যবেক্ষণ করতে পারেন। একটি বুলিয়ান visible প্যারামিটারের পরিবর্তে, AnimatedVisibility এর এই সংস্করণটি একটি ল্যাম্বডা নেয় যা প্যারেন্ট ট্রানজিশনের টার্গেট অবস্থাকে একটি বুলিয়ানে রূপান্তর করে।

বিস্তারিত জানার জন্য 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")
            }
        }
    }
}

একটি ট্রানজিশনকে ক্যাপসুলেট করুন এবং এটিকে পুনরায় ব্যবহারযোগ্য করুন

সহজ ব্যবহারের ক্ষেত্রে, আপনার UI-এর মতো একই কম্পোজেবলে ট্রানজিশন অ্যানিমেশন সংজ্ঞায়িত করা একটি বৈধ বিকল্প। তবে, অনেকগুলি অ্যানিমেটেড মান সহ একটি জটিল উপাদানের উপর কাজ করার সময়, আপনি অ্যানিমেশন বাস্তবায়নকে কম্পোজেবল UI থেকে আলাদা করতে চাইতে পারেন।

আপনি এমন একটি ক্লাস তৈরি করে এটি করতে পারেন যেখানে সমস্ত অ্যানিমেশন মান থাকবে এবং একটি 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 , 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)
)

নিম্ন-স্তরের অ্যানিমেশন API গুলি

পূর্ববর্তী বিভাগে উল্লিখিত সমস্ত উচ্চ-স্তরের অ্যানিমেশন API গুলি নিম্ন-স্তরের অ্যানিমেশন API গুলির উপর ভিত্তি করে তৈরি।

animate*AsState ফাংশনগুলি হল সহজবোধ্য API যা তাৎক্ষণিক মান পরিবর্তনকে অ্যানিমেশন মান হিসেবে রেন্ডার করে। এই কার্যকারিতাটি Animatable দ্বারা সমর্থিত, যা একটি একক মান অ্যানিমেট করার জন্য একটি কোরোটিন-ভিত্তিক API।

updateTransition একটি ট্রানজিশন অবজেক্ট তৈরি করে যা একাধিক অ্যানিমেটিং মান পরিচালনা করতে পারে এবং যখন কোনও অবস্থা পরিবর্তন হয় তখন সেগুলি চালাতে পারে। rememberInfiniteTransition অনুরূপ, কিন্তু এটি একটি অসীম ট্রানজিশন তৈরি করে যা অনির্দিষ্টকালের জন্য চলতে থাকা একাধিক অ্যানিমেশন পরিচালনা করতে পারে। এই সমস্ত API গুলি Animatable ছাড়া কম্পোজেবল, যার অর্থ আপনি কম্পোজিশনের বাইরে এই অ্যানিমেশনগুলি তৈরি করতে পারেন।

এই সমস্ত API গুলি আরও মৌলিক Animation API এর উপর ভিত্তি করে তৈরি। যদিও বেশিরভাগ অ্যাপ Animation সাথে সরাসরি ইন্টারঅ্যাক্ট করে না, আপনি উচ্চ-স্তরের API গুলির মাধ্যমে এর কিছু কাস্টমাইজেশন ক্ষমতা অ্যাক্সেস করতে পারেন। AnimationVector এবং AnimationSpec সম্পর্কে আরও তথ্যের জন্য Customize animations দেখুন।

নিম্ন-স্তরের অ্যানিমেশন API-এর মধ্যে সম্পর্ক
চিত্র ১. নিম্ন-স্তরের অ্যানিমেশন API-এর মধ্যে সম্পর্ক।

Animatable : কোরোটিন-ভিত্তিক একক মান অ্যানিমেশন

Animatable হল একটি ভ্যালু হোল্ডার যা animateTo ব্যবহার করে পরিবর্তনের সাথে সাথে মানটিকে অ্যানিমেট করতে পারে। এটি animate*AsState বাস্তবায়নের জন্য API। এটি ধারাবাহিক ধারাবাহিকতা এবং পারস্পরিক এক্সক্লুসিভনেস নিশ্চিত করে, যার অর্থ হল মান পরিবর্তন সর্বদা অবিচ্ছিন্ন থাকে এবং Compose যেকোনো চলমান অ্যানিমেশন বাতিল করে।

Animatable এর অনেক বৈশিষ্ট্য, যার মধ্যে animateTo ও রয়েছে, হল suspend ফাংশন। এর অর্থ হল আপনাকে এগুলিকে একটি উপযুক্ত coroutine স্কোপে মুড়িয়ে রাখতে হবে। উদাহরণস্বরূপ, আপনি LaunchedEffect composable ব্যবহার করে নির্দিষ্ট কী মানের সময়কালের জন্য একটি স্কোপ তৈরি করতে পারেন।

// 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 এর একটি উদাহরণ তৈরি এবং মনে রাখবেন। boolean flag ok এর মানের উপর নির্ভর করে, রঙটি Color.Green অথবা Color.Red এ অ্যানিমেট হয়। boolean মানের পরবর্তী যেকোনো পরিবর্তন অন্য রঙে একটি অ্যানিমেশন শুরু করে। যদি কোনও অ্যানিমেশন চলমান থাকে যখন মান পরিবর্তন হয়, তাহলে Compose অ্যানিমেশনটি বাতিল করে দেয় এবং নতুন অ্যানিমেশনটি বর্তমান স্ন্যাপশট মান থেকে বর্তমান বেগের সাথে শুরু হয়।

এই Animatable API হল পূর্ববর্তী বিভাগে উল্লিখিত animate*AsState জন্য অন্তর্নিহিত বাস্তবায়ন। Animatable সরাসরি ব্যবহার করলে বিভিন্ন উপায়ে আরও সূক্ষ্ম নিয়ন্ত্রণ পাওয়া যায়:

  • প্রথমত, Animatable একটি প্রাথমিক মান তার প্রথম লক্ষ্য মানের থেকে আলাদা হতে পারে। উদাহরণস্বরূপ, পূর্ববর্তী কোড উদাহরণে প্রথমে একটি ধূসর বাক্স দেখানো হয়েছে, যা তাৎক্ষণিকভাবে সবুজ বা লাল রঙে অ্যানিমেট হয়ে যায়।
  • দ্বিতীয়ত, Animatable কন্টেন্ট মানের উপর আরও বেশি ক্রিয়াকলাপ প্রদান করে, বিশেষ করে snapTo এবং animateDecay
    • snapTo বর্তমান মানটিকে তাৎক্ষণিকভাবে লক্ষ্য মানের সাথে সেট করে। এটি তখন কার্যকর যখন অ্যানিমেশনটি সত্যের একমাত্র উৎস নয় এবং অন্যান্য অবস্থার সাথে সিঙ্ক্রোনাইজ করতে হবে, যেমন টাচ ইভেন্ট।
    • animateDecay একটি অ্যানিমেশন শুরু করে যা প্রদত্ত বেগ থেকে ধীর হয়ে যায়। এটি ফ্লিং আচরণ বাস্তবায়নের জন্য কার্যকর।

আরও তথ্যের জন্য অঙ্গভঙ্গি এবং অ্যানিমেশন দেখুন।

ডিফল্টরূপে, Animatable Float এবং Color সমর্থন করে, তবে আপনি একটি TwoWayConverter প্রদান করে যেকোনো ডেটা টাইপ ব্যবহার করতে পারেন। আরও তথ্যের জন্য AnimationVector দেখুন।

আপনি একটি AnimationSpec প্রদান করে অ্যানিমেশন স্পেসিফিকেশন কাস্টমাইজ করতে পারেন। আরও তথ্যের জন্য AnimationSpec দেখুন।

Animation : ম্যানুয়ালি নিয়ন্ত্রিত অ্যানিমেশন

Animation হল সর্বনিম্ন স্তরের অ্যানিমেশন API। আমরা এখন পর্যন্ত যেসব অ্যানিমেশন দেখেছি তার বেশিরভাগই Animation উপর ভিত্তি করে তৈরি। Animation দুটি উপপ্রকার রয়েছে: TargetBasedAnimation এবং DecayAnimation

অ্যানিমেশনের সময় ম্যানুয়ালি নিয়ন্ত্রণ করার জন্য শুধুমাত্র Animation ব্যবহার করুন। Animation স্টেটলেস, এবং এতে জীবনচক্রের কোনও ধারণা নেই। এটি উচ্চ-স্তরের API-এর জন্য একটি অ্যানিমেশন গণনা ইঞ্জিন হিসেবে কাজ করে।

TargetBasedAnimation

অন্যান্য API গুলি বেশিরভাগ ব্যবহারের ক্ষেত্রে প্রযোজ্য, কিন্তু 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 দ্বারা সেট করা মান থেকে শুরু হয় এবং সময়ের সাথে সাথে ধীর হয়ে যায়।

{% অক্ষরে অক্ষরে %} {% এন্ডভারব্যাটিম %} {% অক্ষরে অক্ষরে %} {% এন্ডভারব্যাটিম %}