공유 요소의 전환 맞춤설정

공유 요소 전환 애니메이션의 실행 방식을 맞춤설정하기 위해 공유 요소의 전환 방식을 변경하는 데 사용할 수 있는 몇 가지 매개변수가 있습니다.

애니메이션 사양

크기 및 위치 이동에 사용되는 애니메이션 사양을 변경하려면 Modifier.sharedElement()에 다른 boundsTransform 매개변수를 지정하면 됩니다. 이렇게 하면 초기 Rect 위치와 타겟 Rect 위치가 제공됩니다.

예를 들어 이전 예의 텍스트가 원호 모션을 따라 움직이게 하려면 boundsTransform 매개변수를 지정하여 keyframes 사양을 사용합니다.

val textBoundsTransform = BoundsTransform { initialBounds, targetBounds ->
    keyframes {
        durationMillis = boundsAnimationDurationMillis
        initialBounds at 0 using ArcMode.ArcBelow using FastOutSlowInEasing
        targetBounds at boundsAnimationDurationMillis
    }
}
Text(
    "Cupcake", fontSize = 28.sp,
    modifier = Modifier.sharedBounds(
        rememberSharedContentState(key = "title"),
        animatedVisibilityScope = animatedVisibilityScope,
        boundsTransform = textBoundsTransform
    )
)

모든 AnimationSpec를 사용할 수 있습니다. 이 예에서는 keyframes 사양을 사용합니다.

그림 1. 다양한 boundsTransform 매개변수를 보여주는 예

크기 조절 모드

두 공유 경계 간에 애니메이션을 적용할 때 resizeMode 매개변수를 RemeasureToBounds 또는 ScaleToBounds로 설정할 수 있습니다. 이 매개변수는 공유 요소가 두 상태 간에 전환되는 방식을 결정합니다. ScaleToBounds는 먼저 전방 탐색 (또는 타겟) 제약 조건으로 하위 레이아웃을 측정합니다. 그러면 하위 요소의 안정적인 레이아웃이 공유된 경계에 맞게 조정됩니다. ScaleToBounds는 상태 간의 '그래픽 배율'로 생각할 수 있습니다.

반면 RemeasureToBounds는 타겟 크기에 따라 애니메이션된 고정 제약 조건을 사용하여 sharedBounds의 하위 레이아웃을 다시 측정하고 다시 배치합니다. 재측정은 모든 프레임이 될 수 있는 경계 크기 변경으로 인해 트리거됩니다.

Text 컴포저블의 경우 텍스트를 다른 줄로 재배치하고 리플로우하는 것을 방지하므로 ScaleToBounds를 사용하는 것이 좋습니다. 가로세로 비율이 다른 경계와 두 공유 요소 간에 유동적인 연속성을 원한다면 RemeasureToBounds를 사용하는 것이 좋습니다.

두 크기 조절 모드의 차이점은 다음 예에서 볼 수 있습니다.

ScaleToBounds

RemeasureToBounds

최종 레이아웃으로 건너뛰기

기본적으로 두 레이아웃 간에 전환할 때 레이아웃 크기는 시작 상태와 최종 상태 사이에 애니메이션 처리됩니다. 이는 텍스트와 같은 콘텐츠를 애니메이션화할 때 바람직하지 않은 동작일 수 있습니다.

다음 예에서는 두 가지 방식으로 화면에 진입하는 설명 텍스트 'Lorem Ipsum'을 보여줍니다. 첫 번째 예에서는 컨테이너의 크기가 커짐에 따라 텍스트가 리플로우되고 두 번째 예에서는 텍스트가 커져도 리플로우하지 않습니다. Modifier.skipToLookaheadSize()를 추가하면 리플로우가 커질 때 리플로우가 방지됩니다.

Modifier.skipToLookahead() 없음 - 'Lorem Ipsum' 텍스트 리플로우 확인

Modifier.skipToLookahead() - 'Lorem Ipsum' 텍스트가 애니메이션 시작 시 최종 상태를 유지합니다.

클립 및 오버레이

Compose에서 공유 요소를 만들 때 중요한 개념은 여러 컴포저블 간에 공유할 수 있도록 전환이 대상에서 일치하기 시작하면 컴포저블의 렌더링이 레이어 오버레이로 승격된다는 것입니다. 결과적으로 상위 요소의 경계와 레이어 변환 (예: 알파 및 배율)을 이스케이프합니다.

공유되지 않은 다른 UI 요소 위에 렌더링되며 전환이 완료되면 요소가 오버레이에서 자체 DrawScope로 삭제됩니다.

공유 요소를 도형으로 자르려면 표준 Modifier.clip() 함수를 사용합니다. sharedElement() 뒤에 배치합니다.

Image(
    painter = painterResource(id = R.drawable.cupcake),
    contentDescription = "Cupcake",
    modifier = Modifier
        .size(100.dp)
        .sharedElement(
            rememberSharedContentState(key = "image"),
            animatedVisibilityScope = this@AnimatedContent
        )
        .clip(RoundedCornerShape(16.dp)),
    contentScale = ContentScale.Crop
)

공유 요소가 상위 컨테이너 외부에서 렌더링되지 않도록 하려면 sharedElement()에서 clipInOverlayDuringTransition를 설정하면 됩니다. 기본적으로 중첩된 공유 경계의 경우 clipInOverlayDuringTransition는 상위 sharedBounds()의 클립 경로를 사용합니다.

공유 요소 전환 중에 하단 표시줄이나 플로팅 작업 버튼과 같은 특정 UI 요소를 항상 맨 위에 유지할 수 있도록 Modifier.renderInSharedTransitionScopeOverlay()를 사용합니다. 기본적으로 이 수정자는 공유된 전환이 활성화되는 동안 오버레이에 콘텐츠를 유지합니다.

예를 들어 Jetsnack에서 BottomAppBar는 화면이 표시되지 않을 때까지 공유 요소 위에 배치해야 합니다. 컴포저블에 수정자를 추가하면 승격된 상태가 유지됩니다.

Modifier.renderInSharedTransitionScopeOverlay() 사용 안 함

다음으로 서명됨: Modifier.renderInSharedTransitionScopeOverlay()

때로는 공유되지 않은 컴포저블이 애니메이션 처리되면서 전환 전에 다른 컴포저블 위에 유지되기를 원할 수 있습니다. 이러한 경우 renderInSharedTransitionScopeOverlay().animateEnterExit()를 사용하여 공유 요소 전환이 실행될 때 컴포저블에 애니메이션을 적용합니다.

JetsnackBottomBar(
    modifier = Modifier
        .renderInSharedTransitionScopeOverlay(
            zIndexInOverlay = 1f,
        )
        .animateEnterExit(
            enter = fadeIn() + slideInVertically {
                it
            },
            exit = fadeOut() + slideOutVertically {
                it
            }
        )
)

그림 2.애니메이션이 전환될 때 슬라이딩되는 하단 앱 바

드물지만 공유 요소가 오버레이에서 렌더링되지 않도록 하려면 sharedElement()renderInOverlayDuringTransition를 false로 설정하면 됩니다.

동위 레이아웃에 공유 요소 크기 변경 알림

기본적으로 sharedBounds()sharedElement()는 레이아웃이 전환될 때 상위 컨테이너에 크기 변경을 알리지 않습니다.

전환 시 상위 컨테이너에 크기 변경을 전파하려면 placeHolderSize 매개변수를 PlaceHolderSize.animatedSize로 변경합니다. 이렇게 하면 항목이 확장되거나 축소됩니다. 레이아웃의 다른 모든 항목은 변경사항에 응답합니다.

PlaceholderSize.contentSize(기본값)

PlaceholderSize.animatedSize

(한 항목이 커지면 목록의 다른 항목이 아래로 이동하는 것을 확인하세요.)