How does shared element work in android jetpack compose?
Asked Answered
R

0

6

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)
                    }
                })
        }

enter image description here

When I click BottomSheet item to start shared element transition and navigate UserDetailsScreen.

enter image description here

Reinke answered 27/6, 2023 at 8:42 Comment(1)
You are not disposing your root view. If you check the repo you shared above, after the transition they dispose the root: github.com/mxalbert1996/compose-shared-elements/blob/…Reddick

© 2022 - 2024 — McMap. All rights reserved.