Jetpack Compose

للألعاب وتحريك الأشكال

وجهات كريم

wajahatkarim.com

WajahatKarim

🔥 خبير من جوجل في مجال الأندرويد (GDE) .
📱 مبرمج أندرويد 💻 مشارك في العناصر مفتوحة المصدر
  📝 كاتب تقني 🎤 متحدث في المؤتمرات
 compose_version = '1.0.0-beta05' 

تم التحديث لنسخة

Jetpack Compose

مجموعة أدوات حديثة لواجهة المستخدم  لتبسيط وتسريع برمجة شاشات المستخدم على بإستخدام كود أقل وأدوات قوية Android نظام Kotlin ولغة

 Jetpack Compose تم عمله بـ

Jetpack Compose تحريك الأشكال في

animateContentSize() معدل

 AnimatedVisibility Composable

 animate*AsState() تحريك عنصر واحد بإستخدام

Animatable تكرار الحركة بإستخدام

أستخدام تحويل الأشكال لعمل تحريك مثالي

Jetpack Compose تحريك الأشكال في

animateContentSize() معدل

 AnimatedVisibility Composable

 animate*AsState() تحريك عنصر واحد بإستخدام

Animatable تكرار الحركة بإستخدام

أستخدام تحويل الأشكال لعمل تحريك مثالي

animateContentSize() معدل

animateContentSize() معدل

@Composable
fun ExpandableText() {
    val shortText = "Click me"
    val longText = "Very long text passage that spans
    		\nacross multiple lines, paragraphs
            	\nand pages"
    var short by remember { mutableStateOf(true) }
    Box(
        modifier = Modifier
            .background(
                Color.Blue,
                RoundedCornerShape(15.dp)
            )
            .clickable { short = !short }
            .padding(20.dp)
            .wrapContentSize()
            .animateContentSize()
    ) {
        Text(
            if (short) {
                shortText
            } else {
                longText
            },
            style = TextStyle(color = Color.White)
        )
    }
}
@Composable
fun PortraitModeImage() {
  var portraitMode by remember { mutableStateOf(true) }
  Box(
    Modifier.clickable { portraitMode = !portraitMode }
      .sizeIn(maxWidth = 300.dp, maxHeight = 300.dp)
      .background(
          if (portraitMode) Color.Yellow else Color.Green)
      .animateContentSize(
          animSpec = tween(500, easing = LinearEasing),
          endListener = { startSize, endSize ->  
             Log.d("Compose", "$startSize -> $endSize")
          }
      )
      .aspectRatio(if (portraitMode) 3 / 4f else 16 / 9f)
    ) {
        Text(
          if (portraitMode) {
             "3 : 4"
          } else {
             "16 : 9"
          },
          style = TextStyle(color = Color.Black)
      )
    }
}

animateContentSize() معدل

Jetpack Compose تحريك الأشكال في

animateContentSize() معدل

 AnimatedVisibility Composable

 animate*AsState() تحريك عنصر واحد بإستخدام

Animatable تكرار الحركة بإستخدام

أستخدام تحويل الأشكال لعمل تحريك مثالي

 AnimatedVisibility Composable

AnimatedVisbility Composable

@OptIn(ExperimentalAnimationApi::class)
@Composable
fun VisibilityAnimationFAB() {
    var expanded by remember { mutableStateOf(true) }
    FloatingActionButton(
        onClick = { expanded = !expanded },
    ) {
        Row(Modifier.padding(start = 16.dp, end = 16.dp)) {
            Icon(
                imageVector = Icons.Default.Favorite,
                contentDescription = "Favorite Icon",
                Modifier.align(Alignment.CenterVertically)
            )
            AnimatedVisibility(
                expanded,
                modifier = Modifier.align(Alignment.CenterVertically)
            ) {
                Text(modifier = Modifier.padding(start = 8.dp), text = "Like")
            }
        }
    }
}
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun VisibilityAnimationFAB() {
    var expanded by remember { mutableStateOf(true) }
    FloatingActionButton(
        onClick = { expanded = !expanded },
    ) {
        Row(Modifier.padding(start = 16.dp, end = 16.dp)) {
            Icon(
                imageVector = Icons.Default.Favorite,
                contentDescription = "Favorite Icon",
                Modifier.align(Alignment.CenterVertically)
            )
            AnimatedVisibility(
                expanded,
                modifier = Modifier.align(Alignment.CenterVertically),
                enter = slideInHorizontally(
                    initialOffsetX = { 300 }, 
                    animSpec = tween(durationMillis = 2000)
                ),
                exit = slideOutVertically(
                    targetOffsetY = { 100 }, 
                    animSpec = tween(durationMillis = 2000)
                )
            ) {
                Text(text = "Like")
            }
        }
    }
}

Default

Enter

Enter + Exit

AnimatedVisbility Composable

cs.android.com

LazyColumn في AnimatedVisibility

Composables في عدة AnimatedVisibility

AnimatedVisbility Composable

Jetpack Compose تحريك الأشكال في

animateContentSize() معدل

 AnimatedVisibility Composable

 animate*AsState() تحريك عنصر واحد بإستخدام

Animatable تكرار الحركة بإستخدام

أستخدام تحويل الأشكال لعمل تحريك مثالي

 

 

 animate*AsState() تحريك عنصر واحد بإستخدام

 

 

animate*AsState()

لتحريك قيمة عنصر واحد fire-and-forget وظائف

تم إلغاءه animate() سابقا

للألوان animatedColorAsState()

 DP لقيم الأحجام animatedDpAsState()

... والمزيد

@Composable
fun ScaleAndColorAnimation() {
    val enabled = remember { mutableStateOf(true) }
    
    val color: Color by animateColorAsState(
        if (enabled.value) Color.Blue else Colors.green)
        
    val height: Dp by animateDpAsState(if (enabled.value) 40.dp else 60.dp)
    val width: Dp by animateDpAsState(if (enabled.value) 150.dp else 300.dp)
    
    Button(
        onClick = { enabled.value = !enabled.value },
        colors = ButtonDefaults.buttonColors(backgroundColor = color),
        modifier = Modifier
            .padding(16.dp)
            .preferredHeight(height)
            .preferredWidth(width),
    ) {
        Text("Scale & Color")
    }
}

1 - animate*AsState()

2 - animate*AsState()

@Composable
fun GenderSelectAnimation() {
  val female = remember { mutableStateOf(true) }
  Row(horizontalArrangement = Arrangement.Center,
      modifier = Modifier.padding(8.dp).fillMaxWidth(),
      verticalAlignment = Alignment.CenterVertically
  ) {
      Image(
        painter = painterResource(R.drawable.male),
        contentDescription = "Male Image",
        contentScale = ContentScale.Crop,
        modifier = Modifier
            .preferredSize(animateDpAsState(if (female.value) 100.dp else 250.dp).value)
            .border(width = animateDpAsState(if (female.value) 0.dp else 4.dp).value,
                color = animateColorAsState(if (female.value) Color.Transparent else Color.Red).value)
            .padding(8.dp)
            .clickable { female.value = !female.value }
      )
      Image(
        painter = painterResource(R.drawable.female),
        contentDescription = "Female Image",
        contentScale = ContentScale.Crop,
        modifier = Modifier
        // ... 
        // Like previous image
      )
  }
}

animate*AsState() أمثلة على

github.com/Gurupreet/ComposeCookBook

Jetpack Compose تحريك الأشكال في

animateContentSize() معدل

 AnimatedVisibility Composable

 animate*AsState() تحريك عنصر واحد بإستخدام

Animatable تكرار الحركة بإستخدام

أستخدام تحويل الأشكال لعمل تحريك مثالي

 

 

 

Animatable تكرار الحركة بإستخدام

 

Animatable تكرار الحركة بإستخدام

@Composable
fun HeartBeatDemo() {
    val animScale = remember { Animatable(initialValue = 1f) }
    val animColor = remember { Animatable(initialValue = Color.Red) }

    LaunchedEffect(animScale) {
        animScale.animateTo(
            targetValue = 2f,
            animationSpec = infiniteRepeatable(
                animation = tween(durationMillis = 300, delayMillis = 1000),
                repeatMode = RepeatMode.Reverse
            )
        )
    }

    LaunchedEffect(animColor) {
        animColor.animateTo(
            targetValue = Color.Blue,
            animationSpec = repeatable(iterations = 2,
                animation = tween(durationMillis = 300, delayMillis = 1000),
                repeatMode = RepeatMode.Reverse
            )
        )
    }

    Image(imageVector = Icons.Default.Favorite,
    	contentDescription = "Favourite Icon",
        modifier = Modifier.padding(10.dp).size((40*animScale.value).dp),
        colorFilter = ColorFilter.tint(animColor.value)
    )
}

للسماح لك برسم أي شكل عليه Spacer()

@Composable
fun Canvas
(
   modifier: Modifier, 
   onDraw: DrawScope.() -> Unit
) = Spacer(modifier.drawBehind(onDraw))

Compose Canvas 101

drawing API - DrawScope

drawRect()

drawOval()

drawLine()

drawImage()

drawRoundRect()

drawCircle()

drawArc()

drawPath()

fun DrawScope.drawMyShape() { }

Compose Canvas 101

Compose Canvas 101

Canvas مثال شكل معدل بإستخدام

Canvas(modifier = Modifier.fillMaxSize()) {
    drawCircle(
        color = Color.Red,
        radius = 300f
    )

    drawCircle(
        color = Color.Green,
        radius = 200f
    )

    drawCircle(
        color = Color.Blue,
        radius = 100f
    )
}

Canvas بإستخدام Animatable

@Composable
fun MovingSquare() {
    val animPosX = remember { Animatable(initialValue = 0f) }
    
    LaunchedEffect(animPosX) {
        animPosX.animateTo(
            targetValue = 500f,
            animationSpec = infiniteRepeatable(
                animation = tween(durationMillis = 1000)
            )
        )
    }

    Canvas(modifier = Modifier.preferredSize(100.dp), onDraw = {
        withTransform({
            translate(left = animPosX.value)
        }) {
            drawRect(color = Color.Red)
        }
    })
}

مثال بسيط في تحريك مربع

Canvas أمثلة أكثر على

يعتمد بالكامل على الرياضيات والرسم

 github.com/wajahatkarim3/DinoCompose

github.com/alexjlockwood/bees-and-bombs-compose/

Jetpack Compose تحريك الأشكال في

animateContentSize() معدل

 AnimatedVisibility Composable

 animate*AsState() تحريك عنصر واحد بإستخدام

Animatable تكرار الحركة بإستخدام

أستخدام تحويل الأشكال لعمل تحريك مثالي

أستخدام تحويل الأشكال لعمل تحريك مثالي

class DemoActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContent {
         DemoTheme {
            Scaffold(
               topBar = {
                  TopAppBar( /* App Bar */ )
               },
               floatingActionButton = {
                  ExplodingFabButton()
               },
               bodyContent = {
                  // Content Composables
              }
           }
        }
    }
}

تحويل الأشكال - 1

enum class FabSizeState {
    NORMAL, EXPLODED
}

@Composable
fun ExplodingFabButton() {
   var fabSizeState by remember { mutableStateOf(FabSizeState.NORMAL) }
   
   val fabTransition: Transition<FabSizeState> = updateTransition(fabSizeState)
   
   val fabSize: Float by fabTransition.animateFloat() { state ->
        when (state) {
            FabSizeState.NORMAL -> 80f
            FabSizeState.EXPLODED -> 5000f
        }
    }

    val fabColor: Color by fabTransition.animateColor() { state ->
        when (state) {
            FabSizeState.NORMAL -> secondaryColor
            FabSizeState.EXPLODED -> primaryColor
        }
    }
}

تحويل الأشكال - 2

@Composable
fun ExplodingFabButton() {
   var fabSizeState by remember { mutableStateOf(FabSizeState.NORMAL) }
   
   val fabTransition: Transition<FabSizeState> = updateTransition(fabSizeState)
   
   val fabSize: Float by fabTransition.animateFloat() { /* from previous slide */ }
   val fabColor: Color by fabTransition.animateColor() { /* from previous slide */ }
      
   FloatingActionButton(
        onClick = {
            fabSizeState = if (fabSizeState == FabSizeState.NORMAL)
                FabSizeState.EXPLODED
            else FabSizeState.NORMAL
        },
        modifier = Modifier.size(fabSize.dp),
        backgroundColor = fabColor
    ) {
        Icon(
            imageVector = Icons.Default.Add,
            contentDescription = "Add"
        )
    }
}

تحويل الأشكال - 3

val fabSize: Float by fabTransition.animateFloat(
        transitionSpec = {
            when {
                FabSizeState.NORMAL isTransitioningTo FabSizeState.EXPLODED -> {
                    keyframes {
                        durationMillis = 1000
                        80f at 0
                        35f at 200
                        5000f at 1000
                    }
                }
                FabSizeState.EXPLODED isTransitioningTo FabSizeState.NORMAL -> {
                    tween(durationMillis = 1000, easing = FastOutSlowInEasing)
                }
                else -> snap()
            }
        }
    ) { state ->
        when (state) {
            FabSizeState.NORMAL -> 80f
            FabSizeState.EXPLODED -> 5000f
        }
    }

تحويل الأشكال - 4

تحويل الأشكال - 5

تحويل الأشكال - أمثلة

https://joebirch.co/

https://www.raywenderlich.com/13282144-jetpack-compose-animations-tutorial-getting-started

معاين التحريك

ماذا أختار لتحريك العناصر؟

https://developer.android.com/jetpack/compose/animation

المصادر

Alex Lockwood

https://github.com/alexjlockwood/bees-and-bombs-compose

https://github.com/alexjlockwood/android-2048-compose

Joe Birch

https://joebirch.co/exploring-jetpack-compose/

Gurupreet Singh

https://github.com/Gurupreet/ComposeCookBook

Leland Richardson

https://www.twitch.tv/intelligibabble

مصادر جوجل الرسمية

https://developer.android.com/courses/pathways/compose

Vinay Gaba

https://github.com/vinaygaba/Learn-Jetpack-Compose-By-Example

شاكر لحضوركم اليوم

الكود بالمحاضرة مرفوع على

https://github.com/wajahatkarim3/droidcon2020

WajahatKarim

wajahatkarim.com/subscribe

wajahatkarim.com