From 33a11e210113df097e512d94b0b2e5a571bc3586 Mon Sep 17 00:00:00 2001 From: Kaushal-Vasava Date: Thu, 5 Jan 2023 11:47:50 +0530 Subject: [PATCH 1/9] Add Surface to Greeting composable to enhance UI --- .../com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt index 187ee32..79878ac 100644 --- a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt @@ -28,6 +28,7 @@ class MainActivity : ComponentActivity() { } } } + /*** Composable functions : A composable function is a regular function annotated with @Composable. @@ -35,10 +36,12 @@ This enables your function to call other @Composable functions within it. You can see how the Greeting function is marked as @Composable. This function will produce a piece of UI hierarchy displaying the given input, String. Text is a composable function provided by the library. -***/ + ***/ @Composable fun Greeting(name: String) { - Text(text = "Hello $name!") + Surface(color = MaterialTheme.colorScheme.primary) { + Text(text = "Hello $name!") + } } @Preview(showBackground = true) From ec794aff20146d65e82c286887bfbba6ac10bf00 Mon Sep 17 00:00:00 2001 From: Kaushal-Vasava Date: Thu, 5 Jan 2023 12:04:16 +0530 Subject: [PATCH 2/9] Reused composable function --- .../apps/jetpackcomposebasic/MainActivity.kt | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt index 79878ac..c5b5990 100644 --- a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt @@ -4,6 +4,7 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -18,17 +19,22 @@ class MainActivity : ComponentActivity() { setContent { JetPackComposeBasicTheme { // A surface container using the 'background' color from the theme - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background - ) { - Greeting("Android") - } + MyApp(modifier = Modifier.fillMaxSize()) } } } } +@Composable +fun MyApp(modifier: Modifier = Modifier) { + Surface( + modifier = modifier, + color = MaterialTheme.colorScheme.background + ) { + Greeting("Android") + } +} + /*** Composable functions : A composable function is a regular function annotated with @Composable. @@ -48,6 +54,6 @@ fun Greeting(name: String) { @Composable fun DefaultPreview() { JetPackComposeBasicTheme { - Greeting("Android") + MyApp() } } \ No newline at end of file From 845c667e9b68395b0598482fb044e3af7e6a0577 Mon Sep 17 00:00:00 2001 From: Kaushal-Vasava Date: Thu, 5 Jan 2023 12:37:51 +0530 Subject: [PATCH 3/9] Add Row, Column and Button composable --- .../apps/jetpackcomposebasic/MainActivity.kt | 58 ++++++++++++++----- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt index c5b5990..0722d94 100644 --- a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt @@ -3,14 +3,12 @@ package com.lahsuak.apps.jetpackcomposebasic import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import com.lahsuak.apps.jetpackcomposebasic.ui.theme.JetPackComposeBasicTheme class MainActivity : ComponentActivity() { @@ -26,27 +24,55 @@ class MainActivity : ComponentActivity() { } @Composable -fun MyApp(modifier: Modifier = Modifier) { - Surface( - modifier = modifier, - color = MaterialTheme.colorScheme.background - ) { - Greeting("Android") +fun MyApp( + modifier: Modifier = Modifier, + names: List = listOf("World", "Kaushal") +) { + Column(modifier = modifier.padding(vertical = 4.dp)) { + for (name in names) { + Greeting(name) + } } } -/*** -Composable functions : +/** + * Composable functions : A composable function is a regular function annotated with @Composable. This enables your function to call other @Composable functions within it. You can see how the Greeting function is marked as @Composable. This function will produce a piece of UI hierarchy displaying the given input, String. Text is a composable function provided by the library. - ***/ + **/ + +/** + * 1. Column is Vertical linear layout of view system + * 2. Row is Horizontal linear layout of view system + * 3. Box is FrameLayout of view system + */ + +/** + * Button is a composable provided by the material3 package +which takes a composable as the last argument. +Since trailing lambdas can be moved outside of the parentheses, +you can add any content to the button as a child. For example, a Text + */ + @Composable fun Greeting(name: String) { - Surface(color = MaterialTheme.colorScheme.primary) { - Text(text = "Hello $name!") + Surface( + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp) + ) { + Row(modifier = Modifier.padding(24.dp)) { + Column(modifier = Modifier.weight(1f)) { + Text(text = "Hello,") + Text(text = name) + } + ElevatedButton(onClick = { }) { + Text(text = "Show more") + } + } + } } From a9eef7ccec7e392cac9e2234cc7ca9d6699aec7d Mon Sep 17 00:00:00 2001 From: Kaushal-Vasava Date: Thu, 5 Jan 2023 14:11:42 +0530 Subject: [PATCH 4/9] Add handling of composable state and add onboarding screen with state hoisting --- .../apps/jetpackcomposebasic/MainActivity.kt | 125 +++++++++++++----- 1 file changed, 93 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt index 0722d94..4aa5ca9 100644 --- a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt @@ -5,12 +5,24 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.* import androidx.compose.material3.* -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.lahsuak.apps.jetpackcomposebasic.ui.theme.JetPackComposeBasicTheme +/** + * In Composable functions, state that is read or modified by multiple functions +should live in a common ancestor—this process is called state hoisting. +To hoist means to lift or elevate. + + * Making state hoistable avoids duplicating state and introducing bugs, +helps reuse composables, and makes composables substantially easier to test. +Contrarily, state that doesn't need to be controlled by a composable's parent should not be hoisted. +The source of truth belongs to whoever creates and controls that state. + **/ + class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -24,62 +36,111 @@ class MainActivity : ComponentActivity() { } @Composable -fun MyApp( - modifier: Modifier = Modifier, - names: List = listOf("World", "Kaushal") -) { - Column(modifier = modifier.padding(vertical = 4.dp)) { - for (name in names) { - Greeting(name) +fun MyApp(modifier: Modifier = Modifier) { + // shouldShowOnboarding is using a by keyword instead of the =. + // This is a property delegate that saves you from typing .value every time. + var shouldShowOnboarding by remember { mutableStateOf(true) } + + Surface(modifier) { + if (shouldShowOnboarding) { + OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false }) + } else { + Greetings() } } } /** - * Composable functions : -A composable function is a regular function annotated with @Composable. -This enables your function to call other @Composable functions within it. -You can see how the Greeting function is marked as @Composable. -This function will produce a piece of UI hierarchy displaying the given input, -String. Text is a composable function provided by the library. - **/ - -/** - * 1. Column is Vertical linear layout of view system - * 2. Row is Horizontal linear layout of view system - * 3. Box is FrameLayout of view system - */ - -/** - * Button is a composable provided by the material3 package -which takes a composable as the last argument. -Since trailing lambdas can be moved outside of the parentheses, -you can add any content to the button as a child. For example, a Text + * "State" and "MutableState" are interfaces that hold some value +and trigger UI updates (recompositions) whenever that value changes. +However you can't just assign mutableStateOf to a variable inside a composable. + * As explained before, recomposition can happen at any time which would call the composable again, +resetting the state to a new mutable state with a value of false. + * To preserve state across recompositions, remember the mutable state using "remember". +remember is used to guard against recomposition, so the state is not reset. */ @Composable fun Greeting(name: String) { + val expanded = remember { mutableStateOf(false) } + + val extraPadding = if (expanded.value) 48.dp else 0.dp + Surface( color = MaterialTheme.colorScheme.primary, modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp) ) { Row(modifier = Modifier.padding(24.dp)) { - Column(modifier = Modifier.weight(1f)) { + Column( + modifier = Modifier + .weight(1f) + .padding(bottom = extraPadding) + ) { Text(text = "Hello,") Text(text = name) } - ElevatedButton(onClick = { }) { - Text(text = "Show more") + ElevatedButton( + onClick = { expanded.value = !expanded.value } + ) { + Text(if (expanded.value) "Show less" else "Show more") } } + } +} + +@Composable +private fun Greetings( + modifier: Modifier = Modifier, + names: List = listOf("World", "Kaushal") +) { + Column(modifier = modifier.padding(vertical = 4.dp)) { + for (name in names) { + Greeting(name = name) + } + } +} +// add new on-boarding screen +@Composable +fun OnboardingScreen( + onContinueClicked: () -> Unit, + modifier: Modifier = Modifier +) { + Column( + modifier = modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("Welcome to the Basics Codelab!") + Button( + modifier = Modifier.padding(vertical = 24.dp), + onClick = onContinueClicked + ) { + Text("Continue") + } } } -@Preview(showBackground = true) +@Preview(showBackground = true, widthDp = 320) @Composable fun DefaultPreview() { JetPackComposeBasicTheme { - MyApp() + Greetings() + } +} + +@Preview(showBackground = true, widthDp = 320, heightDp = 320) +@Composable +fun OnboardingPreview() { + JetPackComposeBasicTheme { + OnboardingScreen(onContinueClicked = {}) + } +} + +@Preview +@Composable +fun MyAppPreview() { + JetPackComposeBasicTheme { + MyApp(Modifier.fillMaxSize()) } } \ No newline at end of file From 0017805d687d925d617c605805215b18b184a3a5 Mon Sep 17 00:00:00 2001 From: Kaushal-Vasava Date: Thu, 5 Jan 2023 14:36:48 +0530 Subject: [PATCH 5/9] Add lazy column and add state persistent logic --- .../apps/jetpackcomposebasic/MainActivity.kt | 46 ++++++++----------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt index 4aa5ca9..f16b4b6 100644 --- a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt @@ -4,8 +4,11 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.material3.* import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview @@ -13,16 +16,11 @@ import androidx.compose.ui.unit.dp import com.lahsuak.apps.jetpackcomposebasic.ui.theme.JetPackComposeBasicTheme /** - * In Composable functions, state that is read or modified by multiple functions -should live in a common ancestor—this process is called state hoisting. -To hoist means to lift or elevate. + * Note: LazyColumn and LazyRow are equivalent to RecyclerView in Android Views. - * Making state hoistable avoids duplicating state and introducing bugs, -helps reuse composables, and makes composables substantially easier to test. -Contrarily, state that doesn't need to be controlled by a composable's parent should not be hoisted. -The source of truth belongs to whoever creates and controls that state. + * To display a scrollable column we use a LazyColumn. +-> LazyColumn renders only the visible items on screen, allowing performance gains when rendering a big list. **/ - class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -37,9 +35,11 @@ class MainActivity : ComponentActivity() { @Composable fun MyApp(modifier: Modifier = Modifier) { - // shouldShowOnboarding is using a by keyword instead of the =. - // This is a property delegate that saves you from typing .value every time. - var shouldShowOnboarding by remember { mutableStateOf(true) } + /** PERSISTENT STATE + * Instead of using remember you can use rememberSaveable. + This will save each state surviving configuration changes (such as rotations) and process death. + **/ + var shouldShowOnboarding by rememberSaveable { mutableStateOf(true) } Surface(modifier) { if (shouldShowOnboarding) { @@ -50,21 +50,11 @@ fun MyApp(modifier: Modifier = Modifier) { } } -/** - * "State" and "MutableState" are interfaces that hold some value -and trigger UI updates (recompositions) whenever that value changes. -However you can't just assign mutableStateOf to a variable inside a composable. - * As explained before, recomposition can happen at any time which would call the composable again, -resetting the state to a new mutable state with a value of false. - * To preserve state across recompositions, remember the mutable state using "remember". -remember is used to guard against recomposition, so the state is not reset. - */ - @Composable fun Greeting(name: String) { - val expanded = remember { mutableStateOf(false) } + var expanded by rememberSaveable { mutableStateOf(false) } - val extraPadding = if (expanded.value) 48.dp else 0.dp + val extraPadding = if (expanded) 48.dp else 0.dp Surface( color = MaterialTheme.colorScheme.primary, @@ -80,9 +70,9 @@ fun Greeting(name: String) { Text(text = name) } ElevatedButton( - onClick = { expanded.value = !expanded.value } + onClick = { expanded = !expanded } ) { - Text(if (expanded.value) "Show less" else "Show more") + Text(if (expanded) "Show less" else "Show more") } } } @@ -91,10 +81,10 @@ fun Greeting(name: String) { @Composable private fun Greetings( modifier: Modifier = Modifier, - names: List = listOf("World", "Kaushal") + names: List = List(1000) { "item $it" } ) { - Column(modifier = modifier.padding(vertical = 4.dp)) { - for (name in names) { + LazyColumn(modifier = modifier.padding(vertical = 4.dp)) { + items(items = names) { name -> Greeting(name = name) } } From f99d8196c0e4209080202b876c60838d2fe37436 Mon Sep 17 00:00:00 2001 From: Kaushal-Vasava Date: Thu, 5 Jan 2023 15:17:28 +0530 Subject: [PATCH 6/9] Add Animaztion and change light and dark themes --- .../apps/jetpackcomposebasic/MainActivity.kt | 41 +++++++++++++++---- .../jetpackcomposebasic/ui/theme/Color.kt | 7 +++- .../jetpackcomposebasic/ui/theme/Theme.kt | 25 ++++------- 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt index f16b4b6..7561269 100644 --- a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt @@ -1,8 +1,10 @@ package com.lahsuak.apps.jetpackcomposebasic +import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.animation.core.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -11,6 +13,7 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.lahsuak.apps.jetpackcomposebasic.ui.theme.JetPackComposeBasicTheme @@ -25,7 +28,7 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { - JetPackComposeBasicTheme { + JetPackComposeBasicTheme(dynamicColor = false) { // A surface container using the 'background' color from the theme MyApp(modifier = Modifier.fillMaxSize()) } @@ -36,9 +39,9 @@ class MainActivity : ComponentActivity() { @Composable fun MyApp(modifier: Modifier = Modifier) { /** PERSISTENT STATE - * Instead of using remember you can use rememberSaveable. + * Instead of using remember you can use rememberSaveable. This will save each state surviving configuration changes (such as rotations) and process death. - **/ + **/ var shouldShowOnboarding by rememberSaveable { mutableStateOf(true) } Surface(modifier) { @@ -54,7 +57,13 @@ fun MyApp(modifier: Modifier = Modifier) { fun Greeting(name: String) { var expanded by rememberSaveable { mutableStateOf(false) } - val extraPadding = if (expanded) 48.dp else 0.dp + val extraPadding by animateDpAsState( + if (expanded) 48.dp else 0.dp, + animationSpec = spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessLow + ) + ) Surface( color = MaterialTheme.colorScheme.primary, @@ -64,10 +73,14 @@ fun Greeting(name: String) { Column( modifier = Modifier .weight(1f) - .padding(bottom = extraPadding) + .padding(bottom = extraPadding.coerceAtLeast(0.dp)) ) { Text(text = "Hello,") - Text(text = name) + Text( + text = name, style = MaterialTheme.typography.headlineMedium.copy( + fontWeight = FontWeight.ExtraBold + ) + ) } ElevatedButton( onClick = { expanded = !expanded } @@ -114,7 +127,21 @@ fun OnboardingScreen( @Preview(showBackground = true, widthDp = 320) @Composable fun DefaultPreview() { - JetPackComposeBasicTheme { + JetPackComposeBasicTheme(dynamicColor = false) { + Greetings() + } +} + +@Preview( + showBackground = true, + widthDp = 320, + uiMode = UI_MODE_NIGHT_YES, + name = "Dark" +) +@Preview(showBackground = true, widthDp = 320) +@Composable +fun DefaultPreviewDark() { + JetPackComposeBasicTheme(dynamicColor = false) { Greetings() } } diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/theme/Color.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/theme/Color.kt index e0a4930..69564a0 100644 --- a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/theme/Color.kt +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/theme/Color.kt @@ -8,4 +8,9 @@ val Pink80 = Color(0xFFEFB8C8) val Purple40 = Color(0xFF6650a4) val PurpleGrey40 = Color(0xFF625b71) -val Pink40 = Color(0xFF7D5260) \ No newline at end of file +val Pink40 = Color(0xFF7D5260) + +val Navy = Color(0xFF073042) +val Blue = Color(0xFF4285F4) +val LightBlue = Color(0xFFD7EFFE) +val Chartreuse = Color(0xFFEFF7CF) \ No newline at end of file diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/theme/Theme.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/theme/Theme.kt index 5ec8f0d..b112dfd 100644 --- a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/theme/Theme.kt +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/theme/Theme.kt @@ -10,31 +10,24 @@ import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.core.view.ViewCompat private val DarkColorScheme = darkColorScheme( - primary = Purple80, - secondary = PurpleGrey80, - tertiary = Pink80 + surface = Blue, + onSurface = Navy, + primary = Navy, + onPrimary = Chartreuse ) private val LightColorScheme = lightColorScheme( - primary = Purple40, - secondary = PurpleGrey40, - tertiary = Pink40 - - /* Other default colors to override - background = Color(0xFFFFFBFE), - surface = Color(0xFFFFFBFE), - onPrimary = Color.White, - onSecondary = Color.White, - onTertiary = Color.White, - onBackground = Color(0xFF1C1B1F), - onSurface = Color(0xFF1C1B1F), - */ + surface = Blue, + onSurface = Color.White, + primary = LightBlue, + onPrimary = Navy ) @Composable From 4a1450c22adf9e167915b4e378c7c26c439c5b60 Mon Sep 17 00:00:00 2001 From: KaushalVasava Date: Wed, 26 Jul 2023 23:03:37 +0530 Subject: [PATCH 7/9] Add more animations --- app/build.gradle | 24 +- .../apps/jetpackcomposebasic/MainActivity.kt | 251 +++++++++++++++++- build.gradle | 8 +- 3 files changed, 259 insertions(+), 24 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 75677b5..c35e5f3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,12 +5,12 @@ plugins { android { namespace 'com.lahsuak.apps.jetpackcomposebasic' - compileSdk 33 + compileSdk 34 defaultConfig { applicationId "com.lahsuak.apps.jetpackcomposebasic" minSdk 23 - targetSdk 33 + targetSdk 34 versionCode 1 versionName "1.0" @@ -27,17 +27,17 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } buildFeatures { compose true } composeOptions { - kotlinCompilerExtensionVersion '1.1.1' + kotlinCompilerExtensionVersion '1.4.3' } packagingOptions { resources { @@ -48,15 +48,15 @@ android { dependencies { - implementation 'androidx.core:core-ktx:1.9.0' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' - implementation 'androidx.activity:activity-compose:1.6.1' + implementation 'androidx.core:core-ktx:1.10.1' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1' + implementation 'androidx.activity:activity-compose:1.7.2' implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" - implementation 'androidx.compose.material3:material3:1.1.0-alpha03' + implementation 'androidx.compose.material3:material3:1.2.0-alpha03' testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.4' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt index 7561269..59f55bf 100644 --- a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt @@ -4,15 +4,70 @@ import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.compose.animation.core.* -import androidx.compose.foundation.layout.* +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.animateColor +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateDp +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateInt +import androidx.compose.animation.core.animateRect +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.core.updateTransition +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.togetherWith +import androidx.compose.animation.with +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowForward +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.ElevatedButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.drawscope.rotate import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -29,13 +84,194 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) setContent { JetPackComposeBasicTheme(dynamicColor = false) { - // A surface container using the 'background' color from the theme - MyApp(modifier = Modifier.fillMaxSize()) + ImageBorderAnimation() } } } } +@Composable +fun ImageBorderAnimation() { + val infiniteTransition = rememberInfiniteTransition(label = "infinite") + val rotationAnimation = infiniteTransition.animateFloat( + initialValue = 0f, + targetValue = 360f, + animationSpec = infiniteRepeatable(tween(1000, easing = LinearEasing)), label = "rotate" + ) + + Image( + imageVector = Icons.Default.ArrowForward, contentDescription = null, + modifier = Modifier + .drawBehind { + rotate(rotationAnimation.value) { + drawCircle( + Brush.linearGradient( + colors = listOf( + Color.Blue, Color.Green, Color.Yellow, Color.Red, Color.Magenta + ) + ), + style = Stroke(8f) + ) + } + } + .size(60.dp) + .padding(8.dp) + .clip(CircleShape) + ) +} + +@Composable +fun AnimateContent() { + Column(Modifier.fillMaxSize()) { + var isVisible by remember { + mutableStateOf(false) + } + Button( + colors = ButtonColors( + containerColor = Color.Green, + contentColor = Color.Black, + disabledContainerColor = Color.Gray, + disabledContentColor = Color.DarkGray + ), + onClick = { isVisible = !isVisible }) { + Text("Toggle") + } + AnimatedContent( + targetState = isVisible, + modifier = Modifier + .fillMaxWidth() + .weight(1f), + label = "content", + content = { + if (it) { + Box(modifier = Modifier.background(Color.Green)) + } else { + Box(modifier = Modifier.background(Color.Red)) + } + }, + transitionSpec = { + slideInHorizontally { + if (isVisible) it else -it + } togetherWith slideOutHorizontally { + if (isVisible) -it else it + } + } + ) + } +} + +@Composable +fun RepeatAnimationInfinite() { + Column(Modifier.fillMaxSize()) { + var isRound by remember { + mutableStateOf(false) + } + val repeatable = rememberInfiniteTransition(label = "repeatable") + val color by repeatable.animateColor( + initialValue = Color.Green, + targetValue = Color.Red, + animationSpec = + infiniteRepeatable( + animation = tween(5000), + repeatMode = RepeatMode.Reverse + ), label = "color" + ) + Button( + colors = ButtonColors( + containerColor = Color.Green, + contentColor = Color.Black, + disabledContainerColor = Color.Gray, + disabledContentColor = Color.DarkGray + ), + onClick = { isRound = !isRound }) { + Text("Toggle") + } + Box( + modifier = Modifier + .size(200.dp) + .background(color) + ) + } +} + +@Composable +fun AnimateStateValue() { + Column(Modifier.fillMaxSize()) { + var isRound by remember { + mutableStateOf(false) + } + Button( + colors = ButtonColors( + containerColor = Color.Green, + contentColor = Color.Black, + disabledContainerColor = Color.Gray, + disabledContentColor = Color.DarkGray + ), + onClick = { isRound = !isRound }) { + Text("Toggle") + } + val transition = updateTransition(targetState = isRound, label = null) + val borderRadius by transition.animateInt(transitionSpec = { + tween(2000) + }, label = "borderRadius") { + if (it) 100 + else 0 + } + val color by transition.animateColor(transitionSpec = { + tween(2000) + }, label = "color") { + if (it) + Color.Green + else + Color.Red + } +// val borderRadius by animateIntAsState( +// targetValue = if (isRound) 40 else 20, +// label = "animate", +// animationSpec = spring( +// dampingRatio = Spring.DampingRatioHighBouncy, +// stiffness = Spring.StiffnessLow +// ) +// //tween(delayMillis = 100, durationMillis = 3000) +// ) + Box( + modifier = Modifier + .size(200.dp) + .clip(RoundedCornerShape(borderRadius)) + .background(color) + ) + } +} + +@Composable +fun AnimateViewVisibility() { + Column(Modifier.fillMaxSize()) { + var isVisible by remember { + mutableStateOf(false) + } + Button( + colors = ButtonColors( + containerColor = Color.Green, + contentColor = Color.Black, + disabledContainerColor = Color.Gray, + disabledContentColor = Color.DarkGray + ), + onClick = { isVisible = !isVisible }) { + Text("Toggle") + } + AnimatedVisibility( + visible = isVisible, + enter = slideInVertically() + fadeIn(), + exit = slideOutVertically() + fadeOut(), + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) { + Box(modifier = Modifier.background(Color.Red)) + } + } +} + @Composable fun MyApp(modifier: Modifier = Modifier) { /** PERSISTENT STATE @@ -62,9 +298,8 @@ fun Greeting(name: String) { animationSpec = spring( dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessLow - ) + ), label = "Greeting" ) - Surface( color = MaterialTheme.colorScheme.primary, modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp) diff --git a/build.gradle b/build.gradle index e868fc8..b0f2f02 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,10 @@ buildscript { ext { - compose_version = '1.3.2' + compose_version = '1.4.3' } }// Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '7.3.0' apply false - id 'com.android.library' version '7.3.0' apply false - id 'org.jetbrains.kotlin.android' version '1.6.10' apply false + id 'com.android.application' version '8.0.2' apply false + id 'com.android.library' version '8.0.2' apply false + id 'org.jetbrains.kotlin.android' version '1.8.10' apply false } \ No newline at end of file From 39d5644d19004268c2156e7c048104af5635072d Mon Sep 17 00:00:00 2001 From: KaushalVasava Date: Sat, 16 Sep 2023 12:29:45 +0530 Subject: [PATCH 8/9] Add Navigation Screen Animations --- app/build.gradle | 12 +- .../apps/jetpackcomposebasic/MainActivity.kt | 240 +----------------- .../model/AnimationItem.kt | 6 + .../ui/navigation/AppNavigation.kt | 18 ++ .../ui/navigation/navhost/AppNavHost.kt | 72 ++++++ .../ui/screen/AnimateContent.kt | 62 +++++ .../ui/screen/AnimateStateValue.kt | 74 ++++++ .../ui/screen/AnimateViewVisibility.kt | 52 ++++ .../ui/screen/HomeScreen.kt | 50 ++++ .../ui/screen/ImageBorderAnimation.kt | 56 ++++ .../ui/screen/RepeatAnimationInfinite.kt | 53 ++++ build.gradle | 8 +- 12 files changed, 461 insertions(+), 242 deletions(-) create mode 100644 app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/model/AnimationItem.kt create mode 100644 app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/navigation/AppNavigation.kt create mode 100644 app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/navigation/navhost/AppNavHost.kt create mode 100644 app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/AnimateContent.kt create mode 100644 app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/AnimateStateValue.kt create mode 100644 app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/AnimateViewVisibility.kt create mode 100644 app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/HomeScreen.kt create mode 100644 app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/ImageBorderAnimation.kt create mode 100644 app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/RepeatAnimationInfinite.kt diff --git a/app/build.gradle b/app/build.gradle index c35e5f3..9082afd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,7 +4,7 @@ plugins { } android { - namespace 'com.lahsuak.apps.jetpackcomposebasic' + namespace "com.lahsuak.apps.jetpackcomposebasic" compileSdk 34 defaultConfig { @@ -37,7 +37,7 @@ android { compose true } composeOptions { - kotlinCompilerExtensionVersion '1.4.3' + kotlinCompilerExtensionVersion '1.4.5' } packagingOptions { resources { @@ -48,16 +48,18 @@ android { dependencies { - implementation 'androidx.core:core-ktx:1.10.1' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1' + implementation 'androidx.core:core-ktx:1.12.0' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2' implementation 'androidx.activity:activity-compose:1.7.2' implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" - implementation 'androidx.compose.material3:material3:1.2.0-alpha03' + implementation 'androidx.compose.material3:material3:1.1.1' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" + + implementation "androidx.navigation:navigation-compose:2.7.2" } \ No newline at end of file diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt index 59f55bf..091159b 100644 --- a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt @@ -4,50 +4,17 @@ import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.animation.animateColor -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.Spring -import androidx.compose.animation.core.animateDp import androidx.compose.animation.core.animateDpAsState -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.animateInt -import androidx.compose.animation.core.animateRect -import androidx.compose.animation.core.infiniteRepeatable -import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.spring -import androidx.compose.animation.core.tween -import androidx.compose.animation.core.updateTransition -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInHorizontally -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutHorizontally -import androidx.compose.animation.slideOutVertically -import androidx.compose.animation.togetherWith -import androidx.compose.animation.with -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowForward import androidx.compose.material3.Button -import androidx.compose.material3.ButtonColors import androidx.compose.material3.ElevatedButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface @@ -55,223 +22,30 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.drawBehind -import androidx.compose.ui.draw.rotate -import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.drawscope.Stroke -import androidx.compose.ui.graphics.drawscope.rotate import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.navigation.compose.rememberNavController +import com.lahsuak.apps.jetpackcomposebasic.model.AnimationItem +import com.lahsuak.apps.jetpackcomposebasic.ui.navigation.navhost.AppNavHost +import com.lahsuak.apps.jetpackcomposebasic.ui.screen.ImageBorderAnimation import com.lahsuak.apps.jetpackcomposebasic.ui.theme.JetPackComposeBasicTheme -/** - * Note: LazyColumn and LazyRow are equivalent to RecyclerView in Android Views. - - * To display a scrollable column we use a LazyColumn. --> LazyColumn renders only the visible items on screen, allowing performance gains when rendering a big list. - **/ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { JetPackComposeBasicTheme(dynamicColor = false) { - ImageBorderAnimation() + AppNavHost(navController = rememberNavController()) } } } } -@Composable -fun ImageBorderAnimation() { - val infiniteTransition = rememberInfiniteTransition(label = "infinite") - val rotationAnimation = infiniteTransition.animateFloat( - initialValue = 0f, - targetValue = 360f, - animationSpec = infiniteRepeatable(tween(1000, easing = LinearEasing)), label = "rotate" - ) - - Image( - imageVector = Icons.Default.ArrowForward, contentDescription = null, - modifier = Modifier - .drawBehind { - rotate(rotationAnimation.value) { - drawCircle( - Brush.linearGradient( - colors = listOf( - Color.Blue, Color.Green, Color.Yellow, Color.Red, Color.Magenta - ) - ), - style = Stroke(8f) - ) - } - } - .size(60.dp) - .padding(8.dp) - .clip(CircleShape) - ) -} - -@Composable -fun AnimateContent() { - Column(Modifier.fillMaxSize()) { - var isVisible by remember { - mutableStateOf(false) - } - Button( - colors = ButtonColors( - containerColor = Color.Green, - contentColor = Color.Black, - disabledContainerColor = Color.Gray, - disabledContentColor = Color.DarkGray - ), - onClick = { isVisible = !isVisible }) { - Text("Toggle") - } - AnimatedContent( - targetState = isVisible, - modifier = Modifier - .fillMaxWidth() - .weight(1f), - label = "content", - content = { - if (it) { - Box(modifier = Modifier.background(Color.Green)) - } else { - Box(modifier = Modifier.background(Color.Red)) - } - }, - transitionSpec = { - slideInHorizontally { - if (isVisible) it else -it - } togetherWith slideOutHorizontally { - if (isVisible) -it else it - } - } - ) - } -} - -@Composable -fun RepeatAnimationInfinite() { - Column(Modifier.fillMaxSize()) { - var isRound by remember { - mutableStateOf(false) - } - val repeatable = rememberInfiniteTransition(label = "repeatable") - val color by repeatable.animateColor( - initialValue = Color.Green, - targetValue = Color.Red, - animationSpec = - infiniteRepeatable( - animation = tween(5000), - repeatMode = RepeatMode.Reverse - ), label = "color" - ) - Button( - colors = ButtonColors( - containerColor = Color.Green, - contentColor = Color.Black, - disabledContainerColor = Color.Gray, - disabledContentColor = Color.DarkGray - ), - onClick = { isRound = !isRound }) { - Text("Toggle") - } - Box( - modifier = Modifier - .size(200.dp) - .background(color) - ) - } -} - -@Composable -fun AnimateStateValue() { - Column(Modifier.fillMaxSize()) { - var isRound by remember { - mutableStateOf(false) - } - Button( - colors = ButtonColors( - containerColor = Color.Green, - contentColor = Color.Black, - disabledContainerColor = Color.Gray, - disabledContentColor = Color.DarkGray - ), - onClick = { isRound = !isRound }) { - Text("Toggle") - } - val transition = updateTransition(targetState = isRound, label = null) - val borderRadius by transition.animateInt(transitionSpec = { - tween(2000) - }, label = "borderRadius") { - if (it) 100 - else 0 - } - val color by transition.animateColor(transitionSpec = { - tween(2000) - }, label = "color") { - if (it) - Color.Green - else - Color.Red - } -// val borderRadius by animateIntAsState( -// targetValue = if (isRound) 40 else 20, -// label = "animate", -// animationSpec = spring( -// dampingRatio = Spring.DampingRatioHighBouncy, -// stiffness = Spring.StiffnessLow -// ) -// //tween(delayMillis = 100, durationMillis = 3000) -// ) - Box( - modifier = Modifier - .size(200.dp) - .clip(RoundedCornerShape(borderRadius)) - .background(color) - ) - } -} - -@Composable -fun AnimateViewVisibility() { - Column(Modifier.fillMaxSize()) { - var isVisible by remember { - mutableStateOf(false) - } - Button( - colors = ButtonColors( - containerColor = Color.Green, - contentColor = Color.Black, - disabledContainerColor = Color.Gray, - disabledContentColor = Color.DarkGray - ), - onClick = { isVisible = !isVisible }) { - Text("Toggle") - } - AnimatedVisibility( - visible = isVisible, - enter = slideInVertically() + fadeIn(), - exit = slideOutVertically() + fadeOut(), - modifier = Modifier - .fillMaxWidth() - .weight(1f) - ) { - Box(modifier = Modifier.background(Color.Red)) - } - } -} - @Composable fun MyApp(modifier: Modifier = Modifier) { /** PERSISTENT STATE @@ -329,7 +103,7 @@ fun Greeting(name: String) { @Composable private fun Greetings( modifier: Modifier = Modifier, - names: List = List(1000) { "item $it" } + names: List = List(1000) { "item $it" }, ) { LazyColumn(modifier = modifier.padding(vertical = 4.dp)) { items(items = names) { name -> @@ -342,7 +116,7 @@ private fun Greetings( @Composable fun OnboardingScreen( onContinueClicked: () -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { Column( modifier = modifier.fillMaxSize(), diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/model/AnimationItem.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/model/AnimationItem.kt new file mode 100644 index 0000000..e7d3fab --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/model/AnimationItem.kt @@ -0,0 +1,6 @@ +package com.lahsuak.apps.jetpackcomposebasic.model + +data class AnimationItem( + val name: String, + val navigationRoute: String, +) diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/navigation/AppNavigation.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/navigation/AppNavigation.kt new file mode 100644 index 0000000..f5beb71 --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/navigation/AppNavigation.kt @@ -0,0 +1,18 @@ +package com.lahsuak.apps.jetpackcomposebasic.ui.navigation + +enum class Screen { + Home, + AnimateVisibility, + AnimateContent, + AnimateState, + InfiniteAnimation, + ImageBorderAnimation, +} +sealed class NavigationItem(val route: String) { + object Home : NavigationItem(Screen.Home.name) + object AnimateVisibility : NavigationItem(Screen.AnimateVisibility.name) + object AnimateContent : NavigationItem(Screen.AnimateContent.name) + object AnimateState : NavigationItem(Screen.AnimateState.name) + object InfiniteAnimation : NavigationItem(Screen.InfiniteAnimation.name) + object ImageBorderAnimation : NavigationItem(Screen.ImageBorderAnimation.name) +} \ No newline at end of file diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/navigation/navhost/AppNavHost.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/navigation/navhost/AppNavHost.kt new file mode 100644 index 0000000..cea0545 --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/navigation/navhost/AppNavHost.kt @@ -0,0 +1,72 @@ +package com.lahsuak.apps.jetpackcomposebasic.ui.navigation.navhost + +import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.animation.core.tween +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import com.lahsuak.apps.jetpackcomposebasic.ui.navigation.NavigationItem +import com.lahsuak.apps.jetpackcomposebasic.ui.screen.AnimateContent +import com.lahsuak.apps.jetpackcomposebasic.ui.screen.AnimateStateValue +import com.lahsuak.apps.jetpackcomposebasic.ui.screen.AnimateViewVisibility +import com.lahsuak.apps.jetpackcomposebasic.ui.screen.HomeScreen +import com.lahsuak.apps.jetpackcomposebasic.ui.screen.ImageBorderAnimation +import com.lahsuak.apps.jetpackcomposebasic.ui.screen.RepeatAnimationInfinite + +@Composable +fun AppNavHost( + modifier: Modifier = Modifier, + navController: NavHostController, + startDestination: String = NavigationItem.Home.route, +) { + NavHost( + modifier = modifier, + navController = navController, + startDestination = startDestination, + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Left, + tween(400) + ) + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Left, + tween(400) + ) + }, + popEnterTransition = { + slideIntoContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Companion.Right, + animationSpec = tween(400) + ) + }, + popExitTransition = { + slideOutOfContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Companion.Right, + animationSpec = tween(400) + ) + } + ) { + composable(NavigationItem.Home.route) { + HomeScreen(navController) + } + composable(NavigationItem.AnimateState.route) { + AnimateStateValue() + } + composable(NavigationItem.AnimateContent.route) { + AnimateContent() + } + composable(NavigationItem.AnimateVisibility.route) { + AnimateViewVisibility() + } + composable(NavigationItem.InfiniteAnimation.route) { + RepeatAnimationInfinite() + } + composable(NavigationItem.ImageBorderAnimation.route) { + ImageBorderAnimation() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/AnimateContent.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/AnimateContent.kt new file mode 100644 index 0000000..627285c --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/AnimateContent.kt @@ -0,0 +1,62 @@ +package com.lahsuak.apps.jetpackcomposebasic.ui.screen + +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color + +@Composable +fun AnimateContent() { + Column(Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) { + var isVisible by remember { + mutableStateOf(false) + } + Button( + colors = ButtonDefaults.buttonColors( + containerColor = Color.Green, + contentColor = Color.Black, + disabledContainerColor = Color.Gray, + disabledContentColor = Color.DarkGray + ), + onClick = { isVisible = !isVisible }) { + Text("Toggle") + } + AnimatedContent( + targetState = isVisible, + modifier = Modifier + .fillMaxWidth() + .weight(1f), + label = "content", + content = { + if (it) { + Box(modifier = Modifier.background(Color.Green)) + } else { + Box(modifier = Modifier.background(Color.Red)) + } + }, + transitionSpec = { + slideInHorizontally { + if (isVisible) it else -it + } togetherWith slideOutHorizontally { + if (isVisible) -it else it + } + } + ) + } +} diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/AnimateStateValue.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/AnimateStateValue.kt new file mode 100644 index 0000000..99591eb --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/AnimateStateValue.kt @@ -0,0 +1,74 @@ +package com.lahsuak.apps.jetpackcomposebasic.ui.screen + +import androidx.compose.animation.animateColor +import androidx.compose.animation.core.animateInt +import androidx.compose.animation.core.tween +import androidx.compose.animation.core.updateTransition +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@Composable +fun AnimateStateValue() { + Column(Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) { + var isRound by remember { + mutableStateOf(false) + } + Button( + colors = ButtonDefaults.buttonColors( + containerColor = Color.Green, + contentColor = Color.Black, + disabledContainerColor = Color.Gray, + disabledContentColor = Color.DarkGray + ), + onClick = { isRound = !isRound }) { + Text("Toggle") + } + val transition = updateTransition(targetState = isRound, label = null) + val borderRadius by transition.animateInt(transitionSpec = { + tween(2000) + }, label = "borderRadius") { + if (it) 100 + else 0 + } + val color by transition.animateColor(transitionSpec = { + tween(2000) + }, label = "color") { + if (it) + Color.Green + else + Color.Red + } +// val borderRadius by animateIntAsState( +// targetValue = if (isRound) 40 else 20, +// label = "animate", +// animationSpec = spring( +// dampingRatio = Spring.DampingRatioHighBouncy, +// stiffness = Spring.StiffnessLow +// ) +// //tween(delayMillis = 100, durationMillis = 3000) +// ) + Box( + modifier = Modifier + .size(200.dp) + .clip(RoundedCornerShape(borderRadius)) + .background(color) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/AnimateViewVisibility.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/AnimateViewVisibility.kt new file mode 100644 index 0000000..7f7b7a4 --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/AnimateViewVisibility.kt @@ -0,0 +1,52 @@ +package com.lahsuak.apps.jetpackcomposebasic.ui.screen + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color + +@Composable +fun AnimateViewVisibility() { + Column(Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) { + var isVisible by remember { + mutableStateOf(false) + } + Button( + colors = ButtonDefaults.buttonColors( + containerColor = Color.Green, + contentColor = Color.Black, + disabledContainerColor = Color.Gray, + disabledContentColor = Color.DarkGray + ), + onClick = { isVisible = !isVisible }) { + Text("Toggle") + } + AnimatedVisibility( + visible = isVisible, + enter = slideInVertically() + fadeIn(), + exit = slideOutVertically() + fadeOut(), + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) { + Box(modifier = Modifier.background(Color.Red)) + } + } +} diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/HomeScreen.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/HomeScreen.kt new file mode 100644 index 0000000..c4ff3bb --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/HomeScreen.kt @@ -0,0 +1,50 @@ +package com.lahsuak.apps.jetpackcomposebasic.ui.screen + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Card +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import com.lahsuak.apps.jetpackcomposebasic.model.AnimationItem +import com.lahsuak.apps.jetpackcomposebasic.ui.navigation.NavigationItem + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun HomeScreen(navController: NavController) { + val animationsType = listOf( + AnimationItem("Animate Content", NavigationItem.AnimateContent.route), + AnimationItem("Animate State", NavigationItem.AnimateState.route), + AnimationItem("Animate Visibility", NavigationItem.AnimateVisibility.route), + AnimationItem("Infinite Animation", NavigationItem.InfiniteAnimation.route), + AnimationItem("Image border animation", NavigationItem.ImageBorderAnimation.route), + ) + LazyColumn { + items(animationsType) { + Card( + onClick = { + navController.navigate(it.navigationRoute) + }, modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = 16.dp, + vertical = 8.dp + ) + ) { + Text( + it.name, + fontSize = 32.sp, + fontWeight = FontWeight.SemiBold, + modifier = Modifier.padding(24.dp) + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/ImageBorderAnimation.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/ImageBorderAnimation.kt new file mode 100644 index 0000000..fd704f9 --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/ImageBorderAnimation.kt @@ -0,0 +1,56 @@ +package com.lahsuak.apps.jetpackcomposebasic.ui.screen + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowForward +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.drawscope.rotate +import androidx.compose.ui.unit.dp + +@Composable +fun ImageBorderAnimation() { + val infiniteTransition = rememberInfiniteTransition(label = "infinite") + val rotationAnimation = infiniteTransition.animateFloat( + initialValue = 0f, + targetValue = 360f, + animationSpec = infiniteRepeatable(tween(1000, easing = LinearEasing)), label = "rotate" + ) + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Image( + imageVector = Icons.Default.ArrowForward, contentDescription = null, + modifier = Modifier + .drawBehind { + rotate(rotationAnimation.value) { + drawCircle( + Brush.linearGradient( + colors = listOf( + Color.Blue, Color.Green, Color.Yellow, Color.Red, Color.Magenta + ) + ), + style = Stroke(8f) + ) + } + } + .size(60.dp) + .padding(8.dp) + .clip(CircleShape) + ) + } +} diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/RepeatAnimationInfinite.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/RepeatAnimationInfinite.kt new file mode 100644 index 0000000..54aa520 --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/ui/screen/RepeatAnimationInfinite.kt @@ -0,0 +1,53 @@ +package com.lahsuak.apps.jetpackcomposebasic.ui.screen + +import androidx.compose.animation.animateColor +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@Composable +fun RepeatAnimationInfinite() { + Column( + Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + var isRound by remember { + mutableStateOf(false) + } + val repeatable = rememberInfiniteTransition(label = "repeatable") + val color by repeatable.animateColor( + initialValue = Color.Green, + targetValue = Color.Red, + animationSpec = + infiniteRepeatable( + animation = tween(5000), + repeatMode = RepeatMode.Reverse + ), label = "color" + ) + Box( + modifier = Modifier + .size(200.dp) + .background(color) + ) + } +} diff --git a/build.gradle b/build.gradle index b0f2f02..abd017e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,10 @@ buildscript { ext { - compose_version = '1.4.3' + compose_version = '1.4.5' } }// Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.0.2' apply false - id 'com.android.library' version '8.0.2' apply false - id 'org.jetbrains.kotlin.android' version '1.8.10' apply false + id 'com.android.application' version '8.1.0' apply false + id 'com.android.library' version '8.1.0' apply false + id 'org.jetbrains.kotlin.android' version '1.8.20' apply false } \ No newline at end of file From 8982a6543f56bd445d4d3156b3f7665b933faaae Mon Sep 17 00:00:00 2001 From: Kaushal Vasava <49050597+KaushalVasava@users.noreply.github.com> Date: Sat, 16 Sep 2023 13:17:20 +0530 Subject: [PATCH 9/9] Create README.md --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..add3293 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# 💫 Animations in Jetpack Compose 💫 + +Navigation transition animations without accompanist library. + +# You will learn: +- Visibility animation +- Content Animation +- Composable State Animation +- Infinite Animation +- Navigation Animation +- Navigation in Jetpack Compose + +# Video + +https://github.com/KaushalVasava/JetPackCompose_Basic/assets/49050597/ed4ca42d-4e59-429a-a575-88eda2d259d9 + +# Author +Kaushal Vasava.