การเปลี่ยนองค์ประกอบแบบใช้ร่วมกันในเครื่องมือเขียน

การเปลี่ยนองค์ประกอบที่ใช้ร่วมกันเป็นวิธีที่ราบรื่นในการเปลี่ยนระหว่าง Composable ที่มีเนื้อหาที่สอดคล้องกัน โดยมักใช้สำหรับการนำทาง ซึ่งช่วยให้คุณเชื่อมต่อหน้าจอต่างๆ ด้วยภาพได้เมื่อผู้ใช้ไปยังส่วนต่างๆ ระหว่างหน้าจอเหล่านั้น

ตัวอย่างเช่น ในวิดีโอต่อไปนี้ คุณจะเห็นว่ารูปภาพและชื่อของขนมมีการแชร์จากหน้ารายการไปยังหน้ารายละเอียด

รูปที่ 1 การสาธิตองค์ประกอบที่ใช้ร่วมกันของ Jetsnack

ใน Compose มี API ระดับสูงบางรายการที่ช่วยคุณสร้างองค์ประกอบที่ใช้ร่วมกันได้ ดังนี้

  • SharedTransitionLayout: เลย์เอาต์นอกสุดที่จำเป็นสำหรับการใช้การเปลี่ยนภาพองค์ประกอบที่ใช้ร่วมกัน ซึ่งจะให้SharedTransitionScope คอมโพสิเบิลต้องอยู่ใน SharedTransitionScope เพื่อใช้ตัวแก้ไของค์ประกอบที่แชร์
  • Modifier.sharedElement(): ตัวปรับแต่งที่แจ้งให้ SharedTransitionScope ทราบถึง Composable ที่ควรจับคู่กับ Composable อื่น
  • Modifier.sharedBounds(): ตัวแก้ไขที่แจ้ง SharedTransitionScope ว่าควรใช้ขอบเขตของ Composable นี้เป็นขอบเขตคอนเทนเนอร์สำหรับตำแหน่งที่ควรเกิดการเปลี่ยนผ่าน sharedBounds() ออกแบบมาสำหรับเนื้อหาที่แตกต่างกันในเชิงภาพ ซึ่งต่างจาก sharedElement()

แนวคิดสำคัญเมื่อสร้างองค์ประกอบที่ใช้ร่วมกันใน Compose คือวิธีการทำงานร่วมกับภาพซ้อนทับและการคลิป ดูข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อสำคัญนี้ได้ที่ส่วนการครอบตัดและการวางซ้อน

การใช้งานพื้นฐาน

เราจะสร้างการเปลี่ยนผ่านต่อไปนี้ในส่วนนี้ โดยเปลี่ยนจากรายการ "ลิสต์" ขนาดเล็กเป็นรายการรายละเอียดขนาดใหญ่

รูปที่ 2 ตัวอย่างพื้นฐานของการเปลี่ยนองค์ประกอบที่ใช้ร่วมกันระหว่าง Composable 2 รายการ

วิธีที่ดีที่สุดในการใช้ Modifier.sharedElement() คือใช้ร่วมกับ AnimatedContent, AnimatedVisibility หรือ NavHost เนื่องจากจะจัดการการเปลี่ยนระหว่าง Composable ให้คุณโดยอัตโนมัติ

จุดเริ่มต้นคือ AnimatedContent พื้นฐานที่มีอยู่ซึ่งมี MainContent และ DetailsContent Composable ก่อนที่จะเพิ่มองค์ประกอบที่ใช้ร่วมกัน

รูปที่ 3 กำลังเริ่ม AnimatedContent โดยไม่มีองค์ประกอบแบบใช้ร่วมกันในการเปลี่ยนภาพ

  1. หากต้องการให้องค์ประกอบที่แชร์เคลื่อนไหวระหว่างเลย์เอาต์ 2 แบบ ให้ล้อมรอบ Composable AnimatedContent ด้วย SharedTransitionLayout ขอบเขตจาก SharedTransitionLayout และ AnimatedContent จะส่งไปยัง MainContent และ DetailsContent ดังนี้

    var showDetails by remember {
        mutableStateOf(false)
    }
    SharedTransitionLayout {
        AnimatedContent(
            showDetails,
            label = "basic_transition"
        ) { targetState ->
            if (!targetState) {
                MainContent(
                    onShowDetails = {
                        showDetails = true
                    },
                    animatedVisibilityScope = this@AnimatedContent,
                    sharedTransitionScope = this@SharedTransitionLayout
                )
            } else {
                DetailsContent(
                    onBack = {
                        showDetails = false
                    },
                    animatedVisibilityScope = this@AnimatedContent,
                    sharedTransitionScope = this@SharedTransitionLayout
                )
            }
        }
    }

  2. เพิ่ม Modifier.sharedElement() ลงในเชนตัวปรับแต่ง Composable ใน Composable 2 รายการที่ตรงกัน สร้างออบเจ็กต์ SharedContentState และจดจำด้วย rememberSharedContentState() ออบเจ็กต์ SharedContentState จะจัดเก็บคีย์ที่ไม่ซ้ำกันซึ่งกำหนดองค์ประกอบที่แชร์ ระบุคีย์ที่ไม่ซ้ำกันเพื่อระบุเนื้อหา และใช้ rememberSharedContentState() สำหรับรายการที่ต้องการให้จดจำ ระบบจะส่ง AnimatedContentScope ไปยังตัวแก้ไข ซึ่งใช้เพื่อประสานงานภาพเคลื่อนไหว

    @Composable
    private fun MainContent(
        onShowDetails: () -> Unit,
        modifier: Modifier = Modifier,
        sharedTransitionScope: SharedTransitionScope,
        animatedVisibilityScope: AnimatedVisibilityScope
    ) {
        Row(
            // ...
        ) {
            with(sharedTransitionScope) {
                Image(
                    painter = painterResource(id = R.drawable.cupcake),
                    contentDescription = "Cupcake",
                    modifier = Modifier
                        .sharedElement(
                            rememberSharedContentState(key = "image"),
                            animatedVisibilityScope = animatedVisibilityScope
                        )
                        .size(100.dp)
                        .clip(CircleShape),
                    contentScale = ContentScale.Crop
                )
                // ...
            }
        }
    }
    
    @Composable
    private fun DetailsContent(
        modifier: Modifier = Modifier,
        onBack: () -> Unit,
        sharedTransitionScope: SharedTransitionScope,
        animatedVisibilityScope: AnimatedVisibilityScope
    ) {
        Column(
            // ...
        ) {
            with(sharedTransitionScope) {
                Image(
                    painter = painterResource(id = R.drawable.cupcake),
                    contentDescription = "Cupcake",
                    modifier = Modifier
                        .sharedElement(
                            rememberSharedContentState(key = "image"),
                            animatedVisibilityScope = animatedVisibilityScope
                        )
                        .size(200.dp)
                        .clip(CircleShape),
                    contentScale = ContentScale.Crop
                )
                // ...
            }
        }
    }

หากต้องการดูข้อมูลว่ามีการจับคู่องค์ประกอบที่แชร์เกิดขึ้นหรือไม่ ให้ดึงข้อมูล rememberSharedContentState() ลงในตัวแปร แล้วค้นหา isMatchFound

ซึ่งจะส่งผลให้เกิดภาพเคลื่อนไหวอัตโนมัติต่อไปนี้

รูปที่ 4 ตัวอย่างพื้นฐานของการเปลี่ยนองค์ประกอบที่ใช้ร่วมกันระหว่าง Composable 2 รายการ

คุณอาจสังเกตเห็นว่าสีพื้นหลังและขนาดของคอนเทนเนอร์ทั้งหมดยังคงใช้การตั้งค่าเริ่มต้นของ AnimatedContent

ขอบเขตที่ใช้ร่วมกันเทียบกับองค์ประกอบที่ใช้ร่วมกัน

Modifier.sharedBounds() คล้ายกับ Modifier.sharedElement() อย่างไรก็ตาม ตัวแก้ไขมีความแตกต่างกันดังนี้

  • sharedBounds() มีไว้สำหรับเนื้อหาที่แตกต่างกันในเชิงภาพ แต่ควรแชร์พื้นที่เดียวกันระหว่างสถานะ ในขณะที่ sharedElement() คาดหวังว่าเนื้อหาจะเหมือนกัน
  • เมื่อใช้ sharedBounds() เนื้อหาที่เข้าและออกจากหน้าจอจะมองเห็นได้ในระหว่างการเปลี่ยนสถานะระหว่าง 2 สถานะ ส่วนเมื่อใช้ sharedElement() ระบบจะแสดงผลเฉพาะเนื้อหาเป้าหมายในขอบเขตการเปลี่ยนรูปแบบ Modifier.sharedBounds() มีพารามิเตอร์ enter และ exit สำหรับระบุวิธีที่เนื้อหาควรเปลี่ยนผ่าน ซึ่งคล้ายกับวิธีการทำงานของ AnimatedContent
  • กรณีการใช้งานที่พบบ่อยที่สุดสำหรับ sharedBounds() คือรูปแบบการเปลี่ยนรูปแบบคอนเทนเนอร์ ส่วนกรณีการใช้งานตัวอย่างสำหรับ sharedElement() คือการเปลี่ยนภาพแบบฮีโร่
  • เมื่อใช้ Composable Text เราขอแนะนำให้ใช้ sharedBounds() เพื่อรองรับการเปลี่ยนแปลงแบบอักษร เช่น การเปลี่ยนระหว่างตัวเอียงและตัวหนา หรือการเปลี่ยนสี

จากตัวอย่างก่อนหน้านี้ การเพิ่ม Modifier.sharedBounds() ลงใน Row และ Column ใน 2 สถานการณ์ที่แตกต่างกันจะช่วยให้เราแชร์ขอบเขตของทั้ง 2 อย่างและแสดงภาพเคลื่อนไหวของการเปลี่ยนผ่านได้ ซึ่งจะช่วยให้ทั้ง 2 อย่างขยายขนาดระหว่างกันได้ ดังนี้

@Composable
private fun MainContent(
    onShowDetails: () -> Unit,
    modifier: Modifier = Modifier,
    sharedTransitionScope: SharedTransitionScope,
    animatedVisibilityScope: AnimatedVisibilityScope
) {
    with(sharedTransitionScope) {
        Row(
            modifier = Modifier
                .padding(8.dp)
                .sharedBounds(
                    rememberSharedContentState(key = "bounds"),
                    animatedVisibilityScope = animatedVisibilityScope,
                    enter = fadeIn(),
                    exit = fadeOut(),
                    resizeMode = SharedTransitionScope.ResizeMode.scaleToBounds()
                )
                // ...
        ) {
            // ...
        }
    }
}

@Composable
private fun DetailsContent(
    modifier: Modifier = Modifier,
    onBack: () -> Unit,
    sharedTransitionScope: SharedTransitionScope,
    animatedVisibilityScope: AnimatedVisibilityScope
) {
    with(sharedTransitionScope) {
        Column(
            modifier = Modifier
                .padding(top = 200.dp, start = 16.dp, end = 16.dp)
                .sharedBounds(
                    rememberSharedContentState(key = "bounds"),
                    animatedVisibilityScope = animatedVisibilityScope,
                    enter = fadeIn(),
                    exit = fadeOut(),
                    resizeMode = SharedTransitionScope.ResizeMode.scaleToBounds()
                )
                // ...

        ) {
            // ...
        }
    }
}

รูปที่ 5 ขอบเขตที่ใช้ร่วมกันระหว่าง Composable 2 รายการ

ทำความเข้าใจขอบเขต

หากต้องการใช้ Modifier.sharedElement() คอมโพสิเบิลต้องอยู่ใน SharedTransitionScope SharedTransitionLayout ที่ประกอบกันได้จะให้ SharedTransitionScope ตรวจสอบว่าได้วางไว้ที่จุดระดับบนสุดเดียวกันในลำดับชั้น UI ที่มีองค์ประกอบที่คุณต้องการแชร์

โดยทั่วไปแล้ว คุณควรวาง Composable ไว้ใน AnimatedVisibilityScope ด้วย โดยทั่วไปแล้วจะใช้ AnimatedContent เพื่อสลับระหว่าง Composable หรือเมื่อใช้ AnimatedVisibility โดยตรง หรือใช้ฟังก์ชันที่ประกอบกันได้ NavHost เว้นแต่คุณจะจัดการระดับการมองเห็นด้วยตนเอง หากต้องการใช้ขอบเขตหลายรายการ ให้บันทึกขอบเขตที่จำเป็นไว้ใน CompositionLocal, ใช้ Context Receiver ใน Kotlin หรือส่งขอบเขตเป็นพารามิเตอร์ไปยังฟังก์ชัน

ใช้ CompositionLocals ในสถานการณ์ที่คุณมีขอบเขตหลายรายการที่ต้องติดตาม หรือลำดับชั้นที่ซ้อนกันหลายระดับ CompositionLocalช่วยให้คุณเลือกขอบเขตที่ต้องการบันทึกและใช้งานได้ ในทางกลับกัน เมื่อคุณใช้ตัวรับบริบท เลย์เอาต์อื่นๆ ในลําดับชั้นอาจลบล้างขอบเขตที่ระบุโดยไม่ตั้งใจ เช่น หากคุณมี AnimatedContent ที่ซ้อนกันหลายรายการ ระบบอาจลบล้างขอบเขตได้

val LocalNavAnimatedVisibilityScope = compositionLocalOf<AnimatedVisibilityScope?> { null }
val LocalSharedTransitionScope = compositionLocalOf<SharedTransitionScope?> { null }

@Composable
private fun SharedElementScope_CompositionLocal() {
    // An example of how to use composition locals to pass around the shared transition scope, far down your UI tree.
    // ...
    SharedTransitionLayout {
        CompositionLocalProvider(
            LocalSharedTransitionScope provides this
        ) {
            // This could also be your top-level NavHost as this provides an AnimatedContentScope
            AnimatedContent(state, label = "Top level AnimatedContent") { targetState ->
                CompositionLocalProvider(LocalNavAnimatedVisibilityScope provides this) {
                    // Now we can access the scopes in any nested composables as follows:
                    val sharedTransitionScope = LocalSharedTransitionScope.current
                        ?: throw IllegalStateException("No SharedElementScope found")
                    val animatedVisibilityScope = LocalNavAnimatedVisibilityScope.current
                        ?: throw IllegalStateException("No AnimatedVisibility found")
                }
                // ...
            }
        }
    }
}

หรือหากลำดับชั้นของคุณไม่ได้ซ้อนกันลึก คุณสามารถส่งขอบเขตลงมาเป็นพารามิเตอร์ได้ ดังนี้

@Composable
fun MainContent(
    animatedVisibilityScope: AnimatedVisibilityScope,
    sharedTransitionScope: SharedTransitionScope
) {
}

@Composable
fun Details(
    animatedVisibilityScope: AnimatedVisibilityScope,
    sharedTransitionScope: SharedTransitionScope
) {
}

องค์ประกอบที่แชร์กับ AnimatedVisibility

ตัวอย่างก่อนหน้านี้แสดงวิธีใช้องค์ประกอบที่แชร์กับ AnimatedContent แต่องค์ประกอบที่แชร์ก็ใช้กับ AnimatedVisibility ได้เช่นกัน

ตัวอย่างเช่น ในตัวอย่างตารางกริดแบบ Lazy นี้ แต่ละองค์ประกอบจะรวมอยู่ใน AnimatedVisibility เมื่อคลิกรายการ เนื้อหาจะมีเอฟเฟกต์ภาพที่ดึงออกจาก UI ไปยังคอมโพเนนต์ที่คล้ายกับกล่องโต้ตอบ

var selectedSnack by remember { mutableStateOf<Snack?>(null) }

SharedTransitionLayout(modifier = Modifier.fillMaxSize()) {
    LazyColumn(
        // ...
    ) {
        items(listSnacks) { snack ->
            AnimatedVisibility(
                visible = snack != selectedSnack,
                enter = fadeIn() + scaleIn(),
                exit = fadeOut() + scaleOut(),
                modifier = Modifier.animateItem()
            ) {
                Box(
                    modifier = Modifier
                        .sharedBounds(
                            sharedContentState = rememberSharedContentState(key = "${snack.name}-bounds"),
                            // Using the scope provided by AnimatedVisibility
                            animatedVisibilityScope = this,
                            clipInOverlayDuringTransition = OverlayClip(shapeForSharedElement)
                        )
                        .background(Color.White, shapeForSharedElement)
                        .clip(shapeForSharedElement)
                ) {
                    SnackContents(
                        snack = snack,
                        modifier = Modifier.sharedElement(
                            sharedContentState = rememberSharedContentState(key = snack.name),
                            animatedVisibilityScope = this@AnimatedVisibility
                        ),
                        onClick = {
                            selectedSnack = snack
                        }
                    )
                }
            }
        }
    }
    // Contains matching AnimatedContent with sharedBounds modifiers.
    SnackEditDetails(
        snack = selectedSnack,
        onConfirmClick = {
            selectedSnack = null
        }
    )
}

รูปที่ 6 องค์ประกอบที่แชร์กับ AnimatedVisibility

การสั่งซื้อตัวขยาย

เมื่อใช้ Modifier.sharedElement() และ Modifier.sharedBounds() ลำดับของเชนตัวแก้ไขจะมีความสำคัญ เช่นเดียวกับส่วนที่เหลือของ Compose การวางตัวแก้ไขที่ส่งผลต่อขนาดอย่างไม่ถูกต้องอาจทำให้เกิดการกระโดดของภาพที่ไม่คาดคิดระหว่างการจับคู่องค์ประกอบที่แชร์

เช่น หากคุณวางตัวแก้ไขระยะห่างจากขอบในตำแหน่งที่ต่างกันในองค์ประกอบที่ใช้ร่วมกัน 2 รายการ ภาพเคลื่อนไหวจะมีความแตกต่างกันในด้านภาพ

var selectFirst by remember { mutableStateOf(true) }
val key = remember { Any() }
SharedTransitionLayout(
    Modifier
        .fillMaxSize()
        .padding(10.dp)
        .clickable {
            selectFirst = !selectFirst
        }
) {
    AnimatedContent(targetState = selectFirst, label = "AnimatedContent") { targetState ->
        if (targetState) {
            Box(
                Modifier
                    .padding(12.dp)
                    .sharedBounds(
                        rememberSharedContentState(key = key),
                        animatedVisibilityScope = this@AnimatedContent
                    )
                    .border(2.dp, Color.Red)
            ) {
                Text(
                    "Hello",
                    fontSize = 20.sp
                )
            }
        } else {
            Box(
                Modifier
                    .offset(180.dp, 180.dp)
                    .sharedBounds(
                        rememberSharedContentState(
                            key = key,
                        ),
                        animatedVisibilityScope = this@AnimatedContent
                    )
                    .border(2.dp, Color.Red)
                    // This padding is placed after sharedBounds, but it doesn't match the
                    // other shared elements modifier order, resulting in visual jumps
                    .padding(12.dp)

            ) {
                Text(
                    "Hello",
                    fontSize = 36.sp
                )
            }
        }
    }
}

ขอบเขตที่ตรงกัน

ขอบเขตที่ไม่ตรงกัน: สังเกตว่าภาพเคลื่อนไหวขององค์ประกอบที่แชร์ดูผิดปกติเล็กน้อยเนื่องจากต้องปรับขนาดให้เป็นขอบเขตที่ไม่ถูกต้อง

ตัวแก้ไขที่ใช้ก่อนตัวแก้ไของค์ประกอบที่แชร์จะให้ข้อจำกัดแก่ตัวแก้ไของค์ประกอบที่แชร์ ซึ่งจะใช้เพื่อรับขอบเขตเริ่มต้นและขอบเขตเป้าหมาย และต่อมาคือภาพเคลื่อนไหวของขอบเขต

ตัวแก้ไขที่ใช้หลังจากตัวแก้ไของค์ประกอบที่ใช้ร่วมกันจะใช้ข้อจำกัดจากก่อนหน้านี้เพื่อวัดและคำนวณขนาดเป้าหมายขององค์ประกอบย่อย ตัวแก้ไของค์ประกอบที่ใช้ร่วมกันจะสร้างชุดข้อจำกัดแบบเคลื่อนไหวเพื่อค่อยๆ เปลี่ยนรูปแบบองค์ประกอบย่อยจากขนาดเริ่มต้นเป็นขนาดเป้าหมาย

ข้อยกเว้นคือหากคุณใช้ resizeMode = ScaleToBounds() สำหรับภาพเคลื่อนไหว หรือ Modifier.skipToLookaheadSize() ใน Composable ในกรณีนี้ Compose จะจัดวางเลย์เอาต์ของคอมโพสิเบิลย่อยโดยใช้ข้อจำกัดเป้าหมาย และใช้ค่าตัวคูณมาตราส่วนเพื่อแสดงภาพเคลื่อนไหวแทนการเปลี่ยนขนาดเลย์เอาต์เอง

คีย์ที่ไม่ซ้ำกัน

เมื่อทำงานกับองค์ประกอบที่ใช้ร่วมกันที่ซับซ้อน แนวทางปฏิบัติแนะนำคือสร้างคีย์ที่ไม่ใช่สตริง เนื่องจากสตริงอาจทำให้เกิดข้อผิดพลาดในการจับคู่ได้ คีย์แต่ละรายการต้องไม่ซ้ำกันเพื่อให้เกิดการจับคู่ ตัวอย่างเช่น ใน Jetsnack เรามีองค์ประกอบที่ใช้ร่วมกันต่อไปนี้

รูปที่ 7 รูปภาพแสดง Jetsnack พร้อมคำอธิบายประกอบสำหรับแต่ละส่วนของ UI

คุณสามารถสร้าง enum เพื่อแสดงประเภทองค์ประกอบที่แชร์ได้ ในตัวอย่างนี้ การ์ดของว่างทั้งหมดอาจปรากฏจากหลายตำแหน่งบนหน้าจอหลัก เช่น ในส่วน "ยอดนิยม" และ "แนะนำ" คุณสามารถสร้างคีย์ที่มี snackId, origin ("ยอดนิยม" / "แนะนำ") และ type ขององค์ประกอบที่แชร์ซึ่งจะแชร์ได้ดังนี้

data class SnackSharedElementKey(
    val snackId: Long,
    val origin: String,
    val type: SnackSharedElementType
)

enum class SnackSharedElementType {
    Bounds,
    Image,
    Title,
    Tagline,
    Background
}

@Composable
fun SharedElementUniqueKey() {
    // ...
            Box(
                modifier = Modifier
                    .sharedElement(
                        rememberSharedContentState(
                            key = SnackSharedElementKey(
                                snackId = 1,
                                origin = "latest",
                                type = SnackSharedElementType.Image
                            )
                        ),
                        animatedVisibilityScope = this@AnimatedVisibility
                    )
            )
            // ...
}

เราขอแนะนำให้ใช้คลาสข้อมูลสำหรับคีย์ เนื่องจากคลาสข้อมูลจะใช้ hashCode() และ equals()

จัดการระดับการมองเห็นองค์ประกอบที่แชร์ด้วยตนเอง

ในกรณีที่คุณอาจไม่ได้ใช้ AnimatedVisibility หรือ AnimatedContent คุณสามารถจัดการระดับการเข้าถึงองค์ประกอบที่แชร์ได้ด้วยตนเอง ใช้ Modifier.sharedElementWithCallerManagedVisibility() และระบุเงื่อนไขของคุณเองซึ่งกำหนดว่าเมื่อใดที่ควรแสดงหรือไม่แสดงรายการ

var selectFirst by remember { mutableStateOf(true) }
val key = remember { Any() }
SharedTransitionLayout(
    Modifier
        .fillMaxSize()
        .padding(10.dp)
        .clickable {
            selectFirst = !selectFirst
        }
) {
    Box(
        Modifier
            .sharedElementWithCallerManagedVisibility(
                rememberSharedContentState(key = key),
                !selectFirst
            )
            .background(Color.Red)
            .size(100.dp)
    ) {
        Text(if (!selectFirst) "false" else "true", color = Color.White)
    }
    Box(
        Modifier
            .offset(180.dp, 180.dp)
            .sharedElementWithCallerManagedVisibility(
                rememberSharedContentState(
                    key = key,
                ),
                selectFirst
            )
            .alpha(0.5f)
            .background(Color.Blue)
            .size(180.dp)
    ) {
        Text(if (selectFirst) "false" else "true", color = Color.White)
    }
}

ข้อจำกัดในปัจจุบัน

API เหล่านี้มีข้อจำกัดบางประการ โดยเฉพาะอย่างยิ่งในเรื่องต่อไปนี้

  • ไม่รองรับความสามารถในการทำงานร่วมกันระหว่าง Views และ Compose ซึ่งรวมถึง Composable ใดก็ตามที่รวม AndroidView เช่น Dialog หรือ ModalBottomSheet
  • ระบบไม่รองรับภาพเคลื่อนไหวอัตโนมัติสำหรับรายการต่อไปนี้
    • รูปภาพที่แชร์ที่ประกอบกันได้:
      • ContentScale จะไม่เคลื่อนไหวโดยค่าเริ่มต้น โดยจะสแนปไปยังจุดสิ้นสุดที่ตั้งไว้ ContentScale
    • การครอบตัดรูปร่าง - ไม่มีการรองรับในตัวสำหรับการเคลื่อนไหวอัตโนมัติระหว่างรูปร่าง เช่น การเคลื่อนไหวจากสี่เหลี่ยมจัตุรัสเป็นวงกลมเมื่อรายการเปลี่ยนผ่าน
    • สำหรับกรณีที่ไม่รองรับ ให้ใช้ Modifier.sharedBounds() แทน sharedElement() และเพิ่ม Modifier.animateEnterExit() ลงในรายการ