Android

상품리스트 - 좋아요 - 툴팁 - 트러블 슈팅

그란. 2021. 6. 5. 22:55

환경 : MVVM + 리사이클러뷰 + 데이터바인딩 + DiffUtil 

 

요구 조건 : 상품 리스트에서 좋아요를 눌렀을때 툴팁이 2초동안 보이게 해주세요!

 

 

 

- 툴팁 보여주기 

 

animate() 를 이용

tvTooltip.apply {
        alpha = 1f
        animate()
            .alpha(1f)
            .setDuration(2_000L)
            .setListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator?) {
                    alpha = 0f
                }
            })
    }

 

처음에 툴팁 TextView의 alpha 값을 0으로 설정후 

좋아요 버튼을 누르면 alpha: 1로 변경, 2초후에 alpha: 0으로 변경

 

 

구현

 

좋아요 버튼 클릭 -> 

  1. ViewModel :  해당 아이템 Id 값으로 API 통신 -> 상품 리스트 버튼의 속성 변경 
  2. RecyclerViewAdapter : 해당 아이템 포지션의 툴팁 애니메이션 작동 

 

ViewModel 아이템 연결 부분

 

   <CheckBox
                android:id="@+id/iv_like"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:checked="@{item.like}"
                android:onClick="@{()->item.wishClick.invoke()}"
 />
data class ProductUiModel(
	val wishClick: () -> Unit
)

class ProductListUiMapper(
	private val onWishClick : Subject<Product>
){
	override fun mapToView(from: List<Product>): List<ProductUiModel> {	
    
    		return from.map{
        		ProductUiModel(
	        	    ...
	           	    wishClick = { onWishClick.onNext(product) }
         		 )

       		}
   	 }

}

 

 

Adapter 코드 : onBindViewHolder()

override fun onBindViewHolder(holder: BaseViewHolder<ProductUiModel>, position: Int) {

		holder.ivLike.setOnCheckedChangeListener { _, isChecked ->
                   if (isChecked) {
                       binding.tvTooltip.apply {
                           alpha = 1f
                           animate()
                               .alpha(1f)
                               .setDuration(2_000L)
                               .setListener(object : AnimatorListenerAdapter() {
                                   override fun onAnimationEnd(animation: Animator?) {
                                       binding.tvTooltip.alpha = 0f
                                   }
                               })
                       }
                   }
                   
}

xml에서 클릭리스너를 연결했으니 뷰홀더에서는 setOnCheckedChangeListener로 연결상태를 감지하여 

체크됐을때 툴팁을 띄워주는 방식으로 구현했다.

 

 

하지만 이렇게 하면 아래와 같이 스크롤하면 데이터가 바인딩될때마다 CheckedChange가 동작하여 

툴팁이 갑자기 켜지는 버그를 보게 된다.

 

 

스크롤시 툴팁이 켜지는 현상

 

그렇다고 어댑터에서 클릭리스너를 연결해놓으면 xml에서도 click 리스너를 연결해놨기 때문에 중복으로 동작하지 않는다 

(xml에서의 onClick만 동작하게 된다, 어댑터보다 xml로 연결하는게 더 나중에 바인딩 되는듯)

 

 

그래서 onClick을 어댑터에서만 연결해주고 item 에 이벤트를 넘기는 방식으로 변경 ( xml에서는 제거 )

 

override fun onBindViewHolder(holder: BaseViewHolder<ProductUiModel>, position: Int) {
	
    binding.ivLike.setOnClickListener {
	    val item = getItem[position]
        item.wishClick(item.id, layoutPosition)
    }
		
}

item.id 는 뷰모델에서 통신할때 사용하고 

layoutPosition 은 액티비티에서 해당 포지션의 툴팁을 보여주는데 사용할 예정.

 

data class ProductUiModel(
	val wishClick: (Long, Int) -> Unit
)


class ProductListUiMapper(
	private val onWishClick : Subject<Pair<Long,Int>>
){
	override fun mapToView(from: List<Product>): List<ProductUiModel> {	
    
    		return from.map{
        		ProductUiModel(
	        	    ...
	           	      wishClick = { itemId, layoutPosition ->
                    		onWishClick.onNext(itemId to layoutPosition)
                	     }
         		 )

       		}
   	 }

}

 

뷰모델에서 itemId로 통신후 

layoutPosition 을 액티비티에 넘겨준다 

val showWishTooltip: MutableLiveData<Int> = SingleLiveData()

 showWishTooltip.observe { position ->
                val viewHolder =
                    viewDataBinding.rvProduct.findViewHolderForAdapterPosition(position)
                (viewHolder as ProductAdapter.ProductItemViewHolder).binding.tvTooltip.apply {
                    alpha = 1f
                    animate()
                        .alpha(1f)
                        .setDuration(2_000L)
                        .setListener(object : AnimatorListenerAdapter() {
                            override fun onAnimationEnd(animation: Animator?) {
                                alpha = 0f
                            }
                        })
                }

 

-> 액티비티에서 리사이클러뷰의 특정 인덱스 뷰홀더에 접근하여 뷰에 접근하는 방식

 

 

정리하면서 생각해보니 구지 어댑터에서 액티비티로 넘겨 처리할 필요가 없었다. 

어댑터에서 툴팁을 처리하고 뷰모델에 전달하면 된다.

 

 

그런데 이 부분을 바인딩어댑터로 처리할 수 있을까? 

트리거를 어떻게 줘야하지??

@BindingAdapter("tooltip")
fun TextView.bindTooltip(isStart: Boolean) {
    this.apply {
        alpha = 1f
        animate()
            .alpha(1f)
            .setDuration(2_000L)
            .setListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator?) {
                    alpha = 0f
                }
            })
    }
}