I want to add an animation to the texts, which will work automatically when the screen is opened
In Android View it can be done easily by just putting this in OnCreate
val animation = AnimationUtils.loadAnimation(this,R.anim.example)
text.startAnimation(animation)
How can the same idea work in Compose ?
What I know in Compose is that the state must be changed to run a particular element animation
You can use a side effect
DisposableEffect(Unit) {
//do something
onDispose { }
}
or:
LaunchedEffect(Unit) {
//update the value to start the animation
}
Related
I know how to implement BottomSheet in Material 2 Jetpack Compose using BottomSheetScaffold.
But there is no BottomSheetScaffold in Material 3. Also, there is nothing in official samples about BottomSheet.
So I was able to make it work!
It seems that, as of today, BottomSheetScaffold is not available yet on Material3, this is discussed in this issue I found digging around: https://issuetracker.google.com/issues/229839039
I quote the important part from the reply of a google dev:
we aren't in a super easy spot with Swipeable. It currently has a number of critical bugs that need to be addressed first (we are working on this), which is why we are limiting the surface we are exposing for Swipeable in M3 for the time. Our plan for the coming months is to focus on this specific area and improve developer experience.
Material 3 for Jetpack Compose is still in alpha - this means we
consider components production-ready, but the API shape is flexible
while in alpha. This gives us space to iterate while getting
real-world feedback from developers, which ultimately helps improve
your experience. Copy-pasting source code for components that are not
(fully) implemented or exposed in an alpha version can be a good thing
to do in the meantime! Owning the source code while the API shape is
still flexible gives you a number of benefits like ease of updating
dependencies, even if the APIs change, and allows you to evolve your
components in your own pace.
So I just followed the advice and I copy pasted BottomSheetScaffold into my project. Of course it did not work straight away because of a few missing classes and some minor API changes. At the end I was able to make it work by pulling and hacking the following classes and adding them to my project:
BottomSheetScaffold.kt
Drawer.kt
Strings.kt
Swipeable.kt
I have created a gist with the source code if you want to try:
https://gist.github.com/Marlinski/0b043968c2f574d70ee6060aeda54882
You will have to change the import to make it work on your project as well as add the "-Xjvm-default=all" option by adding the following into your gradle file in the android{} section:
android{
...
kotlinOptions {
freeCompilerArgs += ["-Xjvm-default=all"]
// "-Xjvm-default=all" option added because of this error:
// ... Inheritance from an interface with '#JvmDefault' members is only allowed with -Xjvm-default option
// triggered by porting BottomSheetScaffold for Material3 on Swipeable.kt:844
}
}
It works very well for me, will keep this solution until it is officially supported in material3.
Hope it helps!
I got pretty similar results using a fullscreen dialog with AnimatedVisibility, here is the code if interested:
// Visibility state for the dialog which will trigger it only once when called
val transitionState = remember {
MutableTransitionState(false).apply {
targetState = true
}
}
Dialog(
onDismissRequest = {} // You can set a visibility state variable to false in here which will close the dialog when clicked outside its bounds, no reason to when full screen though,
properties = DialogProperties(
// This property makes the dialog full width of the screen
usePlatformDefaultWidth = false
)
) {
// Visibility animation, more information in android docs if needed
AnimatedVisibility(
visibleState = transitionState,
enter = slideInVertically(
initialOffsetY = { it },
animationSpec = ...
),
exit = slideOutVertically(
targetOffsetY = { it },
animationSpec = ...
)
)
) {
Box(
modifier = Modifier.fillMaxSize().background(color = ...)
) {
// Your layout
// This can be any user interraction that closes the dialog
Button(
transitionState.apply { targetState = false }
) ...
}
}
All of this is in a composable function that gets called when a UI action to open said dialog is performed, it's not ideal but it works.
Hope I was able to help!
There is already a great answer by Marlinski, but i would like to add that there is also a ModalBottomSheetLayout which also does not have any implementation for Material 3.
I created a gist for people who need it in use right now:
https://gist.github.com/Pasha831/bdedcfee01acdc96cf3ae643da64f88a
We finally have ModalBottomSheet in Material3.
var openBottomSheet by rememberSaveable { mutableStateOf(false) }
val bottomSheetState = rememberSheetState(skipHalfExpanded = true)
// Sheet content
if (openBottomSheet) {
ModalBottomSheet(
onDismissRequest = { openBottomSheet = false },
sheetState = bottomSheetState,
) {
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
Button(
// Note: If you provide logic outside of onDismissRequest to remove the sheet,
// you must additionally handle intended state cleanup, if any.
onClick = {
scope.launch { bottomSheetState.hide() }.invokeOnCompletion {
if (!bottomSheetState.isVisible) {
openBottomSheet = false
}
}
}
) {
Text("Hide Bottom Sheet")
}
}
}
}
For more link here: https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#ModalBottomSheet(kotlin.Function0,androidx.compose.ui.Modifier,androidx.compose.material3.SheetState,androidx.compose.ui.graphics.Shape,androidx.compose.ui.graphics.Color,androidx.compose.ui.graphics.Color,androidx.compose.ui.unit.Dp,androidx.compose.ui.graphics.Color,kotlin.Function0,kotlin.Function1)
I am familiar with the Transition API in Jetpack Compose that lets me easily animate between two states inside a Compose component.
But what would be the best way to animate between three different states?
Consider as an example a loading indicator for a component. The states could be NotLoading, Loading and HasLoaded. My thinking here would be to have a loading indicator for a component and transition between those states:
Transition for showing the loading indicator: NotLoading -> Loading
Transition for showing the data: Loading -> HasLoaded
I guess what kind of transition doesn't really matter but I was thinking first fade in loading indicator then fade out loading indicator and fade in content. But this is just an example; in reality I need to specify the transition parameters.
What would be the best way to achieve this with Jetpack Compose? Not sure if my state thinking here is the best approach for this either.
You can use the Transition API with more than 2 states - and define the individual properties of each component using animate*AsState APIs.
There is another option if you have completely different Composables, you can use the AnimatedContent APIs.
For example, the below sample uses an enum UiState, and a button to change between the states. The content is then wrapped inside the AnimatedContent() composable. By default, the initial content fades out and then the target content fades in (this behavior is called fade through).
#Composable
fun AnimatedContentStateExample() {
Column {
var state by remember { mutableStateOf(UiState.Loading) }
Button(onClick = {
state = when (state) {
UiState.Loading -> UiState.Loaded
UiState.Loaded -> UiState.Empty
UiState.Empty -> UiState.Loading
}
}) {
Text("Switch States")
}
AnimatedContent(
targetState = state
) { targetState ->
// Make sure to use `targetState`, not `state`.
when (targetState) {
UiState.Loading -> {
CircularProgressIndicator()
}
UiState.Loaded -> {
Box(
Modifier
.background(androidGreen)
.size(100.dp))
Text("Loaded")
}
UiState.Empty -> {
Box(
Modifier
.background(androidBlue)
.size(200.dp))
Text("Empty")
}
}
}
}
}
You can customize this animation behavior by specifying a ContentTransform object to the transitionSpec parameter. You can create ContentTransform by combining an EnterTransition with an ExitTransition using the with infix function. You can apply SizeTransform to the ContentTransform by attaching it with the using infix function.
More information about AnimatedContent can be found here: https://developer.android.com/jetpack/compose/animation#animatedcontent.
And samples of its usage here: https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedContentSamples.kt
I am using a BottomNavigation with 4 composables. All of them have a LazyColumn with each item in the LazyColumn having an image populated from the network using Coil for Jetpack Compose. Similar to Twitter/YouTube.
When I navigate between these items, the composables get destroyed and recompose only when navigated back to them. Even the coil images are cleared and re-fetched (from memory or local storage) when navigated between these composables. This is of course the expected behavior.
The problem is that this is causing the navigation between them to be too slow. Coil images take about 400ms to 700ms to load the image for every navigation. Apps like YouTube/LinkedIn are literally instant in their BottomBar navigations.
When I was using XML for this, I would make the fragments(used as bottom nav items ) with an appear/disappear logic to avoid this time delay while navigating between them.
How do I achieve the same with Compose ?
I am using the following versions:
//compose navigation
implementation "androidx.navigation:navigation-compose:2.4.0-beta01"
implementation "com.google.accompanist:accompanist-navigation-animation:0.21.0-beta"
Well I had the same problem using emulator or phone both work well with small simplistic composables. And when I create more complex Composable and try animating the navigation, using the accompanist animation library it gets very laggy.
But then I tried building the release APK file, since it is usually optimized and way faster, and that will significantly speed up your app. Here is link on how you can generate signed APK then install it on your phone or emulator and see if that fixes your problem!
You could also check if you accidentally disabled the hardware acceleration from the manifest. Make sure you set android:hardwareAccelerated="true" in you activity tag.
In case that does not help either, then you have to implement your own animation and use Shared ViewModel, for communication and triggering the transition from one Composable to another. The idea is that you can use modifier offset property to show/hide the Composable by placing it outside the screen.
First set your ViewModel, and add mutable state variable, that will trigger the transition from Home to Settings and vice versa.
This is not the best practice, since there is no way to directly pass data from one composable to another as you would normally do with the normal navigation. But you can still share data using the Shared ViewModel. Using this method it will not recompose your Composable, and thus be really fast. So far I have no problem with any out of memory exceptions even on some very old/slow devices with 2GB RAM.
class SharedViewModel : ViewModel() {
// changing this mutable state will trigger the transition animation
private val _switchHomeToSetting = mutableStateOf(true)
val switchHomeToSetting: State<Boolean> = _switchHomeToSetting
fun switchHomeToSettings() {
_switchHomeToSetting.value = !_switchHomeToSetting.value
}
}
Now create your two Composable functions Home and Settings respectively
#Composable
fun HomeScreen(viewModel: SharedViewModel) {
// draw your subcomponents
}
#Composable
fun SettingsScreen(viewModel: SharedViewModel) {
// draw your subcomponents
}
And finally initialize the animation in you main activity
class MainActivity : ComponentActivity() {
val viewModel by viewModels<CityWeatherViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// set your offset animation
val density = LocalDensity.current
val width = with(density) { (1.adw).toPx() }
val animatedOffsetX: Float by animateFloatAsState(
targetValue = if (!viewModel.switchHomeToSetting.value) 0f else width,
animationSpec = tween(1200)
)
// Home screen
Box(
modifier = Modifier
.fillMaxSize()
.offset { IntOffset((-width + animatedOffsetX).toInt(), 0) }
) {
HomeScreen(viewModel = viewModel)
}
// Settings screen
Box(
modifier = Modifier
.fillMaxSize()
.offset { IntOffset(animatedOffsetX.toInt(), 0) }
) {
SettingsScreen(viewModel = viewModel)
}
}
}
}
Here is the result using the Composable Navigation, together with the Accompanist Animation. As you can see it is very laggy indeed
And now here is the result using our custom animation, where it is very smooth since no Composable is recomposed.
I am using android compose jetpack version "0.1.0-dev14". According to application requirement, I want to auto focus TextField once screen is visible. and another scenario is like I want to focus next TextField in same screen on next ImeAction.Next action. I could not find any method or solution for this problem. If Anyone can help me to sole this problem, It will be really appreciated.
Thank you..!!
With 1.0.0 to achieve autofocus you can use:
DisposableEffect(Unit) {
focusRequester.requestFocus()
onDispose { }
}
In this way the system grants focus to the component associated with this FocusRequester.
For example:
val focusRequester = FocusRequester()
TextField(
//...
modifier = Modifier
// add focusRequester modifier
.focusRequester(focusRequester)
)
This sample shows how to request focus on the next view.
To achieve autofocus on first view on the screen, I believe this can be used:
// ... composable context...
onActive(callback = {
focusModifiers.first().requestFocus()
})
// ... composable context...
Currently developping on a Pepper robot (Android dev), I'm trying to use some "basic" animations (from QiSQK lib).
For instance, when calling a WS, I'm starting a "think" animation using animation/animate. Then, when the WS call ends, I try to use another animation ("showing the tablet").
I saw that Pepper can't animate twice if the previous animation isn't finished/cancelled.
So, I used requestCancellation(), but it didn't stop the animation.
I also used cancel(mayInterruptIfRunning), didn't stop either.
So, I can't chain 2 animations without waiting the previous animation to stop (my WS call = 3-4s max).
Any idea ?
Example :
private var animate: Future<Animate>? = null
fun animate(animRes: Int) {
animate?.requestCancellation()
AnimationBuilder
.with(qiContext)
.withResources(animRes)
.buildAsync()
.thenConsume { futureAnimation ->
animate = AnimateBuilder
.with(qiContext)
.withAnimation(futureAnimation?.value)
.buildAsync()
animate?.andThenConsume {
it.async().run()
}
}
}
Thx,
Bastien.
Finally found out my issue.
Actually, I was storing the animate reference when creating the object like this :
animate = AnimateBuilder
.with(qiContext)
.withAnimation(futureAnimation?.value)
.buildAsync()
So, I printed my objects (like every Future's used in my callbacks), then I found that after using it.async().run() from my animate andThenConsume callback, which returns the running animation reference, is different from the one I created before (was thinking reusing same old ref).
So, instead, here my new (working) code :
fun animate(animRes: Int) {
//Cancelling possible running animation to display a new one
animate?.requestCancellation()
AnimationBuilder
.with(qiContext)
.withResources(animRes)
.buildAsync()
.thenConsume { futureAnimation ->
AnimateBuilder
.with(qiContext)
.withAnimation(futureAnimation?.value)
.buildAsync()
.andThenConsume {
animate = it.async().run() as Future<Animate>
}
}
}