Android使用协程实现自定义Toast
最近有个消息提示需要显示10s,刚开始使用协程写了一个shoowToast方法,传入消息内容、显示时间和toast显示类型即可,以为能满足需求,结果测试说只有5s,查看日志和源码发现Android系统中Toast显示有2种类型Toast.LENGTH_SHORT和
Toast.LENGTH_LONG,分别代表Toast消息显示的时间为短暂(大约2秒)和长时间(大约3.5秒),这和我们所需要的还是有很大差距的,于是通过自定义WindowManager+协程方式实现了此需求.
1.showToast方法如下:
kotlin
object ToastUtils {
private var toastJob :Job ?= null
fun Context.showToast(message: String, duration: Int = Toast.LENGTH_SHORT, delayTime: Long = 2000L) {
val toast = Toast.makeText(this@showToast, message, duration)
toast.show()
toastJob?.cancel()
toastJob = CoroutineScope(Dispatchers.Main).launch {
delay(delayTime)
toast.cancel()
}
}
}
2.使用示例:
kotlin
private fun initViews() {
val textView = findViewById<TextView>(R.id.tv_test)
textView.setOnClickListener {
mCountdownJob = countDownCoroutines(10, lifecycleScope,
onTick = { second ->
textView.text = buildString {
append(second)
append("s后重发")
}
}, onStart = {
// 倒计时开始
}, onFinish = {
// 倒计时结束,重置状态
textView.text = buildString {
append("发送验证码")
}
})
showToast("祝大家国庆节快乐,万事如意",1,1000L * 10)
}
}
3.实现的效果如下:
可以看到虽然显示了Toast,但是5s就消失了,设置显示时间和动态传入10s都是不行的。
4.自定义Toast弹框(协程):
使用协程实现
kotlin
package com.cloud.customtoastdemo.toast
import android.content.Context
import android.graphics.PixelFormat
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
import android.widget.TextView
import com.cloud.customtoastdemo.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
/**
* @auth: njb
* @date: 2024/10/13 16:56
* @desc: 描述
*/
object EasyToast {
private var easyToastView: View? = null
private var windowManager: WindowManager? = null
private var mToastJob:Job ?= null
private val TAG = "EasyToast"
/**
*
* @param context 上下文
* @param message 提示内容消息
* @param duration 可动态设置在的显示时间
* @param gravity 显示位置 top、center、bottom
*/
fun showCustomToast(context: Context, message: String, duration: Int, gravity: Int) {
windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
if (easyToastView == null) {
easyToastView = inflater.inflate(R.layout.custom_easy_toast, null)
val textView = easyToastView?.findViewById<TextView>(R.id.tv_message)
textView?.text = message
}
val params = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
)
params.gravity = gravity
params.x = 0
params.y = 0
if (easyToastView?.parent == null) {
windowManager?.addView(easyToastView, params)
}
mToastJob?.cancel()
mToastJob = CoroutineScope(Dispatchers.Main).launch {
delay(duration.toLong())
Log.d(TAG, "时间到了结束弹框$duration")
if (easyToastView != null) {
windowManager?.removeView(easyToastView)
easyToastView = null
}
}
}
fun cancelEasyToast() {
if (easyToastView != null) {
windowManager?.removeView(easyToastView)
easyToastView = null
}
mToastJob?.cancel()
}
}
5.自定义Toast弹框(Handler):
使用Handler实现:
kotlin
package com.cloud.customtoastdemo.toast
import android.content.Context
import android.graphics.PixelFormat
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
import android.widget.TextView
import com.cloud.customtoastdemo.R
/**
* @auth: njb
* @date: 2024/10/13 16:56
* @desc: 描述
*/
object EasyToast {
private var toastView: View? = null
private var easyToastView: View? = null
private var windowManager: WindowManager? = null
private val handler = Handler(Looper.getMainLooper())
private lateinit var runnable: Runnable
/**
*
* @param context 上下文
* @param message 提示内容
* @param message 提示内容消息
* @param duration 显示时间
* @param gravity 显示位置
* @param gravity 显示位置 top、center、bottom
*/
fun showToast(context: Context, message: String, duration: Int, gravity: Int) {
windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
if (toastView == null) {
toastView = inflater.inflate(R.layout.custom_toast, null)
val textView = toastView?.findViewById<TextView>(R.id.tv_message)
if (easyToastView == null) {
easyToastView = inflater.inflate(R.layout.custom_toast, null)
val textView = easyToastView?.findViewById<TextView>(R.id.tv_message)
textView?.text = message
}
val params = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
)
params.gravity = gravity
params.x = 0
params.y = 0
if (toastView?.parent == null) {
windowManager?.addView(toastView, params)
if (easyToastView?.parent == null) {
windowManager?.addView(easyToastView, params)
}
runnable = Runnable {
if (toastView != null) {
windowManager?.removeView(toastView)
toastView = null
if (easyToastView != null) {
windowManager?.removeView(easyToastView)
easyToastView = null
}
handler.removeCallbacks(runnable)
}
handler.postDelayed(runnable!!, duration.toLong())
}
fun cancelEasyToast() {
runnable?.let {
handler.removeCallbacks(it)
}
if (toastView != null) {
windowManager?.removeView(toastView)
toastView = null
if (easyToastView != null) {
windowManager?.removeView(easyToastView)
easyToastView = null
}
}
}
6.使用示例:
package com.cloud.customtoastdemo
import android.os.Bundle
import android.util.Log
import android.view.Gravity
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.lifecycleScope
import com.cloud.customtoastdemo.contants.Constants
import com.cloud.customtoastdemo.toast.EasyToast
import com.cloud.customtoastdemo.toast.ToastUtils.showToast
import com.cloud.customtoastdemo.utils.CountDownUtils.countDownCoroutines
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
private var mCountdownJob: Job? = null
private val TAG = "MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
initViews()
}
private fun initViews() {
val textView = findViewById<TextView>(R.id.tv_test)
textView.setOnClickListener {
mCountdownJob = countDownCoroutines(10, lifecycleScope,
onTick = { second ->
Log.d(TAG, "toast显示时间$second")
textView.text = buildString {
append(second)
append("s后重发")
}
}, onStart = {
// 倒计时开始
}, onFinish = {
// 倒计时结束,重置状态
textView.text = buildString {
append("发送验证码")
}
})
/*
lifecycleScope.launch {
delay(1000)
showToast("祝大家国庆节快乐,万事如意",1,1000L * 10)
}
*/
EasyToast.showCustomToast(
this@MainActivity,
message = buildString {
append("祝大家国庆节快乐,万事如意")
},
duration = Constants.TOAST_SHOW_TIME,
gravity = Gravity.TOP
)
}
}
}
7.实现效果如下:
8.日志打印:
9.总结:
从上面的截图可以看出基本上是满足要求的,显示了10sToast提示才消失,至于这个显示时间你可以根据自己的需求动态设置,我这里也没有设置默认时长,尝试过利用反射修改Toast的显示时间和协程delpay方式设置显示时间都没有生效,所以采用WindowManager+协程的方式,当然使用Handler+dialog也可以,今天的内容就到这里,如何实现动态显示Toast时长,打卡收工,关机睡觉.