几种常见的创建全局消息弹框的方法分析

本章涉及知识点复习:

Activity、Dialog、PopWinow、Toast窗口添加机制

https://www.jianshu.com/p/a02a4f504948

总结:添加View到Window就是调用WindowManager 的 addView 方法,需要三个对象,WindowManager、View、LayoutParams。

需求:

  • 全局消息弹框
  • 顶部弹入弹出
  • 点击跳转聊天界面
  • 消息外部区域点击事件透传

以下是几种常见的创建全局消息弹框的方法:

1、通过AlertDialog实现

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Utils.init(applicationContext)
    }

    public fun showDialog(v: View) {
        ActivityLifecycleManager[BaseApplication.instance].getCurrentActivity()?.let 
    {
            AppUtils.showMessage(it, "")
        }
    }

    public fun showCustomView(v: View) {
        ActivityLifecycleManager[BaseApplication.instance].getCurrentActivity()?.let
    {
            AppUtils.showActivityWindow(it,"")
        }
    }
    
    public fun showActivity(v: View) {
        AppUtils.showMessageActivity("")
    }
    public fun showSystemWindow(v: View) {
        AppUtils.showSystemWindow("")
    }

    public fun showOverlayPermission(v:View){
        AppUtils.checkOverlayPermission(this)
    }
}
    fun showMessage(context: Activity, msg: String) {
        var dialog = AlertDialog.Builder(context, R.style.TopMessageDialog).create()
        var view = View.inflate(context, R.layout.layout_new_message, null)
        dialog.setView(view)
        dialog.window?.setGravity(Gravity.TOP)
        //设置沉浸式导航栏
        dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
        dialog.window?.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
        //设置window以外区域的点击事件透传的下层window
        dialog.window?.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
        //设置dialog的动画效果
        dialog.window?.setWindowAnimations(R.style.DialogScaleAnimStyle)
        dialog.show()
        var llContainer =
            dialog.window?.decorView?.findViewById(R.id.fl_container)
        var params = llContainer?.layoutParams
        params?.width = ScreenUtils.getScreenWidth()
        params?.height = SizeUtils.dp2px(65f) +     
        XStatusBarHelper.getStatusBarHeight(context)
        llContainer?.layoutParams = params
        llContainer?.setPadding(
            SizeUtils.dp2px(20f),
            XStatusBarHelper.getStatusBarHeight(context),
            SizeUtils.dp2px(20f),
            SizeUtils.dp2px(10f)
        )
        llContainer?.setOnClickListener {
            ToastUtils.showShort("跳转到聊天界面")
            dialog?.dismiss()
        }
    }

 

    

优点:不需要申请悬浮窗口权限

缺点:必须依赖activity,上下文必须传入activity。某些情况下无法获取当前可见activity时无法使用,如果 Dialog 弹出来之前 Activity 已经被销毁了,则这个 Dialog 在弹出的时候就会抛出异常,这个在线上版本收集到的崩溃中偶尔会出现。

附activity生命周期管理工具类:

class ActivityLifecycleManager : Application.ActivityLifecycleCallbacks {

    private var totalActivityCount = 0
    private var activityCount = 0
    private var foreground = false
    private var sCurrentActivityWeakRef: WeakReference? = null
    private val listeners = CopyOnWriteArrayList()

    companion object {
        private var instance: ActivityLifecycleManager? = null
        fun init(application: Application): ActivityLifecycleManager {
            if (instance == null) {
                instance =
                    ActivityLifecycleManager()
                application.registerActivityLifecycleCallbacks(instance)
            }
            return instance as ActivityLifecycleManager
        }

        operator fun get(application: Application): ActivityLifecycleManager {
            if (instance == null) {
                init(application)
            }
            return instance!!
        }

        operator fun get(ctx: Context): ActivityLifecycleManager {
            if (instance == null) {
                val appCtx = ctx.applicationContext
                if (appCtx is Application) {
                    init(appCtx)
                }
                throw IllegalStateException(
                    "Foreground is not initialised and " + "cannot obtain the 
        Application object"
                )
            }
            return instance as ActivityLifecycleManager
        }

        fun get(): ActivityLifecycleManager {
            if (instance == null) {
                throw IllegalStateException(
                    "Foreground is not initialised - invoke " + "at least once with parameterised init/get"
                )
            }
            return instance as ActivityLifecycleManager
        }
    }

    override fun onActivityCreated(activity: Activity, p1: Bundle?) {
        totalActivityCount++
        AppActivityManager.instance.addActivity(activity)
    }

    override fun onActivityStarted(activity: Activity) {
        activityCount++
    }

    override fun onActivitySaveInstanceState(activity: Activity, p1: Bundle) {
    }

    override fun onActivityResumed(activity: Activity) {
        sCurrentActivityWeakRef = WeakReference(activity)
        if (!foreground) {
            foreground = true
            LogUtils.dTag("Lifecycle", "app into the foreground")
        }
    }

    override fun onActivityPaused(activity: Activity) {
    }

    override fun onActivityStopped(activity: Activity) {
        activityCount--
        if (activityCount == 0 && totalActivityCount > 0) {
            foreground = false
            LogUtils.dTag("Lifecycle", "app into the background")
        }

    }

    override fun onActivityDestroyed(activity: Activity) {
        totalActivityCount--
        AppActivityManager.instance.removeActivity(activity)
        if (totalActivityCount == 0) LogUtils.dTag("Lifecycle", "app exit")
    }

    fun isForeground(): Boolean {
        return foreground
    }

    fun isBackground(): Boolean {
        return !foreground
    }

    fun getCurrentActivity(): Activity? {
        return sCurrentActivityWeakRef?.get()
    }

    fun addListener(listener: AppStateListener) {
        listeners.add(listener)
    }

    fun removeListener(listener: AppStateListener) {
        listeners.remove(listener)
    }

    interface AppStateListener {
        fun onBecameForeground()
        fun onBecameBackground()
    }
}

2、通过Activity的Window实现

    fun showActivityWindow(context: Activity, msg: String) {
        val layoutParams = WindowManager.LayoutParams()
        layoutParams.flags =
            WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
                    WindowManager.LayoutParams.FLAG_FULLSCREEN or
                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
        layoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
        layoutParams.gravity = Gravity.TOP
        layoutParams.x = 0
        layoutParams.y = 0
        layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT
        layoutParams.height = SizeUtils.dp2px(65f) + 
        XStatusBarHelper.getStatusBarHeight(context)
        layoutParams.format = PixelFormat.TRANSPARENT
        layoutParams.windowAnimations = R.style.DialogScaleAnimStyle
        val layoutInflater = LayoutInflater.from(context) as LayoutInflater
        val view = layoutInflater.inflate(R.layout.layout_new_message, null) as View
        val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as 
        WindowManager
        windowManager.addView(view, layoutParams)
        view.setPadding(
            SizeUtils.dp2px(20f),
            XStatusBarHelper.getStatusBarHeight(context),
            SizeUtils.dp2px(20f),
            SizeUtils.dp2px(10f)
        )
        view.setOnClickListener {
            ToastUtils.showShort("跳转到聊天界面")
            windowManager.removeView(it)
        }
    }

优点:不需要申请悬浮窗口权限

缺点:必须依赖activity,上下文必须传入activity。某些情况下无法获取当前可见activity时无法使用。

3、通过系统Window实现

fun showSystemWindow(msg: String) {
        val layoutParams = WindowManager.LayoutParams()
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
            //8.0以后使用TYPE_SYSTEM_ALERT,部分手机直接抛出异常,崩溃
            layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
        } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
            layoutParams.type = WindowManager.LayoutParams.TYPE_TOAST  
        } else {
            layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
        }
        layoutParams.flags =
            WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or 
                    WindowManager.LayoutParams.FLAG_FULLSCREEN or 
                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
        layoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
        layoutParams.gravity = Gravity.TOP
        layoutParams.x = 0
        layoutParams.y = 0
        layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT
        layoutParams.height =
            SizeUtils.dp2px(65f) + 
        XStatusBarHelper.getStatusBarHeight(BaseApplication.instance)
        layoutParams.format = PixelFormat.TRANSPARENT
        layoutParams.windowAnimations = R.style.DialogScaleAnimStyle
        val layoutInflater =
            LayoutInflater.from(BaseApplication.instance.applicationContext) as 
        LayoutInflater
        val view = layoutInflater.inflate(R.layout.layout_new_message, null) as View
        val windowManager =
            BaseApplication.instance.getSystemService(Context.WINDOW_SERVICE) as 
        WindowManager
        windowManager.addView(view, layoutParams)
        view.setPadding(
            SizeUtils.dp2px(20f),
            XStatusBarHelper.getStatusBarHeight(BaseApplication.instance),
            SizeUtils.dp2px(20f),
            SizeUtils.dp2px(10f)
        )
        view.setOnClickListener {
            ToastUtils.showShort("跳转到聊天界面")
            windowManager.removeView(it)
        }
    }

 

fun checkOverlayPermission(context: Context): Boolean {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (Settings.canDrawOverlays(context)) {
                Toast.makeText(context, "已取得悬浮窗使用权限", Toast.LENGTH_SHORT).show()
            } else {
                Toast.makeText(context, "需要取得权限以使用悬浮窗", Toast.LENGTH_SHORT).show()
                val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
                intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                intent.data = Uri.parse("package:${context.packageName}")
                context.startActivity(intent)
            }
            return Settings.canDrawOverlays(context)
        } else {
            //SDK在23以下,不用管.
            return true
        }
    }

优点:不需要依赖任何activity

缺点:6.0之后需要申请悬浮窗使用权限,否则无法显示。实际情况是大部分人不会允许。

 

4、通过透明Activity实现

    fun showMessageActivity(msg: String) {    
       NewMessageNotificationActivity.start(BaseApplication.instance.applicationContext)
    }
class NewMessageNotificationActivity : AppCompatActivity(), View.OnClickListener {

    companion object {
        private const val EXTRA_KEY1 = "extra_key1"
        private val bgColors: List =
            mutableListOf("#F76793", "#D2B469", "#43D96D", "#50C7FB")

        fun start(context: Context) {
            val intent = Intent(context, NewMessageNotificationActivity::class.java)
            if (context !is Activity) {
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            }
            context.startActivity(intent)
        }
    }

    private var tast: Runnable? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        window.setGravity(Gravity.TOP)
        window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
        window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
        setFinishOnTouchOutside(true)
        initLayout()
        initView()
        initData()
        initEvent()
        tast = Runnable { finish() }
        BaseApplication.handler.postDelayed(tast, 3000)
    }

    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        if (event.keyCode == 4) {
            finish()
        }
        return super.dispatchKeyEvent(event)
    }

    fun initLayout() {
        setContentView(R.layout.layout_new_message)
        var background = fl_container.background as GradientDrawable
       background.setColor(Color.parseColor(bgColors[java.util.Random().nextInt(4)]))
        var layoutParams = fl_container.layoutParams as FrameLayout.LayoutParams
        layoutParams.width = ScreenUtils.getScreenWidth()
        layoutParams.height = SizeUtils.dp2px(65f) + 
        XStatusBarHelper.getStatusBarHeight(this)
        fl_container.layoutParams = layoutParams
        fl_container.setPadding(
            SizeUtils.dp2px(20f),
            XStatusBarHelper.getStatusBarHeight(this),
            SizeUtils.dp2px(20f),
            SizeUtils.dp2px(10f)
        )
    }


    fun initEvent() {
        fl_container.setOnClickListener(this)
    }

    fun initData() {

    }

    override fun onStop() {
        super.onStop()
        tast?.let {
            BaseApplication.handler.removeCallbacks(it)
        }
        finish()
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        tast?.let {
            BaseApplication.handler.removeCallbacks(it)
            BaseApplication.handler.postDelayed(it, 3000)
        }
        initView()
    }

    fun initView() {
    }

    override fun onClick(v: View) {
        when (v.id) {
            R.id.fl_container -> {
                ToastUtils.showShort("跳转到聊天界面")
                finish()
            }
        }
    }


    override fun finish() {
        super.finish()
        overridePendingTransition(R.anim.slide_in_null, R.anim.pop_top_out)
    }

    override fun onBackPressed() {
        finish()
    }

}

 

优点:弹框以四大组件的形式存在,不需要用户开通悬浮窗权限。各版本、各机型不存在适配问题。且有完整的生命周期管理,可以处理更多的事务。

缺点:暂无。

 

你可能感兴趣的:(android)