출처: 안드로이드 공식 문서 (https://developer.android.com/guide/fragments/animate)
Transition Animation은 기존에 떠있는 뷰가 다음 뷰의 위치와 연결되면서 움직이는 듯한 효과 를 얘기합니다. 마치 재활용 되는 듯한 느낌을 낼 수 있고, 보다 자연스럽게 화면 전환이 이루어지는 효과를 낼 수 있습니다.
이번 글에서는 Fragment
에서 RecyclerView
아이템을 클릭했을 때 Transition Animation
을 적용할 수 있는 방법에 대해 얘기할 것입니다.
https://developer.android.com/guide/navigation/navigation-animate-transitions
위 링크에서 프래그먼트 대상으로 공유 요소 전환
이라는 섹션의 가이드를 따라해보면 이 방법이 RecyclerView
에서 Fragment
로의 작동되지 않는다는 것을 알 수 있습니다. (몸으로 꺠달았습니다)
이유는 다음과 같습니다
RecyclerView의 각각의 item에 동일한 TransitionName을 적용시켰기 떄문에 어디서 어떻게 움직여야 하는지 알 수가 없게 되어 작동하지 않게 됩니다.
그래서 유니크한 이름을 정할 수 있도록 작업을 진행하고, 이를 받을 수 있도록 작업한다면 Transition Animation을 만들 수 있습니다.
가장 먼저 유니크하게 값을 줄 수 있도록 xml resource를 선언해줍니다.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="characterImageTransition">characterImage %s</string>
<string name="characterNameTransition">characterName %s</string>
</resources>
이후 기본적으로 DataBinding
을 사용하고 있다는 가정하에, RecyclerView
의 각각의 아이템의 고유한 값을 적용시켜 StringValue
를 완성시키면 됩니다.
<data>
<variable
name="character"
type="com.ssong_develop.core_model.Characters" />
</data>
.... 중략 ....
android:transitionName="@{@string/characterImageTransition(String.valueOf(character.id))}"
만약 앱이 API 14이하를 제공한다면, xml attribute
가 아닌 직접 메서드를 호출하여 setTransitionName(name: String)
적용시킵니다.
그 다음 Transition
의 효과를 선언하기 위한 xml file을 하나 더 생성합니다
<?xml version="1.0" encoding="utf-8"?>
<transitionSet>
<changeBounds />
<changeTransform />
<changeClipBounds />
<changeImageTransform />
</transitionSet>
changeBounds : 장면의 변경 전후에 대상 뷰의 경계를 캡처, 전환 중에 해당 내용을 애니메이션으로 만듬
changeTransform : 뷰의 스케일(scale) 또는 회전을 캡처하고 전환 중 해당 내용을 애니메이션으로 만듬
changeClipBounds : 변경 전후 getClipBounds() 를 캡처 및 전환 중 해당 내용을 애니메이션으로 만듬
changeImageTransform : ImageView의 매트릭스를 캡처 및 전환 중 해당 내용을 애니메이션으로 만듬
그외에도 여러 attribute
가 존재하지만 검색하시면서 사용하시길 바랍니다
여기까지 됐다면 이젠 연결만 남았습니다.
기존의 Adapter와 click을 연결한 interface
혹은 lamda
를 수정해 애니메이션을 적용하고자 하는 view
와 TransitionName
을 인자로 넘겨줍니다
/** click interface **/
interface ItemClickDelegate {
fun onItemClick(
characterImageView: View,
characterNameView: View,
characterImageTransitionName: String,
characterNameTransitionName: String,
characters: Characters
)
}
/** viewHolder **/
override fun onClick(view: View) {
itemClickDelegate.onItemClick(
characterImageView = binding.ivCharacterImage,
characterNameView = binding.tvCharacterName,
characterImageTransitionName = binding.ivCharacterImage.transitionName,
characterNameTransitionName = binding.tvCharacterName.transitionName,
characters = characters
)
}
각각의 리사이클러뷰 구현이 다를테니 부분만 캡쳐해서 확인하였습니다.
이후엔 공식문서에 나와있는 대로 RecyclerView
를 구현하고 있는 fragment
에서 메서드를 호출하고FragmentNavigatorExtras
를 적용해주면 끝이 납니다.
/** fragment **/
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
postponeEnterTransition()
(binding.root.parent as? ViewGroup)?.doOnPreDraw {
startPostponedEnterTransition()
}
... 중략 ...
}
override fun onItemClick(
characterImageView: View,
characterNameView: View,
characterImageTransitionName: String,
characterNameTransitionName: String,
characters: Characters
) {
val bundle = Bundle()
bundle.putParcelable(CHARACTER_KEY, characters)
val extras = FragmentNavigatorExtras(
characterImageView to characterImageTransitionName,
characterNameView to characterNameTransitionName
)
findNavController().navigate(
R.id.action_characterFragment_to_characterDetailFragment,
args = bundle,
navOptions = null,
navigatorExtras = extras
)
}
이와 같은 방법을 통해서 RecyclerView와 Fragment간의 Transition Animation 구현을 만들었습니다.