Android 全局通知弹窗示例分析详解

目录
  • 需求分析
  • 一、Dialog的编写
  • 二、获取当前显示的Activity的弱引用
  • 三、封装和使用

需求分析

如何创建一个全局通知的弹窗?如下图所示。

从手机顶部划入,短暂停留后,再从顶部划出。

首先需要明确的是:

1、这个弹窗的弹出逻辑不一定是当前界面编写的,比如用户上传文件,用户可能继续浏览其他页面的内容,但是监听文件是否上传完成还是在原来的Activity,但是Dialog的弹出是需要当前页面的上下文Context的。

2、Dialog弹窗必须支持手势,用户在Dialog上向上滑时,Dialog需要退出,点击时可能需要处理点击事件。

一、Dialog的编写

/**
 * 通知的自定义Dialog
 */
class NotificationDialog(context: Context, var title: String, var content: String) :
    Dialog(context, R.style.dialog_notifacation_top) {
    private var mListener: OnNotificationClick? = null
    private var mStartY: Float = 0F
    private var mView: View? = null
    private var mHeight: Int? = 0
    init {
        mView = LayoutInflater.from(context).inflate(R.layout.common_layout_notifacation, null)
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(mView!!)
        window?.setGravity(Gravity.TOP)
        val layoutParams = window?.attributes
        layoutParams?.width = ViewGroup.LayoutParams.MATCH_PARENT
        layoutParams?.height = ViewGroup.LayoutParams.WRAP_CONTENT
        layoutParams?.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
        window?.attributes = layoutParams
        window?.setWindowAnimations(R.style.dialog_animation)
        //按空白处不能取消
        setCanceledOnTouchOutside(false)
        //初始化界面数据
        initData()
    }
    private fun initData() {
        val tvTitle = findViewById<TextView>(R.id.tv_title)
        val tvContent = findViewById<TextView>(R.id.tv_content)
        if (title.isNotEmpty()) {
            tvTitle.text = title
        }
        if (content.isNotEmpty()) {
            tvContent.text = content
        }
    }
    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                if (isOutOfBounds(event)) {
                    mStartY = event.y
                }
            }
            MotionEvent.ACTION_UP -> {
                if (mStartY > 0 && isOutOfBounds(event)) {
                    val moveY = event.y
                    if (abs(mStartY - moveY) >= 20) {  //滑动超过20认定为滑动事件
                        //Dialog消失
                    } else {                //认定为点击事件
                        //Dialog的点击事件
                        mListener?.onClick()
                    }
                    dismiss()
                }
            }
        }
        return false
    }
    /**
     * 点击是否在范围外
     */
    private fun isOutOfBounds(event: MotionEvent): Boolean {
        val yValue = event.y
        if (yValue > 0 && yValue <= (mHeight ?: (0 + 40))) {
            return true
        }
        return false
    }
    private fun setDialogSize() {
        mView?.addOnLayoutChangeListener { v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
            mHeight = v?.height
        }
    }
    /**
     * 显示Dialog但是不会自动退出
     */
    fun showDialog() {
        if (!isShowing) {
            show()
            setDialogSize()
        }
    }
    /**
     * 显示Dialog,3000毫秒后自动退出
     */
    fun showDialogAutoDismiss() {
        if (!isShowing) {
            show()
            setDialogSize()
            //延迟3000毫秒后自动消失
            Handler(Looper.getMainLooper()).postDelayed({
                if (isShowing) {
                    dismiss()
                }
            }, 3000L)
        }
    }
    //处理通知的点击事件
    fun setOnNotificationClickListener(listener: OnNotificationClick) {
        mListener = listener
    }
    interface OnNotificationClick {
        fun onClick()
    }
}

Dialog的主题

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
    <style name="dialog_notifacation_top">
        <item name="android:windowIsTranslucent">true</item>
        <!--设置背景透明-->
        <item name="android:windowBackground">@android:color/transparent</item>
        <!--设置dialog浮与activity上面-->
        <item name="android:windowIsFloating">true</item>
        <!--去掉背景模糊效果-->
        <item name="android:backgroundDimEnabled">false</item>
        <item name="android:windowNoTitle">true</item>
        <!--去掉边框-->
        <item name="android:windowFrame">@null</item>
    </style>
    <style name="dialog_animation" parent="@android:style/Animation.Dialog">
        <!-- 进入时的动画 -->
        <item name="android:windowEnterAnimation">@anim/dialog_enter</item>
        <!-- 退出时的动画 -->
        <item name="android:windowExitAnimation">@anim/dialog_exit</item>
    </style>
</resources>

Dialog的动画

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="600"
        android:fromYDelta="-100%p"
        android:toYDelta="0%p" />
</set>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="300"
        android:fromYDelta="0%p"
        android:toYDelta="-100%p" />
</set>

Dialog的布局,通CardView包裹一下就有立体阴影的效果

<androidx.cardview.widget.CardView
    android:id="@+id/cd"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="@dimen/size_15dp"
    app:cardCornerRadius="@dimen/size_15dp"
    app:cardElevation="@dimen/size_15dp"
    app:layout_constraintTop_toTopOf="parent">
    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/et_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="@dimen/size_15dp"
        app:layout_constraintTop_toTopOf="parent">
        <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#000000"
            android:textSize="@dimen/font_14sp" android:textStyle="bold"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
        <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/tv_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/size_15dp"
            android:textColor="#333"
            android:textSize="@dimen/font_12sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tv_title" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

二、获取当前显示的Activity的弱引用

/**
 * 前台Activity管理类
 */
class ForegroundActivityManager {
    private var currentActivityWeakRef: WeakReference<Activity>? = null
    companion object {
        val TAG = "ForegroundActivityManager"
        private val instance = ForegroundActivityManager()
        @JvmStatic
        fun getInstance(): ForegroundActivityManager {
            return instance
        }
    }
    fun getCurrentActivity(): Activity? {
        var currentActivity: Activity? = null
        if (currentActivityWeakRef != null) {
            currentActivity = currentActivityWeakRef?.get()
        }
        return currentActivity
    }
    fun setCurrentActivity(activity: Activity) {
        currentActivityWeakRef = WeakReference(activity)
    }
}

监听所有Activity的生命周期

class AppLifecycleCallback:Application.ActivityLifecycleCallbacks {
    companion object{
        val TAG = "AppLifecycleCallback"
    }
    override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
        //获取Activity弱引用
        ForegroundActivityManager.getInstance().setCurrentActivity(activity)
    }
    override fun onActivityStarted(activity: Activity) {
    }
    override fun onActivityResumed(activity: Activity) {
        //获取Activity弱引用
        ForegroundActivityManager.getInstance().setCurrentActivity(activity)
    }
    override fun onActivityPaused(activity: Activity) {
    }
    override fun onActivityStopped(activity: Activity) {
    }
    override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
    }
    override fun onActivityDestroyed(activity: Activity) {
    }
}

在Application中注册

//注册Activity生命周期
registerActivityLifecycleCallbacks(AppLifecycleCallback())

三、封装和使用

/**
 * 通知的管理类
 * example:
 *     //发系统通知
 *    NotificationControlManager.getInstance()?.notify("文件上传完成", "文件上传完成,请点击查看详情")
 *    //发应用内通知
 *     NotificationControlManager.getInstance()?.showNotificationDialog("文件上传完成","文件上传完成,请点击查看详情",
 *           object : NotificationControlManager.OnNotificationCallback {
 *                override fun onCallback() {
 *                   Toast.makeText(this@MainActivity, "被点击了", Toast.LENGTH_SHORT).show()
 *                 }
 *    })
 */
class NotificationControlManager {
    private var autoIncreament = AtomicInteger(1001)
    private var dialog: NotificationDialog? = null
    companion object {
        const val channelId = "aaaaa"
        const val description = "描述信息"
        @Volatile
        private var sInstance: NotificationControlManager? = null
        @JvmStatic
        fun getInstance(): NotificationControlManager? {
            if (sInstance == null) {
                synchronized(NotificationControlManager::class.java) {
                    if (sInstance == null) {
                        sInstance = NotificationControlManager()
                    }
                }
            }
            return sInstance
        }
    }
    /**
     * 是否打开通知
     */
    fun isOpenNotification(): Boolean {
        val notificationManager: NotificationManagerCompat =
            NotificationManagerCompat.from(
                ForegroundActivityManager.getInstance().getCurrentActivity()!!
            )
        return notificationManager.areNotificationsEnabled()
    }
    /**
     * 跳转到系统设置页面去打开通知,注意在这之前应该有个Dialog提醒用户
     */
    fun openNotificationInSys() {
        val context = ForegroundActivityManager.getInstance().getCurrentActivity()!!
        val intent: Intent = Intent()
        try {
            intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
            //8.0及以后版本使用这两个extra.  >=API 26
            intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
            intent.putExtra(Settings.EXTRA_CHANNEL_ID, context.applicationInfo.uid)
            //5.0-7.1 使用这两个extra.  <= API 25, >=API 21
            intent.putExtra("app_package", context.packageName)
            intent.putExtra("app_uid", context.applicationInfo.uid)
            context.startActivity(intent)
        } catch (e: Exception) {
            e.printStackTrace()
            //其他低版本或者异常情况,走该节点。进入APP设置界面
            intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
            intent.putExtra("package", context.packageName)
            //val uri = Uri.fromParts("package", packageName, null)
            //intent.data = uri
            context.startActivity(intent)
        }
    }
    /**
     * 发通知
     * @param title 标题
     * @param content 内容
     * @param cls 通知点击后跳转的Activity,默认为null跳转到MainActivity
     */
    fun notify(title: String, content: String, cls: Class<*>) {
        val context = ForegroundActivityManager.getInstance().getCurrentActivity()!!
        val notificationManager =
            context.getSystemService(AppCompatActivity.NOTIFICATION_SERVICE) as NotificationManager
        val builder: Notification.Builder
        val intent = Intent(context, cls)
        val pendingIntent: PendingIntent? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
        } else {
            PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT)
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val notificationChannel =
                NotificationChannel(channelId, description, NotificationManager.IMPORTANCE_HIGH)
            notificationChannel.enableLights(true);
            notificationChannel.lightColor = Color.RED;
            notificationChannel.enableVibration(true);
            notificationChannel.vibrationPattern =
                longArrayOf(100, 200, 300, 400, 500, 400, 300, 200, 400)
            notificationManager.createNotificationChannel(notificationChannel)
            builder = Notification.Builder(context, channelId)
                .setSmallIcon(R.drawable.jpush_notification_icon)
                .setContentIntent(pendingIntent)
                .setContentTitle(title)
                .setContentText(content)
        } else {
            builder = Notification.Builder(context)
                .setSmallIcon(R.drawable.jpush_notification_icon)
                .setLargeIcon(
                    BitmapFactory.decodeResource(
                        context.resources,
                        R.drawable.jpush_notification_icon
                    )
                )
                .setContentIntent(pendingIntent)
                .setContentTitle(title)
                .setContentText(content)
        }
        notificationManager.notify(autoIncreament.incrementAndGet(), builder.build())
    }
    /**
     * 显示应用内通知的Dialog,需要自己处理点击事件。listener默认为null,不处理也可以。dialog会在3000毫秒后自动消失
     * @param title 标题
     * @param content 内容
     * @param listener 点击的回调
     */
    fun showNotificationDialog(
        title: String,
        content: String,
        listener: OnNotificationCallback? = null
    ) {
        val activity = ForegroundActivityManager.getInstance().getCurrentActivity()!!
        dialog = NotificationDialog(activity, title, content)
        if (Thread.currentThread() != Looper.getMainLooper().thread) {   //子线程
            activity.runOnUiThread {
                showDialog(dialog, listener)
            }
        } else {
            showDialog(dialog, listener)
        }
    }
    /**
     * show dialog
     */
    private fun showDialog(
        dialog: NotificationDialog?,
        listener: OnNotificationCallback?
    ) {
        dialog?.showDialogAutoDismiss()
        if (listener != null) {
            dialog?.setOnNotificationClickListener(object :
                NotificationDialog.OnNotificationClick {
                override fun onClick() = listener.onCallback()
            })
        }
    }
    /**
     * dismiss Dialog
     */
    fun dismissDialog() {
        if (dialog != null && dialog!!.isShowing) {
            dialog!!.dismiss()
        }
    }
    interface OnNotificationCallback {
        fun onCallback()
    }
}

另外需要注意的点是,因为dialog是延迟关闭的,可能用户立刻退出Activity,导致延迟时间到时dialog退出时报错,解决办法可以在BaseActivity的onDestroy方法中尝试关闭Dialog:

override fun onDestroy() {
    super.onDestroy()
    NotificationControlManager.getInstance()?.dismissDialog()
}

以上就是Android 全局通知弹窗示例分析详解的详细内容,更多关于Android全局通知弹窗的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android隐私协议提示弹窗的实现流程详解

    android studio版本:2021.2.1 例程名称:pravicydialog 功能: 1.启动app后弹窗隐私协议 2.屏蔽返回键 3.再次启动不再显示隐私协议. 本例程的绝大部分代码来自下面链接,因为本人改了一些,增加了一些功能,所以不有脸的算原创了. 下面这个例子是“正宗”app隐私协议实现方法,而且协议内容使用的是txt格式文件,据说如果使用html格式文件,各大平台在审核的时候大概率无法通过,但协议内容的还应该有更详细协议及说明的链接,我没做,暂时还没学会,会了再修改一下.

  • Android Studio实现弹窗设置

    本文实例为大家分享了Android Studio实现弹窗设置的具体代码,供大家参考,具体内容如下 弹窗能很好的显示当前处理事情的状态,那么这里介绍三种常用的弹窗方法. 1.最常用的弹窗显示 直接使用Toast 等会在屏幕下方出现一个短延时的弹窗 首先在Activity中布局好控件以及给id,再就是在Mainactivity中申明id.绑定id,然后设置点击事件,最后就是加上弹窗的代码了 这就是直接在点击事件里边加入这个弹窗就OK了 Toast.makeText(getApplicationCon

  • Android弹窗ListPopupWindow的简单应用详解

    概述 常用的弹窗有菜单,或者Dialog,但更加人性化和可自定义的还是PopupWindow 如果只是展示列表数据或者弹窗列表选择,直接使用ListPopupWindow即可,不用再单独去设置布局. 如果想要更加多样化的那就自定义一个布局,使用PopupWindow即可,也不复杂. 用法 自定义ListPopupWindow类 public class ChargeItemSumPop extends ListPopupWindow { public ChargeItemSumPop(Conte

  • Android实现Window弹窗效果

    本文实例为大家分享了Android实现Window弹窗效果的具体代码,供大家参考,具体内容如下 效果图 第一步 准备弹窗的布局,新建XML文件 photo_window <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_widt

  • Android开发之PopupWindow实现弹窗效果

    本文实例为大家分享了Android开发之PopupWindow实现弹窗的具体代码,供大家参考,具体内容如下 基本框架 在activity_main.xml中设置一个按钮,用于唤出弹窗; <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     android

  • 关于Android输入法弹窗bug的优雅处理

    目录 前言 别人家的产品处理 实现 掘金的输入框弹窗实现 weChat聊天背景不会被压缩的问题 解决方法 方法一 方法二 方法三 总结 前言 最近发现一个bug,在项目中的某个界面,每当弹出输入法时,背景总是随着输入法上移,导致背景被压缩,虽然不打紧,但发现这个bug之后极其不愉快. 别人家的产品处理 随手拿了一部手机举例 搜索框应该在顶部,这样即使弹出输入法也不会遮挡 掘金评论类似的输入框在底部,输入内容时,输入框跟随输入法上移,背景不动 wechat聊天界面,背景不动,输入框和聊天记录随着输

  • Android中常用的三个Dialog弹窗总结解析

    目录 ProgressDialog DatePickerDialog TimePickerDialog 布局 完整代码 ProgressDialog private void showProgressDialog(){ progressDialog = new ProgressDialog(DialogDemo.this); //设置提示信息 progressDialog.setTitle("提示"); progressDialog.setIcon(R.mipmap.touxiang0

  • Android 全局通知弹窗示例分析详解

    目录 需求分析 一.Dialog的编写 二.获取当前显示的Activity的弱引用 三.封装和使用 需求分析 如何创建一个全局通知的弹窗?如下图所示. 从手机顶部划入,短暂停留后,再从顶部划出. 首先需要明确的是: 1.这个弹窗的弹出逻辑不一定是当前界面编写的,比如用户上传文件,用户可能继续浏览其他页面的内容,但是监听文件是否上传完成还是在原来的Activity,但是Dialog的弹出是需要当前页面的上下文Context的. 2.Dialog弹窗必须支持手势,用户在Dialog上向上滑时,Dia

  • android内存及内存溢出分析详解

    一.Android的内存机制    Android的程序由Java语言编写,所以Android的内存管理与Java的内存管理相似.程序员通过new为对象分配内存,所有对象在java堆内分配空间:然而对象的释放是由垃圾回收器来完成的.C/C++中的内存机制是"谁污染,谁治理",java的就比较人性化了,给我们请了一个专门的清洁工(GC).    那么GC怎么能够确认某一个对象是不是已经被废弃了呢?Java采用了有向图的原理.Java将引用关系考虑为图的有向边,有向边从引用者指向引用对象.

  • Webpack source map实战分析详解

    目录 一.webpack基础 二.source-map 2.1 认识source-map 2.2 如何使用source-map 2.3 source-map文件分析 2.4 source-map常见值 2.5 source-map不常见值 2.6 source-map最佳实践 一.webpack基础 推荐我的另一篇文章:Webpack基础 二.source-map 2.1 认识source-map 代码通常运行在浏览器上时,是通过打包压缩的: 真实跑在浏览器上的代码,和我们编写的代码其实是有差异

  • Java逃逸分析详解及代码示例

    概念引入 我们都知道,Java 创建的对象都是被分配到堆内存上,但是事实并不是这么绝对,通过对Java对象分配的过程分析,可以知道有两个地方会导致Java中创建出来的对象并一定分别在所认为的堆上.这两个点分别是Java中的逃逸分析和TLAB(Thread Local Allocation Buffer)线程私有的缓存区. 基本概念介绍 逃逸分析,是一种可以有效减少Java程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法.通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的

  • android 限制某个操作每天只能操作指定的次数(示例代码详解)

    最近有个需求,要求启动页的拦截页每天只能显示3次,超过三次就显示别的页面,然后到第二天才可以再次显示,利用SharePreferences保存天数和每天的次数,大概是思路是:判断 如果是同一天,就去拿保存的次数,当次数小于3才执弹出拦截页,然后,每次弹出,次数就加1,并且保存次数和当天的时间:如果不是同一天,就把次数赋值为1,并且把当天赋值给最后访问的时间,然后保存当前的次数.具体实现如下: package com.example.demo1.test; import android.suppo

  • Android未读消息拖动气泡示例代码详解(附源码)

    前言 拖动清除未读消息可以说在很多应用中都很常见,也被用户广泛接受.本文是一个可以供参考的Demo,希望能有帮助. 提示:以下是本篇文章正文内容,下面案例可供参考 最终效果图及思路 实现关键: 气泡中间的两条边,分别是以ab,cd为数据点,G为控制点的贝塞尔曲线. 步骤: 绘制圆背景以及文本:连接情况绘制贝塞尔曲线:另外端点绘制一个圆 关键代码 1.定义,初始化等 状态:静止.连接.分离.消失 在onSizeChanged中初始化状态,固定气泡以及可动气泡的圆心 代码如下(示例): @Overr

  • Android WindowManger的层级分析详解

    目录 一. Window 分类 二. Window层级 (1)应用程序窗口: (2)子窗口: (3)系统窗口: (三)如何真正查看 Window 的优先级 (四) 层级高低具体分析(对比Toast以及软键盘) (五)如何定制系统层级 一. Window 分类 应用 Window(ApplicationWindow: 对应一个 Acitivity) 子 Window    (SubWindow:不能单独存在,需要依附在特定的父 Window 中,比如常见的一些 Dialog 就是一个子 Windo

  • Android开发中amera2 Preview使用详解

    目录 前言 一.Camera2 Preview需要用到哪些模块 二.各个模块的功能和之间的关系 2.1 SurfaceTexture之SurfaceTextureListener 2.1.1 首先看关于SurfaceTexture的说明 2.1.2 SurfaceTextureListener的使用 2.2 CameraManager 2.2.1 CameraManager的作用 2.2.2 使用CameraManager打开Camera 2.3 CameraDevice之StateCallba

  • Android TextView的TextWatcher使用案例详解

    TextWatcher是一个文本变化监听接口,定义了三个接口,分别是beforeTextChanged,onTextChanged,afterTextCahnged. TextWatcher通常与TextView结合使用,以便在文本变化的不同时机做响应的处理.TextWatcher中三个回调接口都是使用了InputFilter过滤器过滤之后的文字字符作为新的字符对象. 使用方法 mTextView.addTextChangedListener(new TextWatcher(){ @Overri

  • Android常用的数据加密方式代码详解

    前言 Android 很多场合需要使用到数据加密,比如:本地登录密码加密,网络传输数据加密,等.在android 中一般的加密方式有如下: 亦或加密  AES加密  RSA非对称加密  MD5加密算法 当然还有其他的方式,这里暂且介绍以上四种加密算法的使用方式. 亦或加密算法 什么是亦或加密? 亦或加密是对某个字节进行亦或运算,比如字节 A^K = V,这是加密过程; 当你把 V^K得到的结果就是A,也就是 V^K = A,这是一个反向操作过程,解密过程. 亦或操作效率很高,当然亦或加密也是比较

随机推荐