I am trying to do shared element transition in jetpack compose to show user's image and name. I show user's image and name but there is no animation in my code and when the user's picture and name appear on the screen with the shared element, the components in the background, namely ProfileScreen, do not disappear. I don't know much thing shared element transition and there are not many resources I found a few repos on github and tried to implement it. What am I supposed to do to add animation in my code. probable there is a mistake or missing in my code.
Can someone who has implemented or understood the shared element before, help?
I tried this:
https://github.com/mxalbert1996/compose-shared-elements
hear is my code:
private var selectedUser: Int by mutableStateOf(-1)
@Composable
fun ProfileScreenRoute(
navHostController: NavHostController,
sharedViewModel: SharedViewModel,
viewModel: ProfileScreenViewModel = hiltViewModel()
) {
val state by viewModel.state.collectAsState()
ProfileScreen(
navHostController,
sharedViewModel,
state,
onChangeSelectedUser = viewModel::onChangeSelectedUser
)
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ProfileScreen(
navHostController: NavHostController,
sharedViewModel: SharedViewModel,
state: ProfileScreenState,
onChangeSelectedUser: (User) -> Unit
) {
var isShowSharedElementRoute by remember { mutableStateOf(false) }
val modalBottomSheetState =
rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
val scope = rememberCoroutineScope()
if (isShowSharedElementRoute) {
SharedElementsRoot {
val user = selectedUser
val gridState = rememberLazyGridState()
DelayExit(visible = user >= 0) {
UserDetailsScreen(
user = User(
userImage = sharedViewModel.userProfileImg,
userName = state.userName
)
)
}
}
}
ModalBottomSheetLayout(
sheetContent = {
ImagePickerSheet(
onClickImagePickerItem = { imagePickerClickedIndex ->
isShowSharedElementRoute = !isShowSharedElementRoute
selectedUser = selectedUser + 1
scope.launch {
modalBottomSheetState.animateTo(ModalBottomSheetValue.Hidden)
}
})
},
sheetState = modalBottomSheetState,
sheetShape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
) {
Scaffold(
backgroundColor = Color.Transparent,
topBar = {
AccountTopBar(
onBackClick = {
navHostController.navigate(DASHBOARD_ROUTE)
},
onSettingsClick = {
navHostController.navigate(ACCOUNT_SETTINGS_SCREEN_ROUTE)
},
screenDescription = "Profil",
backImage = R.drawable.ic_back,
settingsImage = R.drawable.ic_account_settings
)
}, bottomBar = {
DYTAppVersion()
}) { padding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(top = 20.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
ProfileImage(
userProfileImg = sharedViewModel.userProfileImg ?: Constant.DEFAULT_PROFILE_IMG,
userName = state.userName,
chooseImage = {
scope.launch {
modalBottomSheetState.animateTo(ModalBottomSheetValue.Expanded)
}
}
)
Spacer(modifier = Modifier.padding(bottom = 30.dp))
AccountScreenItem().accountScreenItemList.forEachIndexed { index, accountItem ->
AccountScreenItemComponent(
imgId = accountItem.imgId,
imgContentDescription = accountItem.imgContentDescription,
textContent = accountItem.textContent
) {
accountItem.route?.let { it ->
navHostController.navigate(it)
}
}
Divider(modifier = Modifier.padding(start = 15.dp, end = 15.dp))
}
}
}
}
}
@Composable
private fun UserDetailsScreen(user: User) {
Scaffold() { padding->
Column(
Modifier.fillMaxSize().padding(padding),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
SharedMaterialContainer(
key = "userAvatar",
screenKey = "DetailsScreen",
shape = MaterialTheme.shapes.medium,
color = Color.Transparent,
elevation = 10.dp,
transitionSpec = FadeOutTransitionSpec
) {
val scope = LocalSharedElementsRootScope.current!!
Image(
painter = rememberAsyncImagePainter(model = user.userImage),
contentDescription = "",
modifier = Modifier
.size(200.dp)
.clickable(enabled = !scope.isRunningTransition) { scope.changeUser(-1) },
contentScale = ContentScale.Crop
)
}
SharedElement(
key = "userName",
screenKey = "DetailsScreen",
transitionSpec = CrossFadeTransitionSpec
) {
Text(text = user.userName ?: "", style = MaterialTheme.typography.h1)
}
}
}
}
private fun SharedElementsRootScope.changeUser(user: Int) {
val currentUser = selectedUser
if (currentUser != user) {
val targetUser = if (user >= 0) user else currentUser
if (targetUser >= 0) {
}
selectedUser = user
}
}
private val FadeOutTransitionSpec = MaterialContainerTransformSpec(
durationMillis = 1000,
fadeMode = FadeMode.Out
)
private val CrossFadeTransitionSpec = SharedElementsTransitionSpec(
durationMillis = 1000,
fadeMode = FadeMode.Cross,
fadeProgressThresholds = ProgressThresholds(0.10f, 0.40f)
)
ImagePickerSheetItem
@Composable
fun ImagePickerSheetItem(
title: String,
onClickImagePickerListItem: (Int) -> Unit
) {
Column(modifier = Modifier.fillMaxWidth()) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 15.dp, bottom = 10.dp, top = 25.dp)
) {
Text(
text = title,
style = MaterialTheme.typography.subtitle1,
)
}
Divider()
Column(
modifier = Modifier
.fillMaxWidth()
.padding(15.dp),
verticalArrangement = Arrangement.spacedBy(40.dp)
) {
ProfileBottomSheetItemList().profileBottomSheetList.forEachIndexed { index, profileBottomSheetItem ->
Row(modifier = Modifier
.fillMaxWidth()
.clickable {
onClickImagePickerListItem(index)
}) {
Text(
text = profileBottomSheetItem.title,
fontSize = 15.sp
)
}
}
}
}
}
ImagePickerSheet
@Composable
fun ImagePickerSheet(
onClickImagePickerItem:(Int)->Unit
) {
BaseBottomSheet(
content = {
ImagePickerSheetItem(
title = "Options",
onClickImagePickerListItem = { clickedIndex ->
onClickImagePickerItem(clickedIndex)
})
},
boxCornerRadius = 5.dp)
}
BaseBottomSheet
@Composable
fun BaseBottomSheet(
boxCornerRadius: Dp = 16.dp,
content: @Composable () -> Unit
) {
Box(
modifier = Modifier
.wrapContentHeight()
.fillMaxWidth()
.background(MaterialTheme.colors.bottomSheetBackgroundColor)
.clip(RoundedCornerShape(topEnd = boxCornerRadius, topStart = boxCornerRadius)),
) {
Divider(
color = Color.Gray,
thickness = 5.dp,
modifier = Modifier
.padding(top = 15.dp)
.align(Alignment.TopCenter)
.width(80.dp)
.clip(RoundedCornerShape(50.dp))
)
content()
}
}
ProfileImage
@Composable
fun ProfileImage(
userProfileImg: String,
userName: String?,
chooseImage: () -> Unit
) {
val painter = userProfileImg
val img = rememberAsyncImagePainter(painter)
Box {
Image(
painter = img,
contentScale = ContentScale.Crop,
contentDescription = "",
modifier = Modifier
.size(96.dp)
.clip(CircleShape)
)
Box(
modifier = Modifier
.align(Alignment.BottomEnd)
.offset(y = 10.dp, x = 15.dp)
) {
Image(
painter = painterResource(id = R.drawable.ic_camera),
contentDescription = "",
modifier = Modifier.clickable {
chooseImage()
})
}
}
Spacer(modifier = Modifier.padding(bottom = 20.dp))
Text(
text = "userName" ?: "",
style = MaterialTheme.typography.subtitle1
)
Text(text = "01.02.2022 ")
}
When the user selects the first item of the BottomSheet (I just made any item selected by the user for trial purposes, in order not to confuse the work for now. I will share the code below as you can see. There is no check for selected item.) the user's picture and name will be animated using the shared element to the UserDetailsScreen.
ModalBottomSheetLayout(
sheetContent = {
ImagePickerSheet(
onClickImagePickerItem = { imagePickerClickedIndex ->
isShowSharedElementRoute = !isShowSharedElementRoute
selectedUser = selectedUser + 1
scope.launch {
modalBottomSheetState.animateTo(ModalBottomSheetValue.Hidden)
}
})
}
When I click BottomSheet item to start shared element transition and navigate UserDetailsScreen.