TIL) 230331

Hanseul Lee·2023년 3월 31일
0

TIL

목록 보기
17/23

RecyclerView 아이템이 추가될 때 Animation 넣기

조건은 다음과 같다.

  1. 채팅 기능을 recyclerView로 구현하고 있기 때문에 특정 type에만 애니메이션을 넣으려고 한다.
  2. 아이템이 추가될 때 해당 아이템에만 애니메이션이 적용되어야 한다.

코드로 살펴보자.

  • xml
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@android:anim/bounce_interpolator">
        <scale
            android:duration="500"
            android:fromXScale="0"
            android:fromYScale="0"
            android:pivotX="50%"
            android:pivotY="50%"
            android:toXScale="1.0"
            android:toYScale="1.0" />
    </set>
  • adapter
    class MessageAdapter(val context: Context) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    
        private var messageList = listOf<Message>()
    
        override fun onCreateViewHolder(
            parent: ViewGroup,
            viewType: Int
        ): RecyclerView.ViewHolder {
    
            return when (viewType) {
                SEND_BY_USER -> {
                    val view =
                        ItemChatUserBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                    UserViewHolder(view)
                }
                SEND_BY_BOT -> {
                    val view =
                        ItemChatBotBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                    BotViewHolder(view)
                }
                else -> {
                    val view =
                        ItemChatLineBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                    LineViewHolder(view)
                }
            }
        }
    
        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
            val curMsg = messageList[position]
    
            when (curMsg.sendBy) {
                SEND_BY_USER -> {
                    (holder as UserViewHolder).bind(curMsg)
                }
                SEND_BY_BOT -> {
                    (holder as BotViewHolder).bind(curMsg)
                }
                SEND_BY_LINE -> {
                    (holder as LineViewHolder).bind(curMsg)
                }
            }
        }
    
        override fun getItemCount(): Int {
            return messageList.size
        }
    
        override fun getItemViewType(position: Int): Int {
            return messageList[position].sendBy
        }
    
        fun setMessageList(newList: List<Message>) {
            messageList = newList
            notifyDataSetChanged()
        }
    
        inner class UserViewHolder(binding: ItemChatUserBinding) :
            RecyclerView.ViewHolder(binding.root) {
            var userTxt = binding.itemMsgUserTv
    
            fun bind(item: Message) {
                userTxt.text = item.message
            }
        }
    
        inner class BotViewHolder(binding: ItemChatBotBinding) : RecyclerView.ViewHolder(binding.root) {
            var chatTxt = binding.itemMsgBotTv
    
            init {
                val bounceAnim = AnimationUtils.loadAnimation(context, R.anim.bounce)
                chatTxt.startAnimation(bounceAnim)
            }
    
            fun bind(item: Message) {
                chatTxt.text = item.message
            }
        }
    
        inner class LineViewHolder(binding: ItemChatLineBinding) :
            RecyclerView.ViewHolder(binding.root) {
            var versionTxt = binding.itemMsgVersionTv
    
            fun bind(item: Message) {
                versionTxt.text = item.message
            }
        }
    
    }

전체 코드를 살펴보면 좋을 것 같아서 전부 넣었다. 이제 요구 조건들을 해결하고 있는 블록을 떠와서 자세히보자.

우선 특정 type에만 애니메이션이 넣어져야 한다. 챗봇을 구현하는 recyclerview에 그려지는 type은 총 세 개로, BOT, USER, LINE인데 봇의 채팅이 출력되는 부분에만 애니메이션을 넣고 싶었다. 그래서 해당 부분의 ViewHolder에만 추가했다.

inner class BotViewHolder(binding: ItemChatBotBinding) : RecyclerView.ViewHolder(binding.root) {
        var chatTxt = binding.itemMsgBotTv

        init {
            val bounceAnim = AnimationUtils.loadAnimation(context, R.anim.bounce)
            chatTxt.startAnimation(bounceAnim)
        }

        fun bind(item: Message) {
            chatTxt.text = item.message
        }
    }

애니메이션 관련 코드는 init에 정의해야 한다. 그렇지 않고 bind 메서드에서 해결하게 되면 아이템이 추가될 때마다 해당 아이템에만 애니메이션이 적용되는 게 아니라, 전체 아이템의 모든 BOT type 아이템에 애니메이션이 적용되기 때문이다.

ViewFlipper로 채팅 입력 중인 애니메이션 만들기

https://s3.amazonaws.com/www-inside-design/uploads/2019/01/ezgif.com-resize-3.gif

ViewFlipper를 활용하면 위와 같은 애니메이션을 넣을 수 있다.

ViewFlipper는 여러 개의 자식 뷰를 가지고 있으면서 하나의 자식 뷰만 보여주는 컨테이너다. 자식 뷰들 중 하나를 선택하여 다른 자식 뷰로 전환할 때, 전환 애니메이션을 적용할 수 있다. 기본적으로 자동으로 전환되는 기능이 있다. 그래서 다음 자식 뷰로 전환할 때는 설정된 전환 애니메이션을 실행하고, 자동으로 일정 시간이 지나면 다음 자식 뷰로 자동으로 전환된다.

우선 기존 상대 채팅 xml에 다음 코드를 추가한다.

<ViewFlipper
            android:id="@+id/typing_indicator"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:inAnimation="@anim/fade_in"
            android:outAnimation="@anim/fade_out"
            android:padding="16dp">

            <View
                android:id="@+id/temp1"
                android:layout_width="10dp"
                android:layout_height="10dp"
                android:background="@drawable/baseline_circle_24" />

            <View
                android:id="@+id/temp2"
                android:layout_width="10dp"
                android:layout_height="10dp"
                android:layout_marginStart="13dp"
                android:background="@drawable/baseline_circle_24"
                android:backgroundTint="@color/white" />

            <View
                android:id="@+id/temp3"
                android:layout_width="10dp"
                android:layout_height="10dp"
                android:layout_marginStart="26dp"
                android:background="@drawable/baseline_circle_24" />
</ViewFlipper>
  • inAnimation과 outAnimation을 통해 자식 View가 나타나고 사라지는 과정의 애니메이션을 지정할 수 있다. fadeIn fadeOut 애니메이션은 아래와 같다.
    <?xml version="1.0" encoding="utf-8"?>
    <alpha xmlns:android="http://schemas.android.com/apk/res/android"
        android:fromAlpha="0.0"
        android:toAlpha="1.0"
        android:duration="500"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator" />
    <?xml version="1.0" encoding="utf-8"?>
    <alpha xmlns:android="http://schemas.android.com/apk/res/android"
        android:fromAlpha="1.0"
        android:toAlpha="0.0"
        android:duration="500"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator" />

그리고 typing_indicator를 제어하기 위해 recyclerview adpater를 다음과 같이 구성한다.

inner class BotViewHolder(binding: ItemChatBotBinding) : RecyclerView.ViewHolder(binding.root) {
        var chatTxt = binding.itemMsgBotTv
        private val typingIndicator: ViewFlipper = binding.typingIndicator

        init {
            val bounceAnim = AnimationUtils.loadAnimation(context, R.anim.bounce)
            chatTxt.startAnimation(bounceAnim)

        }

        fun bind(item: Message) {
            chatTxt.text = item.message

            if (item.sendBy == SEND_BY_TYPING) {
                typingIndicator.apply {
                    visibility = View.VISIBLE
                    startFlipping()
                    flipInterval = 200
                }
            } else {
                typingIndicator.apply {
                    visibility = View.GONE
                    stopFlipping()
                }
            }
        }
    }
  • startFlipping()stopFlipping()을 통해 점의 움직임을 제어한다.
  • flipInterval을 활용하면 속도를 조절할 수 있다. 단위는 밀리초다.
  • addView()로 자식 뷰를 추가할 수도 있다.
  • setDisplayedChild()로 전환할 자식 뷰의 인덱스 지정도 가능하다.

0개의 댓글