├── .gitignore ├── .idea ├── .gitignore ├── .name ├── compiler.xml ├── deploymentTargetDropDown.xml ├── gradle.xml ├── kotlinc.xml ├── misc.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── jitpack.yml ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── alexmercerind │ │ └── example │ │ ├── AnimatedTextScreen.kt │ │ ├── Destinations.kt │ │ ├── HomeScreen.kt │ │ └── MainActivity.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── baseline_code_24.xml │ ├── baseline_pause_24.xml │ ├── baseline_shutter_speed_24.xml │ ├── baseline_stop_24.xml │ ├── github.xml │ └── ic_launcher_background.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-mdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxxhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── values │ ├── colors.xml │ ├── strings.xml │ └── themes.xml │ └── xml │ ├── backup_rules.xml │ └── data_extraction_rules.xml ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── moving-letters ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── alexmercerind │ └── movingletters │ ├── AnimatedTextState.kt │ ├── FadeAnimatedText.kt │ ├── JumpAnimatedText.kt │ ├── RotateAnimatedText.kt │ ├── ScaleInAnimatedText.kt │ └── ScaleOutAnimatedText.kt └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Moving Letters Example -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 & onwards, Hitesh Kumar Saini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [moving-letters-android](https://github.com/alexmercerind/moving-letters-android) 2 | 3 | #### Text animation library for Android (Jetpack Compose). 4 | 5 | ## Guide 6 | 7 | Following text animations are available: 8 | 9 | #### ScaleInAnimatedText 10 | 11 | 12 | 13 | #### ScaleOutAnimatedText 14 | 15 | 16 | 17 | #### FadeAnimatedText 18 | 19 | 20 | 21 | #### JumpAnimatedText 22 | 23 | 24 | 25 | #### RotateAnimatedText 26 | 27 | 28 | 29 | #### Programmatic API 30 | 31 | You may use `AnimatedTextState` as follows to control the animation programmatically: 32 | 33 | ```kt 34 | val state = rememberAnimatedTextState() 35 | 36 | XYZAnimatedText( 37 | state = state, 38 | text = "I like Jetpack Compose" 39 | ) 40 | 41 | state.stop() 42 | state.pause() 43 | state.start() 44 | state.resume() 45 | ``` 46 | 47 | #### Example Application 48 | 49 | 50 | 51 | You may download the [APK](https://github.com/alexmercerind/moving-letters-android/files/13639901/app-release.zip) quick trial. 52 | 53 | The [source-code of the example application](https://github.com/alexmercerind/moving-letters-android/tree/main/app/src/main/java/com/alexmercerind/example) provides more details. 54 | 55 | ## Inspiration 56 | 57 | [Moving Letters for Web / JavaScript](https://tobiasahlin.com/moving-letters/) by [@tobiasahlin](https://twitter.com/tobiasahlin). 58 | 59 | I wanted to implement it in Jetpack Compose! 60 | 61 | ## License 62 | 63 | Copyright © 2023 & onwards, Hitesh Kumar Saini. 64 | 65 | This project & the work under this repository is governed by MIT license that can be found in the [LICENSE](./LICENSE) file. 66 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("org.jetbrains.kotlin.android") 4 | } 5 | 6 | android { 7 | namespace = "com.alexmercerind.example" 8 | compileSdk = 34 9 | 10 | defaultConfig { 11 | applicationId = "com.alexmercerind.example" 12 | minSdk = 21 13 | targetSdk = 34 14 | versionCode = 1 15 | versionName = "1.0" 16 | 17 | vectorDrawables { 18 | useSupportLibrary = true 19 | } 20 | } 21 | 22 | buildTypes { 23 | release { 24 | isMinifyEnabled = false 25 | proguardFiles( 26 | getDefaultProguardFile("proguard-android-optimize.txt"), 27 | "proguard-rules.pro" 28 | ) 29 | signingConfig = signingConfigs.getByName("debug") 30 | } 31 | } 32 | compileOptions { 33 | sourceCompatibility = JavaVersion.VERSION_1_8 34 | targetCompatibility = JavaVersion.VERSION_1_8 35 | } 36 | kotlinOptions { 37 | jvmTarget = "1.8" 38 | } 39 | buildFeatures { 40 | compose = true 41 | } 42 | composeOptions { 43 | kotlinCompilerExtensionVersion = "1.4.3" 44 | } 45 | packaging { 46 | resources { 47 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 48 | } 49 | } 50 | } 51 | 52 | dependencies { 53 | 54 | implementation("androidx.core:core-ktx:1.12.0") 55 | implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") 56 | implementation("androidx.activity:activity-compose:1.8.1") 57 | implementation(platform("androidx.compose:compose-bom:2023.03.00")) 58 | implementation("androidx.compose.ui:ui") 59 | implementation("androidx.compose.ui:ui-graphics") 60 | implementation("androidx.compose.ui:ui-tooling-preview") 61 | implementation("androidx.compose.material3:material3:1.2.0-alpha12") 62 | implementation("androidx.navigation:navigation-compose:2.7.5") 63 | debugImplementation("androidx.compose.ui:ui-tooling") 64 | debugImplementation("androidx.compose.ui:ui-test-manifest") 65 | implementation(project(":moving-letters")) 66 | } 67 | -------------------------------------------------------------------------------- /app/jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk17 3 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/alexmercerind/example/AnimatedTextScreen.kt: -------------------------------------------------------------------------------- 1 | package com.alexmercerind.example 2 | 3 | import androidx.compose.foundation.layout.Arrangement 4 | import androidx.compose.foundation.layout.PaddingValues 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.foundation.layout.Spacer 7 | import androidx.compose.foundation.layout.calculateEndPadding 8 | import androidx.compose.foundation.layout.calculateStartPadding 9 | import androidx.compose.foundation.layout.fillMaxSize 10 | import androidx.compose.foundation.layout.height 11 | import androidx.compose.foundation.layout.padding 12 | import androidx.compose.foundation.layout.width 13 | import androidx.compose.foundation.lazy.LazyColumn 14 | import androidx.compose.foundation.lazy.LazyRow 15 | import androidx.compose.material.icons.Icons 16 | import androidx.compose.material.icons.filled.PlayArrow 17 | import androidx.compose.material.icons.filled.Refresh 18 | import androidx.compose.material3.CardDefaults 19 | import androidx.compose.material3.ElevatedCard 20 | import androidx.compose.material3.HorizontalDivider 21 | import androidx.compose.material3.Icon 22 | import androidx.compose.material3.IconButton 23 | import androidx.compose.material3.MaterialTheme 24 | import androidx.compose.material3.Scaffold 25 | import androidx.compose.material3.Text 26 | import androidx.compose.material3.VerticalDivider 27 | import androidx.compose.runtime.Composable 28 | import androidx.compose.runtime.LaunchedEffect 29 | import androidx.compose.runtime.collectAsState 30 | import androidx.compose.runtime.getValue 31 | import androidx.compose.ui.Alignment 32 | import androidx.compose.ui.Modifier 33 | import androidx.compose.ui.graphics.Color 34 | import androidx.compose.ui.platform.LocalLayoutDirection 35 | import androidx.compose.ui.res.painterResource 36 | import androidx.compose.ui.res.stringResource 37 | import androidx.compose.ui.text.font.FontFamily 38 | import androidx.compose.ui.text.style.TextAlign 39 | import androidx.compose.ui.unit.dp 40 | import com.alexmercerind.movingletters.AnimatedTextState 41 | import com.alexmercerind.movingletters.rememberAnimatedTextState 42 | import kotlinx.coroutines.Dispatchers 43 | import kotlinx.coroutines.delay 44 | 45 | @Composable 46 | fun AnimatedTextScreen( 47 | name: String, 48 | content: @Composable (state: AnimatedTextState, text: String) -> Unit, 49 | contentColor: Color, 50 | containerColor: Color 51 | ) { 52 | Scaffold( 53 | contentColor = contentColor, containerColor = containerColor 54 | ) { padding -> 55 | val text = stringResource(id = R.string.text) 56 | val state = rememberAnimatedTextState() 57 | 58 | LaunchedEffect("AnimatedTextScreen", Dispatchers.IO) { 59 | delay(200L) 60 | state.start() 61 | } 62 | 63 | LazyColumn( 64 | modifier = Modifier.fillMaxSize(), 65 | contentPadding = PaddingValues( 66 | top = padding.calculateTopPadding() + 32.dp, 67 | bottom = padding.calculateBottomPadding() + 32.dp, 68 | end = padding.calculateEndPadding(LocalLayoutDirection.current) + 32.dp, 69 | start = padding.calculateStartPadding(LocalLayoutDirection.current) + 32.dp 70 | ) 71 | ) { 72 | item { 73 | content(state, text) 74 | } 75 | item { 76 | Spacer(modifier = Modifier.height(32.dp)) 77 | } 78 | item { 79 | Row( 80 | horizontalArrangement = Arrangement.Start, 81 | verticalAlignment = Alignment.CenterVertically 82 | ) { 83 | IconButton(onClick = state::start) { 84 | Icon( 85 | imageVector = Icons.Default.Refresh, 86 | contentDescription = stringResource(id = R.string.start) 87 | ) 88 | } 89 | Spacer(modifier = Modifier.width(8.dp)) 90 | IconButton(onClick = state::stop) { 91 | Icon( 92 | painter = painterResource(id = R.drawable.baseline_stop_24), 93 | contentDescription = stringResource(id = R.string.stop) 94 | ) 95 | } 96 | Spacer(modifier = Modifier.width(8.dp)) 97 | IconButton(onClick = state::resume) { 98 | Icon( 99 | imageVector = Icons.Default.PlayArrow, 100 | contentDescription = stringResource(id = R.string.resume) 101 | ) 102 | } 103 | Spacer(modifier = Modifier.width(8.dp)) 104 | IconButton(onClick = state::pause) { 105 | Icon( 106 | painter = painterResource(id = R.drawable.baseline_pause_24), 107 | contentDescription = stringResource(id = R.string.pause) 108 | ) 109 | } 110 | } 111 | } 112 | item { 113 | Spacer(modifier = Modifier.height(32.dp)) 114 | } 115 | item { 116 | ElevatedCard( 117 | colors = CardDefaults.cardColors( 118 | containerColor = Color(0xFFFFFFFF), contentColor = Color(0xFF000000) 119 | ), elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) 120 | ) { 121 | Row( 122 | horizontalArrangement = Arrangement.Start, 123 | verticalAlignment = Alignment.CenterVertically, 124 | modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) 125 | ) { 126 | Icon( 127 | painter = painterResource(id = R.drawable.baseline_code_24), 128 | contentDescription = stringResource(id = R.string.code) 129 | ) 130 | Spacer(modifier = Modifier.width(16.dp)) 131 | Text( 132 | text = stringResource(id = R.string.code), 133 | style = MaterialTheme.typography.titleMedium 134 | ) 135 | } 136 | HorizontalDivider() 137 | LazyRow(contentPadding = PaddingValues(16.dp)) { 138 | item { 139 | Text( 140 | text = listOf( 141 | "$name(", " text = \"$text\"", ")" 142 | ).joinToString("\n"), 143 | style = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace) 144 | ) 145 | } 146 | } 147 | } 148 | } 149 | item { 150 | Spacer(modifier = Modifier.height(32.dp)) 151 | } 152 | item { 153 | val playing by state.playing.collectAsState(false) 154 | val ongoing by state.ongoing.collectAsState(false) 155 | val paused by state.paused.collectAsState(false) 156 | val stopped by state.stopped.collectAsState(false) 157 | ElevatedCard( 158 | colors = CardDefaults.cardColors( 159 | containerColor = Color(0xFFFFFFFF), contentColor = Color(0xFF000000) 160 | ), elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) 161 | ) { 162 | Row( 163 | horizontalArrangement = Arrangement.Start, 164 | verticalAlignment = Alignment.CenterVertically, 165 | modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) 166 | ) { 167 | Icon( 168 | painter = painterResource(id = R.drawable.baseline_shutter_speed_24), 169 | contentDescription = stringResource(id = R.string.state) 170 | ) 171 | Spacer(modifier = Modifier.width(16.dp)) 172 | Text( 173 | text = stringResource(id = R.string.state), 174 | style = MaterialTheme.typography.titleMedium 175 | ) 176 | } 177 | HorizontalDivider() 178 | mapOf( 179 | "PLAYING" to playing, 180 | "ONGOING" to ongoing, 181 | "PAUSED" to paused, 182 | "STOPPED" to stopped 183 | ).forEach { 184 | Row( 185 | modifier = Modifier.padding(vertical = 8.dp), 186 | verticalAlignment = Alignment.CenterVertically 187 | ) { 188 | Text( 189 | modifier = Modifier.weight(1.0F), 190 | text = it.key, 191 | textAlign = TextAlign.Center, 192 | style = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace) 193 | ) 194 | VerticalDivider() 195 | Text( 196 | modifier = Modifier.weight(1.0F), 197 | text = it.value.toString().uppercase(), 198 | textAlign = TextAlign.Center, 199 | style = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace) 200 | ) 201 | } 202 | } 203 | } 204 | } 205 | item { 206 | Spacer(modifier = Modifier.height(32.dp)) 207 | } 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /app/src/main/java/com/alexmercerind/example/Destinations.kt: -------------------------------------------------------------------------------- 1 | package com.alexmercerind.example 2 | 3 | interface Destinations { 4 | val value: String 5 | 6 | object Home: Destinations { 7 | override val value = "Home" 8 | } 9 | 10 | object Effect1: Destinations { 11 | override val value = "Effect1" 12 | } 13 | 14 | object Effect2: Destinations { 15 | override val value = "Effect2" 16 | } 17 | 18 | object Effect3: Destinations { 19 | override val value = "Effect3" 20 | } 21 | 22 | object Effect4: Destinations { 23 | override val value = "Effect4" 24 | } 25 | 26 | object Effect5: Destinations { 27 | override val value = "Effect5" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/alexmercerind/example/HomeScreen.kt: -------------------------------------------------------------------------------- 1 | package com.alexmercerind.example 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import androidx.compose.foundation.clickable 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.foundation.layout.size 9 | import androidx.compose.foundation.layout.wrapContentSize 10 | import androidx.compose.foundation.lazy.LazyColumn 11 | import androidx.compose.foundation.shape.CircleShape 12 | import androidx.compose.material3.ExperimentalMaterial3Api 13 | import androidx.compose.material3.Icon 14 | import androidx.compose.material3.IconButton 15 | import androidx.compose.material3.LargeTopAppBar 16 | import androidx.compose.material3.ListItem 17 | import androidx.compose.material3.ListItemDefaults 18 | import androidx.compose.material3.Scaffold 19 | import androidx.compose.material3.Surface 20 | import androidx.compose.material3.Text 21 | import androidx.compose.material3.TopAppBarDefaults 22 | import androidx.compose.material3.rememberTopAppBarState 23 | import androidx.compose.runtime.Composable 24 | import androidx.compose.ui.Alignment 25 | import androidx.compose.ui.Modifier 26 | import androidx.compose.ui.draw.clip 27 | import androidx.compose.ui.draw.clipToBounds 28 | import androidx.compose.ui.graphics.Color 29 | import androidx.compose.ui.input.nestedscroll.nestedScroll 30 | import androidx.compose.ui.platform.LocalContext 31 | import androidx.compose.ui.res.painterResource 32 | import androidx.compose.ui.res.stringResource 33 | import androidx.compose.ui.unit.dp 34 | import androidx.navigation.NavController 35 | 36 | @OptIn(ExperimentalMaterial3Api::class) 37 | @Composable 38 | fun HomeScreen( 39 | navController: NavController, 40 | effects: Map> 41 | ) { 42 | 43 | val topAppBarState = rememberTopAppBarState() 44 | val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( 45 | state = topAppBarState, 46 | snapAnimationSpec = null 47 | ) 48 | Scaffold( 49 | containerColor = Color.Black, 50 | topBar = { 51 | LargeTopAppBar( 52 | title = { Text(text = stringResource(id = R.string.app_name)) }, 53 | scrollBehavior = scrollBehavior, 54 | colors = TopAppBarDefaults.largeTopAppBarColors( 55 | containerColor = Color.Black, 56 | titleContentColor = Color.White, 57 | scrolledContainerColor = Color.Black, 58 | actionIconContentColor = Color.White 59 | ), 60 | actions = { 61 | val context = LocalContext.current 62 | IconButton( 63 | onClick = { 64 | val intent = Intent( 65 | Intent.ACTION_VIEW, 66 | Uri.parse("https://github.com/alexmercerind/moving-letters-android") 67 | ) 68 | context.startActivity(intent) 69 | } 70 | ) { 71 | Icon( 72 | painter = painterResource(id = R.drawable.github), 73 | contentDescription = stringResource(id = R.string.github) 74 | ) 75 | } 76 | } 77 | ) 78 | } 79 | ) { padding -> 80 | LazyColumn( 81 | modifier = Modifier 82 | .fillMaxSize() 83 | .padding(padding) 84 | .nestedScroll(scrollBehavior.nestedScrollConnection) 85 | ) { 86 | val list = effects.entries.withIndex().toList() 87 | for (i in list) { 88 | item { 89 | ListItem( 90 | modifier = Modifier.clickable { 91 | navController.navigate(list[i.index].value.key.value) 92 | }, 93 | leadingContent = { 94 | Surface( 95 | modifier = Modifier 96 | .size(32.dp) 97 | .clip(CircleShape) 98 | .clipToBounds(), 99 | contentColor = Color.Black 100 | ) { 101 | Text( 102 | modifier = Modifier.wrapContentSize(Alignment.Center), 103 | text = (i.index + 1).toString() 104 | ) 105 | } 106 | }, 107 | headlineContent = { 108 | Text(text = stringResource(R.string.effect, i.index + 1)) 109 | }, 110 | colors = ListItemDefaults.colors( 111 | headlineColor = list[i.index].value.value.first, 112 | containerColor = list[i.index].value.value.second, 113 | ) 114 | ) 115 | } 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/java/com/alexmercerind/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.alexmercerind.example 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.SystemBarStyle 6 | import androidx.activity.compose.setContent 7 | import androidx.activity.enableEdgeToEdge 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.ui.graphics.Color 10 | import androidx.compose.ui.text.font.FontWeight 11 | import androidx.navigation.compose.NavHost 12 | import androidx.navigation.compose.composable 13 | import androidx.navigation.compose.rememberNavController 14 | import com.alexmercerind.movingletters.FadeAnimatedText 15 | import com.alexmercerind.movingletters.JumpAnimatedText 16 | import com.alexmercerind.movingletters.RotateAnimatedText 17 | import com.alexmercerind.movingletters.ScaleInAnimatedText 18 | import com.alexmercerind.movingletters.ScaleOutAnimatedText 19 | 20 | class MainActivity : ComponentActivity() { 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | enableEdgeToEdge(statusBarStyle = SystemBarStyle.dark(resources.getColor(R.color.transparent))) 24 | setContent { 25 | MaterialTheme { 26 | val navController = rememberNavController() 27 | 28 | NavHost( 29 | navController = navController, 30 | startDestination = Destinations.Home.value 31 | ) { 32 | composable(Destinations.Home.value) { 33 | HomeScreen( 34 | navController = navController, 35 | effects = mapOf( 36 | Destinations.Effect1 to (Color(0xFFFFFFFF) to Color(0xFF9CA4B5)), 37 | Destinations.Effect2 to (Color(0xFFFFFFFF) to Color(0xFFE7C3B9)), 38 | Destinations.Effect3 to (Color(0xFFFFFFFF) to Color(0xFF234A54)), 39 | Destinations.Effect4 to (Color(0xFFFFFFFF) to Color(0xFFC1605D)), 40 | Destinations.Effect5 to (Color(0xFFFFFFFF) to Color(0xFF46373C)) 41 | ) 42 | ) 43 | } 44 | composable(Destinations.Effect1.value) { 45 | AnimatedTextScreen( 46 | name = "ScaleInAnimatedText", 47 | content = { state, text -> 48 | ScaleInAnimatedText( 49 | state = state, 50 | text = text, 51 | style = MaterialTheme.typography.displayLarge.copy(fontWeight = FontWeight.Black), 52 | animateOnMount = false 53 | ) 54 | }, 55 | contentColor = Color(0xFFFFFFFF), 56 | containerColor = Color(0xFF9CA4B5) 57 | ) 58 | } 59 | composable(Destinations.Effect2.value) { 60 | AnimatedTextScreen( 61 | name = "ScaleOutAnimatedText", 62 | content = { state, text -> 63 | ScaleOutAnimatedText( 64 | state = state, 65 | text = text, 66 | style = MaterialTheme.typography.displayLarge.copy(fontWeight = FontWeight.Black), 67 | animateOnMount = false 68 | ) 69 | }, 70 | contentColor = Color(0xFFFFFFFF), 71 | containerColor = Color(0xFFE7C3B9) 72 | ) 73 | } 74 | composable(Destinations.Effect3.value) { 75 | AnimatedTextScreen( 76 | name = "FadeAnimatedText", 77 | content = { state, text -> 78 | FadeAnimatedText( 79 | state = state, 80 | text = text, 81 | style = MaterialTheme.typography.displayLarge.copy(fontWeight = FontWeight.Black), 82 | animateOnMount = false 83 | ) 84 | }, 85 | contentColor = Color(0xFFFFFFFF), 86 | containerColor = Color(0xFF234A54) 87 | ) 88 | } 89 | composable(Destinations.Effect4.value) { 90 | AnimatedTextScreen( 91 | name = "JumpAnimatedText", 92 | content = { state, text -> 93 | JumpAnimatedText( 94 | state = state, 95 | text = text, 96 | style = MaterialTheme.typography.displayLarge.copy(fontWeight = FontWeight.Black), 97 | animateOnMount = false 98 | ) 99 | }, 100 | contentColor = Color(0xFFFFFFFF), 101 | containerColor = Color(0xFFC1605D) 102 | ) 103 | } 104 | composable(Destinations.Effect5.value) { 105 | AnimatedTextScreen( 106 | name = "RotateAnimatedText", 107 | content = { state, text -> 108 | RotateAnimatedText( 109 | state = state, 110 | text = text, 111 | style = MaterialTheme.typography.displayLarge.copy(fontWeight = FontWeight.Black), 112 | animateOnMount = false 113 | ) 114 | }, 115 | contentColor = Color(0xFFFFFFFF), 116 | containerColor = Color(0xFF46373C) 117 | ) 118 | } 119 | } 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_code_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_pause_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_shutter_speed_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_stop_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/github.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmercerind/moving-letters-android/f732395178e09fe3c59f11a4d7af314d88ae63b6/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmercerind/moving-letters-android/f732395178e09fe3c59f11a4d7af314d88ae63b6/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmercerind/moving-letters-android/f732395178e09fe3c59f11a4d7af314d88ae63b6/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmercerind/moving-letters-android/f732395178e09fe3c59f11a4d7af314d88ae63b6/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmercerind/moving-letters-android/f732395178e09fe3c59f11a4d7af314d88ae63b6/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmercerind/moving-letters-android/f732395178e09fe3c59f11a4d7af314d88ae63b6/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmercerind/moving-letters-android/f732395178e09fe3c59f11a4d7af314d88ae63b6/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmercerind/moving-letters-android/f732395178e09fe3c59f11a4d7af314d88ae63b6/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmercerind/moving-letters-android/f732395178e09fe3c59f11a4d7af314d88ae63b6/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexmercerind/moving-letters-android/f732395178e09fe3c59f11a4d7af314d88ae63b6/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #00000000 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Moving Letters 3 | Code 4 | Effect %1$d 5 | GitHub 6 | Pause 7 | Resume 8 | Start 9 | State 10 | Stop 11 | The quick brown fox jumps over the lazy dog! 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |