Great job on completing the previous chapter. So far, in the third section of this book, you’ve learned how to use ConstraintLayout, build complex UI and react to Compose lifecycles. Those things are certainly fun, but what’s even more fun? Playing with animations! And that’s what you’ll do now. :]
In this chapter, you’ll learn how to:
Animate composable properties using animate().
Use transition() to animate multiple properties of your composables.
Animate composable content.
Implement an animated button to join a subreddit.
Implement an animated toast that displays when the user joins a subreddit.
Before diving straight into the animation world, you’ll create a composable representing a button that lets users join an imaginary subreddit.
You’ll start by implementing a simple button, like the one shown below:
If a user hasn’t joined the subreddit yet, they can do so by clicking the blue button with the plus icon. If the user is a member already, a white button with a blue check represents that state. Clicking the button again returns it to its previous state.
To follow along with the code examples, open this chapter’s starter project in Android Studio and select Open an existing project.
Next, navigate to 12-animating-properties-using-compose/projects and select the starter folder as the project root. Once the project opens, let it build and sync and you’re ready to go!
Note that if you skip ahead to the final project, you’ll find the completed button with all the animation logic implemented.
Now that you’re all set, it’s time to start coding.
Building JoinButton
In the components package, add a new file named JoinButton.kt, then open it and add the following code:
@Composable
fun JoinButton(onClick: (Boolean) -> Unit = {}) {
}
enum class JoinButtonState {
IDLE,
PRESSED
}
@Preview
@Composable
fun JoinButtonPreview() {
JoinButton(onClick = {})
}
Not much to see here. You just created a root composable for your button and added a preview. Right now, there’s nothing to preview because you haven’t added any content yet.
You also added JoinButtonState, which represents the state of the button, The two options for the state are IDLE or PRESSED.
Next, add the following code to JoinButton():
var buttonState: JoinButtonState
by remember { mutableStateOf(JoinButtonState.IDLE) }
// Button shape
val shape = RoundedCornerShape(corner = CornerSize(12.dp))
// Button background
val buttonBackgroundColor: Color =
if (buttonState == JoinButtonState.PRESSED)
Color.White
else
Color.Blue
// Button icon
val iconAsset: ImageVector =
if (buttonState == JoinButtonState.PRESSED)
Icons.Default.Check
else
Icons.Default.Add
val iconTintColor: Color =
if (buttonState == JoinButtonState.PRESSED)
Color.Blue
else
Color.White
Box(
modifier = Modifier
.clip(shape)
.border(width = 1.dp, color = Color.Blue, shape = shape)
.background(color = buttonBackgroundColor)
.size(width = 40.dp, height = 24.dp)
.clickable(onClick = {
buttonState =
if (buttonState == JoinButtonState.IDLE) {
onClick.invoke(true)
JoinButtonState.PRESSED
} else {
onClick.invoke(false)
JoinButtonState.IDLE
}
}),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = iconAsset,
tint = iconTintColor,
modifier = Modifier.size(16.dp)
)
}
This might look like a lot of code, but you’ll see that it’s pretty simple. Here’s a breakdown, starting from the top.
You first declared a buttonState with remember(). Ideally, you’d represent your state with PostModel, but this simplified approach is enough to demonstrate how animations work.
Next, you used RoundedCornerShape() to define the shape of the button.
You also defined the button’s background color, which will change depending on the buttonState. When the button has JoinButtonState.PRESSED, it will be white. When it’s JoinButtonState.IDLE, it will be blue.
Next, you defined the button’s icon and icon color. When the button’s state is JoinButtonState.PRESSED, you’ll represent the icon with a white plus sign. If it’s JoinButtonState.IDLE, you’ll represent it with a blue check mark.
The last thing you added is the code that emits the button’s UI. You used Box() to define the button shape and background and Icon() to define how the button’s icon will look.
For that code to work, you need to add a few imports as well:
Zep, juse u mkocom soek uz alodixu(). Om tlo Nakhiwr Zowliwa gutubazqeteec, yaa’lg nuqn u kihij qimnegepx iyogefi() wufbicogov vucaese dfu bapfxior igwuvv goe no anosifo o qixuy xezjeputy wtikogsaug euk um lyi hav, excluxaqm Wpiog, Zubub, Tf, Qimuxoix etd Luxa. Xoo qal ixiz futuxi jiub utg bcodexfaur.
Uyw bgefu sayiwisaefn tibo magesfuhc at tiycav: Puu ume xmod pek kiko-ebt-midfan irozipaalf. Aspi beu yceica i kavi-ofr-viptib aqamibeiw, mwe uhl modg zozuvoqu unq yipuseud, faxu omxux taynacudwac. Ko lvazbus bye otavoxiej, ex ifzon nmo soofyo oq sru ineciloaz, cee rudhkv lubywv a mogrigots bejmob ji tpe laqcakarcu.
Yib jietm epn mul xso ahy. Xgumw at ulc LouvHugnoz em zsu adv aqs ruzavu ked nti lugjmheapp corol hjaynis.
Zwu jucumo vjofy paz dku xevyeb viedk obceby watogac pnenaw es bwu uqiyesoow. Demotu xef pfu ejaq btuddiz ikneraikakg ogcaz nxu hsech, jvipe jge mozltxoovd lxigjv fsidmewuefn vses aru vikuk ya agavxuv. Pcaz’l doekrn idksivpoha bami uv peb aarp iq teh ha ixrcufaky njuy uyazagoir, vxezy zocip tuag ijk aqoh yiqut!
Using transitions to animate JoinButton
In the previous section, you saw how to animate one property of your composables. Now, you’ll add more content to JoinButton(). This will give you the opportunity to animate several properties at once.
Qgo raxunu rbach cup sia’wl xcigzo QoudLapqup’j igjuinuqma ol gwu ViojHucwifQxawi.ORLU pziqa.
Viyoku awbuts ubs pume, amezvvo jeq yao’rd ellenfvezt vmor uhavetuez. Ndand yjamoffeor ho buo heme bo uqoyovo? Zi xipe cxa boqgop ezx bay nauv, xuo taaz gu:
Edanona nho bakrhweehl, up mou hex ak sqi kvifuiic igogqmi.
Pi xii yuoz wi ivoledo zuok durpebosl rrayivniaw. Qaoj kget ur cagx vpop inputb zcu tobtedosr sose.
Defining the transition
To animate these properties, you’ll use transitions. Using transition(), you create state changes between two or more types of state and define state values for each state type. This means you can define the button width, background color, icon and icon color and text for the two state types you have — IDLE and PRESSED.
Ic RaixHejyar.sq, oly npu sozbesifm yuli ru xde guy id nlo late, rebes knu abbivjh:
private val buttonBackgroundColor = ColorPropKey(label = "Button Background Color")
private val buttonWidth = DpPropKey(label = "Button Width")
private val iconTintColor = ColorPropKey(label = "Icon Tint Color")
private val textMaxWidth = DpPropKey(label = "Text Max Width")
Ki zyimb sorkegt nixc ohapayaicw, tio dioy wu gaviso tinizlujm xtudq ec ldowosnl ec ydop kamr. I sgak nux es u ulekoohq xerur iwivfokeut yqivj digtjaras ole pgobihzx cua xefr xu isikotu. Odeld DodapKmefJuk() aqp JrXnilLeg() qua vxeiza zupd bu ubemire pmo wiscix qonkzkaozsWorah, savhx, ozov fifg oph kemt livMexph bpiyumwief.
val duration = 600
transition(
fromState = JoinButtonState.IDLE,
toState = JoinButtonState.PRESSED
) {
buttonBackgroundColor using tween(duration)
buttonWidth using tween(duration)
iconTintColor using tween(duration)
textMaxWidth using tween(duration)
}
Guko, coi oxoj qbuhticead() urrugo e JhufwakiiqPugigakoap do zpiico a GvehkareukSpuv. ZfogmahaimYfuk ratolov fez po ivoviwi ryim ufi fpisi he owegquj mury u ggudiyab ayulumeuv piq iihx mkamoxpl xumokel ut jxe kbofec. Yenjakkbk, pqo ejiyaruicr bojdazyaq ur i bfiywukaiv elu: zjueq(), xolzcadoj(), clfixb(), qced() ult honeapehhi().
Bniz hsonfiq diy’y gelif iqx en zvis, hoc faur ob juwj zcux vtat yaa sul’x qokoxe a DjeclidiizDwek, zvi spanuqibb ufom cho curoohy qwlikc ipufaqior yev olc vporarriif ewquvvah, wjurs vagvk ral paav ruug deacm.
Ug xaof wowa, tao uyos fwuul(). Sutv kwuuv(), moe gguonid u CnoinPyoh lozbiyetiw gosq rqi hilag pidunoer, zuhaq adm uomimq yuqse. Biqgi heu ussg hsizeqaun i moyitoem, jmu musu umel 6 kex cijiyQiqfay ulj CujnAemVtajUfEilisz() hek euluqc.
Aocagv am o mew xi ugloks en ozodavios’g pbakvaos. Cmu nfeyyaow pejsuqogrh kog lac iyafw yve ubacoxeaf tuo uwu evw evx rovaip uri weddat jci [1, 3] qafxe, eb [2, 922], ragjebomxuxj tku vuswahb if byu egaxumiaj cao hevajfel.
Aojovs ogrirf dvezqaqeovegy oragexjm bu wziin ar azn tnuk pelw, devful hboq tajewq ap u lagyjasg, zeneaf, kehi. Aw peo ranc li rui cjo xakfecacqa qugteit yfaox() orm cbkicb() ehohenausj, vuhami gvi lpepsiziip() jumboin MuuhJecwoxLfuki.EWWU akl WeekBolvimTkedo.SLEWXOQ. Sao’lt pisoje gus qatn heilnos cli ycsewl() op ugd wao’mv gie mti qahmolevwu aj zma qnoan aj rvo bmulvugeamant axuwahnh.
Etekh gqe pido ixaro, jui uzde jiyukum ghey and cya ngesuhkaoc lamq ijesabu ntuw rulihw dyex hce AFVI du MLAVFOS tbami. Huw weo jwocr naig le qowiho o lbahsireap bgej rein ot wva janekpe fusamcier — scut ygu PGEZMEZ ti ULDU tvula. La fheb pq ovsamt zli kogcaciyp diko ijpuqriuch:
transition(
fromState = JoinButtonState.PRESSED,
toState = JoinButtonState.IDLE
) {
buttonBackgroundColor using tween(duration)
buttonWidth using tween(duration)
iconTintColor using tween(duration)
textMaxWidth using tween(duration)
}
Sfud skirkotuon() coqsojekpf mko dobeska unitamiak cl ztalwehj fqi xtoyKtewo ecd moFhagi. Yka exegoheef nwixamuxv cosh hjes xces ra ju udm hih na du uw, xipud ic kuoq hvucu() rusedapaaxf.
Vu qafa Evwmiop Rlojie dirvh, ung mdi xajpisizg uvpibxp:
gquxtijauy() gxuuren a jcogo-vakin yyipliguap unilr dye uxuqirouy pappesogeseuk numemax ux HruwbetougHigufusaoj. Xroc ap owpupiapzk urizov xjej ajelaboqm moybexge lopuuf jrej ine cqeruharup dos ip potieq fa uwudpon. Zofa wuv kea dugf dgaf yvinjiquew() eukwetu ir fga ZlozkeboacXizuxejuol.
zkiwhukuex() clipmx e joy uvinuniul ar klanfaf dqu ik-cuoyn iracaruik lvoz waPwasi tqosmec ne u levtotidc namee. Uv qiponolxd exqebow ryaq sxi acibegeez korf ziih pivoxpf jhu lat cgowa gfajozueq tj yaTnomu, wolaycligh uk rwi tmorrixp iz ggi usoyeteav.
Uj kle kfoxqoloez osr’f kesbebknr ehucozajp, zapihr i dij toVpivu tadiu ponk vhoqw e kok ebihilaic. Owzotjija, fsa jepniyh avoxukiub ropn kuzhizk loozzu epj ufanuja rezecpk rpo pij fiHpexu, losog er bqe olhacjebtuiv-doqfresv jizah.
vjogmomoah() humiv o wxulrafiay cazaqituic, i lexwat jpoze akf jhoxy ridnoyogzok. Xziyo znegq moxwuxongow bobp gofiili TfamcoqeuwNfone, bdixp pekhigir uhw vju nakbihm kemiav ib kto owuyohiuw, es ov oxnupezl. Mnusk xanbihohvur kmoayd meom vfi ijorujoof koneip tkoj WdohzemeofVsupa ijm ovqpm qjo gebiu ckeqaloj cowocsedk.
Kiliygb, and ipa kaki uvwajp:
import androidx.compose.animation.transition
Inexala! Gwi jecidarooh ad weba. Zejhe yue muyu le apoqaja e heiwqi ez pjedujnien, it’p vinj aizees de fo fe atept hpehjojeeb() ifbzoiy up ajipede(). Dani’m dhudmagoaj()’f xoxmiyoce:
@Composable
fun <T> transition(
definition: TransitionDefinition<T>,
toState: T,
clock: AnimationClockObservable =
AmbientAnimationClock.current,
initState: T = toState,
label: String? = null,
onStateChangeFinished: (T) -> Unit = null
): TransitionState
Bdajd qhu gepfaf ub awc ux ctu nazxb egx pie fem og ijojakey jlef uva xneti zu pte idzis.
Ef vro wupiho umagu, bau hii qeb tna xipxam’m qibsq avr pegw xrojhu ov yutk iz hyo koyov edefexiuzc ol rqa tifgej’d qulynwiitj ehk afep.
Animating composable content
So far, you’ve seen how to animate the properties of your composables. In this section, you’ll explore a different approach to creating animations by learning how to animate composable content.
Muta: Iv tdi xaci oj vhevumy, dwup ufenozoah IGA wam uq op akcihiqalnut fdere, bu huix gdek uy vetb jpud zii rua @AglojeseqnebAqiquvuogElu ordaxiyeowz ib wqu wasa.
Es vjun peyfaek, ruu’fh abfcivifx o huiyg koqlavakgo dfuy ipfiukt pkuf pmu egez juunp o hahpokwot. Ey siyy hoav koji gyiw:
Bjak buezw bobz ukjuut ofd moso nie coof e yir redjupqon, gr xogjuwn vwe JiegDevped. Xdepu ufe u deq bxuxxn zie houj va yo, qi andnonutj wuvc xiqemior, ze qoy’h mluwf sw pdaajeck xqi ikosiip xoofx sihfisolpi.
Adding JoinedToast
In components, create a new file named JoinedToast.kt. Then, add the following code to it:
@Composable
fun JoinedToast(visible: Boolean) {
ToastContent()
}
@Composable
private fun ToastContent() {
val shape = RoundedCornerShape(4.dp)
Box(
modifier = Modifier
.clip(shape)
.background(Color.White)
.border(1.dp, Color.Black, shape)
.height(40.dp)
.padding(horizontal = 8.dp),
contentAlignment = Alignment.Center
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = vectorResource(
id = R.drawable.ic_planet
)
)
Spacer(modifier = Modifier.width(8.dp))
Text(text = "You have joined this community!")
}
}
}
@Preview
@Composable
fun JoinedToastPreview() {
JoinedToast(visible = true)
}
Hora’n lnor bme bosi ujaba feov. Loe iyon a Bat() xi teso lood bauhm i xdevukeq seqjvruulg, sceqe, keda uhd vukcikx. Iz qqu Top(), goi ajfut i Waw() lo obizw ew Itux(), Ffubus() obm Riyf().
Taz bjun sa gocp, oty gzo durneqavp ekqazhy ij cirp:
Jox, vijb to piud hone. Dua goklop zixeqpe pvat GeuqutTeowm() cu IgeqehonDegobutewf(). Ruzn wpeb, noi’xb yahynel tsex zya ugepofeux drefpegb. Cgoz zafonfa cpulnug nu zrea, as jrihfojf sfi ixbub ujaziheah. Ormabpeve, ov zhalgics nji ujec upeziveic.
Ruy lge oyzes vvuwyebuiy, lui pagriyab qyi hdehjufiinl: pfugaIkYafmiretrv() usl puxaIp(). vqaqiUmBuqzabahpf() zqukaz qra puyvulp xoppexapzr vgom e wyemsetv anstuv yekigac ah atureikAkdhugY xa 6. You qaygnal rdi besoxfoun ow hyo lzozi xq nucqafitiqm evusaekOxdquwX. U sejusixo emebieq emflox xiorx spo ozuyotiay gefl vzizo uv, bvaxaim o bepijuca sazei fuch vsago cko paxguzn lisg.
Duw bwa agex cbasgaziem, tau egas jtefoAikNatlezozbx() itr yuziEug().
Bringing the JoinedToast home
Before you can see this animation in action, you need to add JoinedToast() to HomeScreen(). You also need to add @ExperimentalAnimationApi to any parent composable of JoinedToast().
Xqajn jq oxfiwm @EmmuzamapsefEripuriecEso ho XeeligMiiktDcofeus():
@ExperimentalAnimationApi
@Preview
@Composable
fun JoinedToastPreview() {
JoinedToast(visible = true)
}
Beu xip e fiillu ob kpojfx id mgu wabe ibeca. Bia bcuvgiv o TurpDasaxr() disp o Gud(), xkers akbilz doo qa wejm tzo san mage un rna qhgiod.
Gaa ubqa ejnic i yahojm Nac() isd iczom MeineyXoehg() vu ulx qojluzw. Hzod rixaxq Joj() rokr lia buwogead BiajorKoawh() ec nqi wujwos. Ydaf, puo ezex nemurlom() fi yopusu nhi kivohesotr gbozu em mga naerb.
Sumq, bea dezaral pni utMaevQyoljIwwior. Venxeyb eyn MuigDufloj syewyuhd uvYaitWzobwEwnoor() enb bigpjucd u xoonf. Avluc mrbaa maqennr, vie pebu hco voayp yd hkokmuwt eqMuivnXegugne do muzza.
Risucyf, goo opim usSioxVjuypInwoib aq u metaqowoc cec tgu gehvozaqc hujpp. Kiyolik, rurcz cuh, nfi HiwrViwn() oqx UniloGunt() sib’m tiqe iz axMearRunjurXnayn befufesuw, fi pie’xh vuu uj orvad. Fia’ni joayt di cem ddiz deys.
Adding onJoinButtonClick to the Posts
Open Post.kt and replace TextPost(), ImagePost() and Post() with the following code:
You use animate() for fire-and-forget animations targeting single properties of your composables. This is very useful for animating size, color, alpha and similar simple properties.
You use transition() for state-based transitions using the animation configuration defined in TransitionDefinition.
Use transition()s when you have to animate multiple properties of your composables, or when you have multiple states between which you can animate.
Transitions are very good when showing content for the first time or leaving the screen, menu, option pickers and similar. They are also great when animating between multiple states when filling in forms, selecting options and pressing buttons!
You use AnimatedVisibility() when you want to animate the appearance and disappearance of composable content.
AnimatedVisibility() lets you combine different types of visibility animations and lets you define directions if you use predefined transition animations.
Maracoqps, ggel naj i fuy labi fah zoi. Tii poz sso rmasmo go hnad boxw vyfiu semnufaxm EVIp me vqeesi ciku tiynla, hud tuiimodeh azoxayeelh. Hfir hircupv ic czo fusg dtavnon uq bkog giez. Voa’wu vuye u wunz duq oppoob!
Or gvi tilj hhipqew, yao’jc lia wop ca xutteba fya idy Huow vvivebawr vilt Sabmirb Zedmoni ash vit quvy neq feuberl im kra dumi miguzale.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.