أسرار تحريك الواجهات في Compose خطوة بخطوة مع أمثلة عملية

التحريك في Compose للمبتدئين: كل ما تحتاجه في دليل واحد
تحريك الأيقونات مثل المحترفين: دليل تفاعلي مع كود جاهز

شرح animations في kotlin compose بكل سهوله

يقدم Kotlin Compose العديد من الميزات التي تجعله قويًا في عملية الرسوم المتحركة، بما في ذلك:التحكم السلس في الرسوم المتحركة  , يوفر Jetpack Compose المرونة في إنشاء الرسوم المتحركة باستخدام واجهات برمجة التطبيقات البسيطة، مثل animateFloatAsState وanimateDpAsState. يمكنك التحكم بسهولة في حالة الرسوم المتحركة بناءً على الحالة الحالية لواجهة المستخدم.


دمج الرسوم المتحركة في مكونات واجهة المستخدم: 

يسمح التركيب بدمج الرسوم المتحركة بسلاسة في أي مؤلف، مما يجعل من السهل التحكم في الرسوم المتحركة بناءً على حالة المكون، مثل جعل العناصر تظهر وتختفي، والتحكم في حجمها أو موقعها.التفاعل مع المستخدم: 

 يستخدم  compose آليات قوية لإدارة الرسم والرسوم المتحركة لتوفير تجربة مستخدم سلسة، حتى في التطبيقات الكبيرة. ومن السهل تخصيص الرسوم المتحركة لتناسب احتياجات تطبيقك باستخدام Tween أو Spring أو Keyframes.


انميشن لتنفيذ عملية حذف للعناصر في كوتلن كومبوس

viewmodel.class


@HiltViewModel
class CounterViewModel : ViewModel() {
    private val _state = MutableStateFlow(ItemUiState())
    val state = _state.asStateFlow() 
    
     ...
  
fun onClick(selectItem: ItemModel) {
        _state.update {
            it.copy(
                model = it.model.filterNot { itemModel -> itemModel.text == selectItem.text }
            )
        }
    }
}

// view + controller

@Composable
fun CounterController(viewModel:CounterViewModel = hiltViewModel()) {
    val state by viewModel.state.collectAsState()

    ShowItems(
        state = state,
        onClick = viewModel::onClick
    )

}

@Composable
fun ShowItems(state: ItemUiState = ItemUiState(),onClick:(ItemModel)-> Unit = {}) {
   items(count = state.model.size, key = { state.model[it].text }) { index ->
                    val item = state.model[index]
LazyColumn(
                contentPadding = PaddingValues(15.dp),
                verticalArrangement = Arrangement.spacedBy(10.dp),
                modifier = Modifier
                    .fillMaxSize()
                    .padding(paddingValues)
            ) {
                items(count = state.model.size, key = { state.model[it].text }) { index ->
                    val item = state.model[index]
                    Card(
                        shape = RoundedCornerShape(15.dp),
                        modifier = Modifier
                            .fillMaxWidth()
                            .combinedClickable(
                                onClick = { onClick(item) },
                            ).padding(bottom = expandedPadding.coerceAtLeast(0.dp))
                            .animateItemPlacement()
                    )
}


شرح الكود تنفيذ عملية الحذف

ما هي مكونات الكود؟ نحتاج أولاً إلى فهم مكونات الكود. يحتوي الكود على جزأين رئيسيين اولا : ViewModel وهو المسؤول عن تخزين وإدارة الحالة.ثانيا UI: والتي تعرض العناصر على الشاشة وتتفاعل مع المستخدم.


ما هي وظيفة ViewModel ؟

الجزء الأول من التعليمات البرمجية هو CounterViewModel الذي يستخدم مكتبة Hilt لحقن التبعية. يحتوي هذا الرمز على MutableStateFlow لتخزين حالة ItemUiState , توجد وظيفة onClick تأخذ عنصرًا محددًا (ItemModel) وتزيله من القائمة المخزنة مؤقتًا.

  • تم وضع key = { state.model[it].text } , لمعرفة key الذي سوف يتم الحذف عنده وترتيب ما اسفله
  • animateItemPlacement() لتنفيذ عملية الانميشن بدون تعقيد


انميشن خلال عملية padding في kotlin compose

@Composable
fun ShowItems(state: ItemUiState = ItemUiState(),onClick:(ItemModel)-> Unit = {}) {
var expanded by remember { mutableStateOf(false) }

    val expandedPadding by animateDpAsState(
        if (expanded) 20.dp else 0.dp,
        animationSpec = spring(
            dampingRatio = Spring.DampingRatioMediumBouncy,
            stiffness = Spring.StiffnessLow
        )
    )
                    Card(
                        shape = RoundedCornerShape(15.dp),
                        modifier = Modifier
                            .fillMaxWidth()
                            .combinedClickable(
                             onLongClick = {
                                    expanded = !expanded
                                },
                            ).padding(bottom = expandedPadding.coerceAtLeast(0.dp))
                            .animateItemPlacement()
                    )
}

شرح كود انميشن التوسيع

var expanded : هو الجزء الذي سوف يعرفنا اذا كان العنصر مفتوح ام مغلق.
onLongClick : يتم هنا تغيير حالة العنصر
expandedPadding : عمل padding مع انميشن وتخصصيه عن طريق animationSpec
padding : هنا تحدث عملية الانميشن ولكن قمنا باضافة expandedPadding حتى يكون اقل قيمه 0 حتى لا يحدث معك مشاكل

شرح AnimatedVisibility واضافة تاثيرات عليه

                // enter = fadeIn() , exit = fadeOut()
                // enter = slideInHorizontally() , exit = slideOutHorizontally()
                // enter = scaleIn() , exit = scaleOut()
                // enter = expandIn() , exit = shrinkOut()
                // enter = fadeIn() + scaleIn() , exit = fadeOut() + scaleOut()
                // enter = slideInHorizontally{it} , exit = slideOutHorizontally{-it}

                AnimatedVisibility(visible = isCheck , enter = slideIn{ IntOffset(-it.width,0) } , exit = slideOut{ IntOffset(it.width,0) }) {
                    Text(text = "Test")
                }

شرح انواع التاثرات في الانميشن داخل كوتلن كومبوس


FadeIn () / FadeOut (): 
يجعل العنصر يتلاشى عن طريق زيادة الشفافية تدريجيًا من 0 إلى 1.FadeOut(): تلاشي العنصر عن طريق تقليل الشفافية تدريجيًا من 1 إلى 0

SlideInHorizontally () / SlideOutHorizontally () :
نقل العنصر من خارج الشاشة أفقيًا إلى الداخل.SlideOutHorizontally (): 
حرك العنصر أفقيًا خارج الشاشة.تسمح هذه الرسوم المتحركة للعناصر بالتحرك أفقيًا، مما يعطي تأثيرًا انزلاقيًا عند الدخول أو الخروج.

scaleIn() / scaleOut():
ينمو العنصر تدريجيًا من حجم أصغر (عادةً 0) إلى حجمه الطبيعي.ScaleOut(): يقوم بتحجيم العنصر تدريجيًا من حجمه الطبيعي إلى حجم أصغر (عادةً 0).يستخدم للتكبير والتصغير عند التكبير والتصغير
expandIn() / shrinkOut():
يقوم بتوسيع العنصر بشكل تدريجي، أي أنه يبدأ صغيرًا ويمتد إلى الحد الأقصى لحجمه.ShrinkOut(): تقليص العنصر تدريجيًا حتى يختفي، أي أنه يبدأ بأقصى حجم له ويتقلص حتى يختفي.

fadeIn() + scaleIn() / fadeOut() + scaleOut():
 يجمع بين تأثيرات التلاشي والتكبير، حيث يظهر العنصر تدريجيًا ويصبح أكبر في نفس الوقت.FadeOut() +scaleOut(): الجمع بين تأثيرات التلاشي والتكبير، حيث يتلاشى العنصر تدريجيًا للداخل والخارج في نفس الوقت.

slideInHorizontally{ it } / slideOutHorizontally{ -it }:
ينقل العنصر أفقيًا من خارج الشاشة إلى الداخل بمقدار محدد (استنادًا إلى القيمة المحددة).SlideOutHorizontally{-it}: انقل العنصر أفقيًا خارج الشاشة بالقيمة المقابلة للقيمة المحددة.الرؤية المتحركة:يتحكم في مظهر/اختفاء العنصر بناءً على قيمة isCheck.

AnimatedVisibility
 هنا هي SlideIn وslideOut والتي تنقل العنصر أفقيًا باستخدام إحداثيات محددة مثل IntOffset.تُستخدم كل هذه الرسوم المتحركة لتوفير تأثيرات بصرية سلسة وجذابة عند إظهار العناصر أو إخفائها في واجهة المستخدم.

حيل الأنيميشن التي ستغير طريقة تصميمك للتطبيقات للأبد

AnimatedVisibility(visible = isCheck , enter = slideIn{ IntOffset(-it.width,0) } , exit = slideOut{ IntOffset(it.width,0) }) {
                    Box(modifier = Modifier
                        .clip(CircleShape)
                        .background(Color.Yellow)) {
                        Icon(
                            Icons.Filled.Menu,
                            "menu",
                            modifier = Modifier
                                .padding(16.dp)
                                .animateEnterExit(
                                    enter = slideInVertically { -it },
                                    exit = slideOutVertically { it },
                                ))
                    }

                }
تتضمن الرسوم المتحركة التحكم في مظهر واختفاء مربع يحتوي على أيقونة في واجهة المستخدم باستخدام تأثيرات بصرية ديناميكية.
 دعونا نحلل الكود بالتفصيل 
احسب الإزاحة باستخدام IntOffset ( it . width , 0 )، مما يعني أن العنصر يتحرك أفقيًا إلى اليمين لمغادرة الشاشة.يتم تطبيق الرسوم المتحركة للدخول والخروج على الكود باستخدام animateEnterExitعند إدراج الأيقونة، فإنها تتحرك عموديًا من أعلى الشاشة إلى مكانها

هل تريد تحسين واجهة المستخدم؟ جرب هذه الأنيميشن الاحترافية الآن

var message by remember { mutableStateOf("") }

Button(onClick = {
                    message += "GeeCoders"
                }) {

                }

                Box(modifier = Modifier
                    .padding(15.dp)
                    .animateContentSize(
                        animationSpec = tween(2000)
                    )){
                    Text("message : ${message}")
                }
في هذا الكود، يتم تطبيق رسم متحرك على عنصر Box عندما يتغير المحتوى الموجود بداخله. دعنا نحلل الكود بالتفصيل:
عند النقر على الزر، يتم تعديل نص متغير الرسالة بإضافة كلمة "GeeCoders". ونتيجة لذلك، يتغير طول النص المعروض في أي مكان آخر في الواجهة.
وهذا يعني أن الرسوم المتحركة تستمر 2000 مللي ثانية (ثانيتين). 
أي أن التغيير في حجم الصندوق سيكون بدون مشاكل خلال فترة زمنية معينة.

سر الأنيميشن المستمرة في واجهات التطبيقات باستخدام Kotlin Compose!

val colorTransition = rememberInfiniteTransition()
    val color by colorTransition.animateColor(
        initialValue = Color.Blue,
        targetValue = Color.Green,
        animationSpec = InfiniteRepeatableSpec(
            tween(2000),
            repeatMode = RepeatMode.Reverse
        ),
        label = "Change Color"
    )

Box(modifier = Modifier
                    .padding(15.dp)
                    .background(color)
                    .animateContentSize(
                        animationSpec = tween(2000)
                    )){
                    Text("message : ${message}")
                }
في هذا الكود، يتم تطبيق انتقال لا نهائي على عنصر Box ويتم تغيير لونه ديناميكيًا باستخدام مكتبة Jetpack Compose. دعنا نحلل الكود بالتفصيل:

يتم إنشاء InfiniteTransition باستخدام RememberInfiniteTransition(). يتيح لك هذا النوع من الانتقال تشغيل رسوم متحركة مستمرة ومتكررة دون توقف.

اللون المتحرك: يتم إنشاء الرسوم المتحركة الملونة باستخدام animateColor:القيمة الأولية = اللون الأزرق: اللون الرئيسي هو الأزرق. القيمة المستهدفة = اللون . الأخضر: الهدف النهائي للرسوم المتحركة هو اللون الأخضر.
AnimationSpec = InfiniteRepeatableSpec(...):هنا، يتم استخدام InfiniteRepeatableSpec لتكرار الرسوم المتحركة إلى ما لا نهاية. توين (2000): تحدد هذه الخاصية مدة الرسوم المتحركة إلى 2000 مللي ثانية (ثانيتين).
RepeatMode.Reverse : وهذا يعني أن الرسوم المتحركة سوف تنعكس بعد إصابة الهدف (أي أنها ستتغير من الأزرق إلى الأخضر، من الأخضر إلى الأزرق). حيث يتغير لون الصندوق باستمرار بألوان متحركة

كيف تجعل تجربة المستخدم أكثر جاذبية بالمقاسات المتحركة

val transition = rememberInfiniteTransition()
    val sizeTransition by transition.animateFloat(
        initialValue = 15f,
        targetValue = 30f,
        animationSpec = InfiniteRepeatableSpec(
            tween(2000),
            repeatMode = RepeatMode.Reverse
        ),
        label = "Change Color"
    )
    
    Box(modifier = Modifier
                    .padding(15.dp)
                    .height(sizeTransition.dp)
                    .animateContentSize(
                        animationSpec = tween(2000)
                    )
تحريك العائمة:يتم استخدام animateFloat لتحريك قيمة عددية عائمة (تعويم):
القيمة الأولية = 15f: القيمة الأولية لحجم العنصر هي 15
targetValue = 30f: الهدف النهائي للرسوم المتحركة هو 30.AnimationSpec =
InfiniteRepeatableSpec(...) :مواصفات الرسوم المتحركة محددة هنا:توين (2000): زمن الانتقال بين قيمتين هو 2000 ميلي ثانية (2 ثانية) : سيتم عكس الرسم المتحرك بمجرد الوصول إلى الهدف، مما يعني أن الحجم سيتغير من 15 إلى 30 ثم ينخفض ​​من 30 إلى 15.

كيف تضيف أنيميشن تفاعلية مبهرة في تطبيقاتك باستخدام Kotlin/Compose

val sizeTransition = updateTransition(targetState = isCheck)
  
  
    val changeColor by sizeTransition.animateColor {
        if (it) Color.Red else Color.Blue
    }
    
    val changeSize by sizeTransition.animateDp(
        label = "",
        transitionSpec = {
            spring(
                dampingRatio = Spring.DampingRatioMediumBouncy,
                stiffness = Spring.StiffnessMedium,
            )
        },
    ) {
        if (it) 100.dp else 50.dp
    }
    
    val randColor = remember { Animatable(Color.Blue) }


        
    Box(modifier = Modifier
                    .clip(CircleShape)
                    .width(changeSize)
                    .background(changeColor)) {}
                    
                    
     Box(
          modifier = Modifier
             .clip(CircleShape)
             .background(randColor.value)
                ) {}

LaunchedEffect(key1 = Unit) {
        val colors = listOf(Color.Blue,Color.Green,Color.Yellow,Color.Magenta,Color.Red)
        colors.forEach() {
            randColor.animateTo(it)
            delay(2000)
        }
    }
updateTransition
تحديث الانتقال (targetState = isCheck):يتم إنشاء الانتقال بناءً على حالة isCheck. اعتمادًا على تغيير الحالة هذا، سيتغير حجم الرسم المتحرك ولونه.

animateColor
عندما يكون isCheck صحيحًا، سيتحول اللون إلى اللون الأحمر وعندما يكون خطأ، سيتحول إلى اللون الأزرق.

animateDp
إذا كانت قيمة isCheck صحيحة، فسيتم ضبط حجم العنصر على 100 dp، وإذا كان خطأ، فسيكون الحجم 50 dp.ربيع (... ): يتم تعيين مواصفات الرسوم المتحركة باستخدام خاصية الربيع لإنشاء تأثير الارتداد عند تغييرها، مما يوفر تجربة رسوم متحركة مرنة وجذابة.

Animatable
يتم استخدام Animatable لتغيير اللون ديناميكيًا بناءً على قائمة محددة من الألوان

Box الأول
يتأثر مربع الاختيار هذا بحجم ولون مختلفين اعتمادًا على حالة isCheck:عند تغيير isCheck، يتم تغيير حجم الصندوق (إلى 100 dp أو 50 dp) واللون (إلى الأحمر أو الأزرق) وفقًا للحالة.

 Box الثاني
يتغير لون هذا المربع بشكل مستمر بناءً على قائمة الألوان

LaunchedEffect
تقوم هذه الوظيفة بشكل دوري بإجراء سلسلة من تغييرات اللون

تعليقات