안드로이드의 Toast는 기본적으로 텍스트 메시지를 위한 것이며, 사용자 인터랙션(예: 버튼 클릭)을 직접 처리하는 기능을 내장하고 있지 않습니다. 따라서 커스텀 Toast로 버튼을 삽입한다고 하더라도 onClick에 대한 처리가 불가능합니다.
그래도 Toast에 Clickable을 추가하고 싶다면? Custom Dialog를 활용하여 어느 정도 비~슷하게 구현이 가능합니다.
우선 위와 같은 형태로 Custom Dialog 레이아웃 XML 파일을 구성했습니다.
그다음, ClickableToast라는 이름으로 Custom Dialog를 생성합니다.
class ClickableToast(private val context: Context, private val text: String) : Dialog(context) {
private lateinit var binding: ViewToastBinding
companion object {
//Toast.LENGTH_SHORT
private const val DURATION_SHORT = 2000L
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ViewToastBinding.inflate(layoutInflater)
setContentView(binding.root)
}
....
}
- Toast.LENGTH_SHORT: 대략 2000밀리 초(2초)
- Toast.LENGTH_LONG: 대략 3500밀리 초(3.5초)
Android Toast의 Fade In - Out 애니메이션을 적용하려면 Dialog의 onStart 시점에 fade In, 그리고 지정한 시간 후에 onCreate에서 fade out 애니메이션 로직을 호출하면 되겠습니다.
먼저 onStart에서 Toast.LENGTH_SHORT 절반 값의 Duration으로 Fade In 애니메이션을 구성합니다.
override fun onStart() {
super.onStart()
val decorView = window?.decorView
val scaleDown = ObjectAnimator.ofPropertyValuesHolder(
decorView,
PropertyValuesHolder.ofFloat("alpha", 0.0f, 1.0f)
)
scaleDown.setDuration(DURATION_SHORT / 2)
scaleDown.start()
}
그리고 onCreate에서 Handler를 생성하여 Fade-Out 애니메이션을 구성합니다.
Handler는 Toast.LENGTH_SHORT 이후에 Fade-Out 애니메이션을 호출하고 해당 애니메이션은 Toast.LENGTH_SHORT 절반 값의 Duration을 가지도록 했습니다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ViewToastBinding.inflate(layoutInflater)
setContentView(binding.root)
Handler(Looper.getMainLooper()).postDelayed({
dismissDialog()
}, DURATION_SHORT)
}
private fun dismissDialog() {
val decorView = window?.decorView
val scaleDown = ObjectAnimator.ofPropertyValuesHolder(
decorView,
PropertyValuesHolder.ofFloat("alpha", 1.0f, 0.0f)
)
scaleDown.addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(p0: Animator) {}
override fun onAnimationEnd(p0: Animator) { dismiss() }
override fun onAnimationCancel(p0: Animator) {}
override fun onAnimationRepeat(p0: Animator) {}
})
scaleDown.setDuration(DURATION_SHORT / 2)
scaleDown.start()
}
참고로 공통적으로 사용된 window.decorView는 Dialog, Activity 등의 최상위 View를 참조하는 것입니다. 이것이 곧 윈도의 콘텐츠가 실제로 표시되는 영역을 가리키고, 그에 대해 효과 수정을 적용할 수 있습니다.
이제 Toast 애니메이션에 대한 효과는 구현이 완료되었습니다.
하지만 기본 다이얼로그 특성상, 뒷배경이 어두워지고, 팝업 위치가 일반 Toast와 다른 차이점들이 존재하죠.
이 부분들을 조정해 주었습니다.
window?.apply {
setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
val params = attributes
params.gravity = Gravity.TOP
params.y = dpToPx(70f)
attributes = params
}
onCreate에서 최상위 View를 컨트롤하는 window를 통해 FLAG_DIM_BEHIND, Color.TRANSPARENT로 뒷배경을 투명하게 처리해 주었고, 중심 값은 TOP, 상단으로부터의 위치는 70f 정도로 비교적 높은 위치에 팝업 시키도록 설정해 주었습니다.
FLAG_NOT_TOUCH_MODAL은 다이얼로그 Toast 팝업을 유지하면서 배경의 컴포넌트는 클릭 가능하도록 해줍니다.
추가적으로 기본 Toast 디자인을 따라가고 싶다면 레이아웃 구성 시, CardView를 활용하여 포인트를 잡아주는 것 또한 진행해 줍니다.
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toastView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardBackgroundColor="#4a4a4a"
app:cardCornerRadius="20dp"
app:cardElevation="0dp">
.....