Android使用协程实现自定义Toast

Android使用协程实现自定义Toast

​ 最近有个消息提示需要显示10s,刚开始使用协程写了一个shoowToast方法,传入消息内容、显示时间和toast显示类型即可,以为能满足需求,结果测试说只有5s,查看日志和源码发现Android系统中Toast显示有2种类型Toast.LENGTH_SHORTToast.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时长,打卡收工,关机睡觉.

10.demo地址如下:

https://gitee.com/jackning_admin/custom-toast-demo

相关推荐
叽哥13 分钟前
Kotlin学习第 8 课:Kotlin 进阶特性:简化代码与提升效率
android·java·kotlin
Cui晨13 分钟前
Android RecyclerView展示List<View> Adapter的数据源使用View
android
氦客13 分钟前
Android Doze低电耗休眠模式 与 WorkManager
android·suspend·休眠模式·workmanager·doze·低功耗模式·state_doze
玲珑Felone26 分钟前
从flutter源码看其渲染机制
android·flutter
诺诺Okami27 分钟前
Android Framework-Launcher-数据的加载
android
诺诺Okami28 分钟前
Android Framework-Launcher-Partner
android
2501_9159184134 分钟前
iOS 上架全流程指南 iOS 应用发布步骤、App Store 上架流程、uni-app 打包上传 ipa 与审核实战经验分享
android·ios·小程序·uni-app·cocoa·iphone·webview
00后程序员张2 小时前
iOS App 混淆与加固对比 源码混淆与ipa文件混淆的区别、iOS代码保护与应用安全场景最佳实践
android·安全·ios·小程序·uni-app·iphone·webview
CrimsonHu2 小时前
Android高性能音频:写一个云顶S10强音争霸混音器
android·音视频开发
灿烂阳光g10 小时前
domain_auto_trans,source_domain,untrusted_app
android·linux