-
-
Notifications
You must be signed in to change notification settings - Fork 368
/
TextAnimationActivity.kt
209 lines (196 loc) · 9.68 KB
/
TextAnimationActivity.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package com.example.jetpackcompose.animation
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.animateColor
import androidx.compose.animation.core.FastOutLinearInEasing
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.animation.core.updateTransition
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.drawscope.rotate
import androidx.compose.ui.res.imageResource
import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.PlaceholderVerticalAlign
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp
import com.example.jetpackcompose.R
class TextAnimationActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// This is an extension function of Activity that sets the @Composable function that's
// passed to it as the root view of the activity. This is meant to replace the .xml file
// that we would typically set using the setContent(R.id.xml_file) method. The setContent
// block defines the activity's layout.
setContent {
TextAnimationComponent()
}
}
}
// We represent a Composable function by annotating it with the @Composable annotation. Composable
// functions can only be called from within the scope of other composable functions. We should
// think of composable functions to be similar to lego blocks - each composable function is in turn
// built up of smaller composable functions.
@Composable
fun TextAnimationComponent() {
// Annotate string is used to define a text with multiple styles.
val text = buildAnnotatedString {
// appended string
append("Jetpack ")
// Add inline content. Inline content is used to describe/tag sections inside the Text
// composable that we will replace by other composables tagged with the same id. For
// example, we add two sections with id's "composeLogo" & "animatedText" below. We will
// reference the same id's in the inlineContent map that we will be passing to the Text
// Commposable.
appendInlineContent("composeLogo", "Compose Logo")
appendInlineContent("animatedText", "Animated Text")
}
val inlineContent = mapOf(
"composeLogo" to InlineTextContent(
placeholder = Placeholder(
width = 2.em,
height = 1.em,
placeholderVerticalAlign = PlaceholderVerticalAlign.AboveBaseline
),
children = {
ComposeLogoComponent()
}
),
"animatedText" to InlineTextContent(
placeholder = Placeholder(
width = 5.em,
height = 35.sp,
placeholderVerticalAlign = PlaceholderVerticalAlign.AboveBaseline
),
children = {
ColorChangingTextComponent()
}
)
)
// Column is a composable that places its children in a vertical sequence. You
// can think of it similar to a LinearLayout with the vertical orientation.
// In addition we also pass a few modifiers to it.
// You can think of Modifiers as implementations of the decorators pattern that are used to
// modify the composable that its applied to. In the example below, we configure the
// Box to occupy the entire available height & width using the Modifier.fillMaxSize() modifier.
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
// Text is a predefined composable that does exactly what you'd expect it to - display text
// on the screen. It allows you to customize its appearance using style, fontWeight,
// fontSize, etc.
// In addition, we also pass it the inlineContent map that is used to describe how we
// will specify alternate composables to describe areas within the Text composable.
Text(
text = text,
style = TextStyle(
fontFamily = FontFamily.Serif,
fontSize = 35.sp
),
inlineContent = inlineContent
)
}
}
// We represent a Composable function by annotating it with the @Composable annotation. Composable
// functions can only be called from within the scope of other composable functions. We should
// think of composable functions to be similar to lego blocks - each composable function is in turn
// built up of smaller composable functions.
@Composable
fun ComposeLogoComponent() {
// There are multiple methods available to load an image resource in Compose. However, it would
// be advisable to use the painterResource method as it loads an image resource asynchronously
val image = ImageBitmap.imageResource(R.drawable.compose_logo)
// rememberInfiniteTransition is used to create a transition that uses infitine
// child animations. Animations typically get invoked as soon as they enter the
// composition so don't need to be explicitly started.
val infiniteTransition = rememberInfiniteTransition()
// Create a value that is altered by the transition based on the configuration. We use
// the animated float value the returns and updates a float from the initial value to
// target value and repeats it (as its called on the infititeTransition).
val rotation by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween<Float>(
durationMillis = 3000,
easing = FastOutLinearInEasing,
),
)
)
// You can think of Modifiers as implementations of the decorators pattern that are
// used to modify the composable that its applied to. In this example, we configure the
// Image composable to have a height of 48 dp.
Canvas(Modifier.size(48.dp)) {
// As the Transition is changing the interpolating the value of the animated float
// "rotation", you get access to all the values including the intermediate values as
// its being updated. The value of "rotation" goes from 0 to 360 and transitions
// infinitely due to the infiniteRepetable animationSpec used above.
rotate(rotation) {
drawImage(image)
}
}
}
// We represent a Composable function by annotating it with the @Composable annotation. Composable
// functions can only be called from within the scope of other composable functions. We should
// think of composable functions to be similar to lego blocks - each composable function is in turn
// built up of smaller composable functions.
@Composable
fun ColorChangingTextComponent() {
// Reacting to state changes is the core behavior of Compose. You will notice a couple new
// keywords that are compose related - remember & mutableStateOf.remember{} is a helper
// composable that calculates the value passed to it only during the first composition. It then
// returns the same value for every subsequent composition. Next, you can think of
// mutableStateOf as an observable value where updates to this variable will redraw all
// the composable functions that access it. We don't need to explicitly subscribe at all. Any
// composable that reads its value will be recomposed any time the value
// changes. This ensures that only the composables that depend on this will be redraw while the
// rest remain unchanged. This ensures efficiency and is a performance optimization. It
// is inspired from existing frameworks like React.
val currentColor by remember { mutableStateOf(Color.Red) }
val transition = updateTransition(currentColor)
val color by transition.animateColor { state ->
when (state) {
Color.Red -> Color.Green
Color.Green -> Color.Blue
Color.Blue -> Color.Red
else -> Color.Red
}
}
// As the Transition is changing the interpolating the value of your props based
// on the "from state" and the "to state", you get access to all the values
// including the intermediate values as they are being updated. We can use the
// state variable and access the relevant props/properties to update the relevant
// composables/layouts. Below, we use state[color] to get get the latest value of color
// and use it to set the color of the Text composable.
Text(
text = "Compose",
color = color,
style = TextStyle(
fontFamily = FontFamily.Serif,
fontSize = 35.sp
)
)
}