안드로이드 코틀린 에니메이션 화면전환 효과 소스입니다. 리사이클뷰를 이용하여 카드뷰를 클릭 했을 경우 전환되는 화면에 에니메이션을 적용한 소스입니다.
카드뷰로 리스트 목록을 보여주며 클릭시 상세 화면으로 넘어가는 액티비티 입니다.
이미지는 패이드 효과를 주며 제목과 내용은 슬라이드로 위로 올라오는 화면전환 에니메이션입니다.
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-parcelize'
}
dependencies {
implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation "com.github.bumptech.glide:glide:4.11.0"
implementation "androidx.recyclerview:recyclerview:1.3.0"
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
데이터를 직렬화 하기 위해서 플러그인에 parcelize 플러그인을 추가합니다.
internal class SingleTypeDiffCallback(
private val viewBinder: ItemBinder
) : DiffUtil.ItemCallback<Any>() {
override fun areItemsTheSame(oldItem: Any, newItem: Any): Boolean {
if(oldItem::class != newItem::class) {
return false
}
return viewBinder?.areItemsTheSame(oldItem, newItem) ?: false
}
override fun areContentsTheSame(oldItem: Any, newItem:Any): Boolean {
return viewBinder?.areContentsTheSame(oldItem, newItem) ?: false
}
}
itemDiffCallback.kt 리사이클 데이터를 대조하여 클릭했을 경우 콜백하는 클래스입니다.
class SingleViewBinderListAdapter(
private val viewBinder: ItemBinder,
stateRestorationPolicy: StateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY,
private val recycleChildrenOnDetach: Boolean = false
) : ListAdapter<Any, ViewHolder>(SingleTypeDiffCallback(viewBinder)) {
init {
this.stateRestorationPolicy = stateRestorationPolicy
}
override fun getItemViewType(position: Int): Int =
viewBinder.getItemLayoutResource()
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
if (recycleChildrenOnDetach) {
val layoutManager = recyclerView.layoutManager
(layoutManager as? LinearLayoutManager)?.recycleChildrenOnDetach = true
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
// println("🔥 SingleViewBinderAdapter onCreateViewHolder() viewType: $viewType")
return viewBinder.createViewHolder(parent)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// println("🤔 SingleViewBinderAdapter onBindViewHolder() position: $position, holder: $holder")
viewBinder.bindViewHolder(currentList[position], holder)
}
override fun onViewRecycled(holder: ViewHolder) {
// println("👻 SingleViewBinderAdapter onViewRecycled() holder: $holder")
viewBinder.onViewRecycled(holder)
super.onViewRecycled(holder)
}
override fun onViewDetachedFromWindow(holder: ViewHolder) {
// println("💀 SingleViewBinderAdapter onViewDetachedFromWindow() holder $holder")
viewBinder.onViewDetachedFromWindow(holder)
super.onViewDetachedFromWindow(holder)
}
}
SingleViewBinderListAdapter.kt 리사이클뷰 어댑터로 리소스 파일에서 itemBinder 를 가져와 묶는 작업입니다.
typealias를 사용 하여 알리아스를 등록해서 추상 클래스를 사용했습니다.
메인 액티비티에서 서브 액티비티로 화면전환 할때 사용하는 서브 액티비티 입니다.
class Activity1_2Details : AppCompatActivity() {
private val TAG = this.javaClass.simpleName
private lateinit var binding: Activity12detailsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = Activity12detailsBinding.inflate(layoutInflater)
setContentView(binding.root)
title = "카드뷰 변형 상세보기"
postponeEnterTransition()
val postCardModel = intent.extras?.getParcelable<PostCardModel>(KEY_POST_MODEL)
postCardModel?.let {
binding.ivPhoto.setImageResource(it.drawablesRes)
binding.tvTitle.text = it.post.title
binding.tvBody.text = it.post.body
}
setUpTransitions()
}
private fun setUpTransitions() {
val transitions = TransitionSet()
val transitionSetIvArcMove = TransitionInflater.from(this).
inflateTransition(R.transition.activity2_detail_transition)
transitionSetIvArcMove.interpolator = AccelerateDecelerateInterpolator()
// Slide Transition with delay for texts
val slide = createSlideTransition()
transitions.addTransition(slide)
// Fade Transition
val fade: Transition = createFadeTransition()
transitions.addTransition(fade)
// Set Window transition for Shared transitions
window.enterTransition = transitions
// 🔥 Should be sharedElementEnterTransition NOT enterTransition
window.sharedElementEnterTransition = transitionSetIvArcMove
// Start postponed transition
startPostponedEnterTransition()
}
private fun createSlideTransition() : Transition {
val slide = Slide(Gravity.BOTTOM).apply {
interpolator = AnimationUtils.loadInterpolator(
this@Activity1_2Details,
android.R.interpolator.linear_out_slow_in
)
startDelay = 200
duration = 500
addTarget(binding.tvTitle)
addTarget(binding.tvBody)
}
// Add color change to Title after Slide Transition is complete
slide.addListener(object : Transition.TransitionListener {
override fun onTransitionStart(transition: Transition?) = Unit
override fun onTransitionEnd(transition: Transition?) {
val colorFrom = binding.tvTitle.currentTextColor
val colorTo = Color.parseColor("#FF8F00")
val colorAnimation: ValueAnimator =
ValueAnimator.ofObject(ArgbEvaluator(), colorFrom, colorTo)
colorAnimation.addUpdateListener { animator ->
binding.tvTitle.setTextColor(animator.animatedValue as Int)
}
colorAnimation.duration = 200
colorAnimation.start()
}
override fun onTransitionCancel(transition: Transition?) = Unit
override fun onTransitionPause(transition: Transition?) = Unit
override fun onTransitionResume(transition: Transition?) = Unit
})
return slide
}
private fun createFadeTransition() : Transition {
val fade: Transition = Fade()
val decor = window.decorView
val view = decor.findViewById<View>(androidx.appcompat.R.id.action_bar_container)
fade.excludeTarget(view, true)
fade.excludeTarget(android.R.id.statusBarBackground, true)
fade.excludeTarget(android.R.id.navigationBarBackground, true)
return fade
}
}
상단이미지는 패이드 효과를 주고 제목 부분은 색상변경과 컨텐츠 부분은 슬라이드 효과를 주는 소스입니다.
class MainActivity : AppCompatActivity() {
private val TAG = this.javaClass.simpleName
private lateinit var listAdapter : SingleViewBinderListAdapter
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
title = "Ch1-2 리사이클뷰 에니메이션 변형"
val postCardViewBinder = PostCardViewBinder {
binding, postCardModel ->
gotoDetailWithTransition(postCardModel, binding)
}
listAdapter = SingleViewBinderListAdapter(postCardViewBinder as ItemBinder)
binding.recyclerView.apply {
this.adapter = listAdapter
layoutManager = LinearLayoutManager(this@MainActivity)
}
listAdapter.submitList(generateMockPosts())
//setContentView(R.layout.activity_main)
}
/*
액티비티 에니메이션 변형 바로가기 목록 함수
*/
private fun gotoDetailWithTransition(
postCardModel: PostCardModel,
binding: ItemPostBinding
) {
val intent = Intent(this@MainActivity, Activity1_2Details::class.java)
intent.putExtra(KEY_POST_MODEL, postCardModel)
// create the transition animation using image, title and body
val pairIvAvatar = Pair<View, String>(binding.ivPhoto, binding.ivPhoto.transitionName)
val pairTvTitle = Pair<View, String>(binding.tvTitle, binding.tvTitle.transitionName)
val pairTvBody = Pair<View, String>(binding.tvBody, binding.tvBody.transitionName)
val options = ActivityOptions
.makeSceneTransitionAnimation(
this,
// pairTvTitle,
pairIvAvatar
)
// start the new activity
startActivity(intent, options.toBundle())
}
private fun generateMockPosts() : List<PostCardModel> {
val postList = ArrayList<PostCardModel>()
val random = Random()
repeat(30) {
val randomNum = random.nextInt(5)
val title = "제목 $randomNum"
val postBody = getString(R.string.bacon_ipsum)
val post = Post(it, it, title, postBody)
postList.add(PostCardModel(post, getDrawableRes(randomNum)))
}
return postList
}
private fun getDrawableRes(userId: Int): Int {
return when {
userId % 6 == 0 -> {
R.drawable.avatar_1_raster
}
userId % 6 == 1 -> {
R.drawable.avatar_2_raster
}
userId % 6 == 2 -> {
R.drawable.avatar_3_raster
}
userId % 6 == 3 -> {
R.drawable.avatar_4_raster
}
userId % 6 == 4 -> {
R.drawable.avatar_5_raster
}
else -> {
R.drawable.avatar_6_raster
}
}
}
}
메인 액티비티로 리사이클뷰를 사용하여 리스트를 뿌려줍니다.
[소스다운받기]
[함께보면 좋은 정보]
안드로이드 코틀린 에니메이션 기초 Ch0-2 (0) | 2023.06.08 |
---|---|
안드로이드 코틀린 에니메이션 기초 Ch0-1 (1) | 2023.06.06 |
안드로이드 스튜디오 마이그레이트 에러 (0) | 2023.06.02 |
안드로이드 코틀린 에니메이션 화면전환 효과 Ch1-1 (0) | 2023.05.28 |
안드로이드 코틀린 에니메이션 효과 넣기 Ch1-0 (0) | 2023.05.20 |