Android?全局通知彈窗示例分析詳解
需求分析
如何創(chuàng)建一個(gè)全局通知的彈窗?如下圖所示。
從手機(jī)頂部劃入,短暫停留后,再?gòu)捻敳縿澇觥?/p>
首先需要明確的是:
1、這個(gè)彈窗的彈出邏輯不一定是當(dāng)前界面編寫(xiě)的,比如用戶上傳文件,用戶可能繼續(xù)瀏覽其他頁(yè)面的內(nèi)容,但是監(jiān)聽(tīng)文件是否上傳完成還是在原來(lái)的Activity,但是Dialog的彈出是需要當(dāng)前頁(yè)面的上下文Context的。
2、Dialog彈窗必須支持手勢(shì),用戶在Dialog上向上滑時(shí),Dialog需要退出,點(diǎn)擊時(shí)可能需要處理點(diǎn)擊事件。
一、Dialog的編寫(xiě)
/** * 通知的自定義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) //初始化界面數(shù)據(jù) 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) { //滑動(dòng)超過(guò)20認(rèn)定為滑動(dòng)事件 //Dialog消失 } else { //認(rèn)定為點(diǎn)擊事件 //Dialog的點(diǎn)擊事件 mListener?.onClick() } dismiss() } } } return false } /** * 點(diǎn)擊是否在范圍外 */ 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但是不會(huì)自動(dòng)退出 */ fun showDialog() { if (!isShowing) { show() setDialogSize() } } /** * 顯示Dialog,3000毫秒后自動(dòng)退出 */ fun showDialogAutoDismiss() { if (!isShowing) { show() setDialogSize() //延遲3000毫秒后自動(dòng)消失 Handler(Looper.getMainLooper()).postDelayed({ if (isShowing) { dismiss() } }, 3000L) } } //處理通知的點(diǎn)擊事件 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> <!--設(shè)置背景透明--> <item name="android:windowBackground">@android:color/transparent</item> <!--設(shè)置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"> <!-- 進(jìn)入時(shí)的動(dòng)畫(huà) --> <item name="android:windowEnterAnimation">@anim/dialog_enter</item> <!-- 退出時(shí)的動(dòng)畫(huà) --> <item name="android:windowExitAnimation">@anim/dialog_exit</item> </style> </resources>
Dialog的動(dòng)畫(huà)
<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>
二、獲取當(dāng)前顯示的Activity的弱引用
/** * 前臺(tái)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) } }
監(jiān)聽(tīng)所有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中注冊(cè)
//注冊(cè)Activity生命周期 registerActivityLifecycleCallbacks(AppLifecycleCallback())
三、封裝和使用
/** * 通知的管理類 * example: * //發(fā)系統(tǒng)通知 * NotificationControlManager.getInstance()?.notify("文件上傳完成", "文件上傳完成,請(qǐng)點(diǎn)擊查看詳情") * //發(fā)應(yīng)用內(nèi)通知 * NotificationControlManager.getInstance()?.showNotificationDialog("文件上傳完成","文件上傳完成,請(qǐng)點(diǎn)擊查看詳情", * object : NotificationControlManager.OnNotificationCallback { * override fun onCallback() { * Toast.makeText(this@MainActivity, "被點(diǎn)擊了", 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 } } /** * 是否打開(kāi)通知 */ fun isOpenNotification(): Boolean { val notificationManager: NotificationManagerCompat = NotificationManagerCompat.from( ForegroundActivityManager.getInstance().getCurrentActivity()!! ) return notificationManager.areNotificationsEnabled() } /** * 跳轉(zhuǎn)到系統(tǒng)設(shè)置頁(yè)面去打開(kāi)通知,注意在這之前應(yīng)該有個(gè)Dialog提醒用戶 */ fun openNotificationInSys() { val context = ForegroundActivityManager.getInstance().getCurrentActivity()!! val intent: Intent = Intent() try { intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS //8.0及以后版本使用這兩個(gè)extra. >=API 26 intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) intent.putExtra(Settings.EXTRA_CHANNEL_ID, context.applicationInfo.uid) //5.0-7.1 使用這兩個(gè)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() //其他低版本或者異常情況,走該節(jié)點(diǎn)。進(jìn)入APP設(shè)置界面 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) } } /** * 發(fā)通知 * @param title 標(biāo)題 * @param content 內(nèi)容 * @param cls 通知點(diǎn)擊后跳轉(zhuǎn)的Activity,默認(rèn)為null跳轉(zhuǎn)到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()) } /** * 顯示應(yīng)用內(nèi)通知的Dialog,需要自己處理點(diǎn)擊事件。listener默認(rèn)為null,不處理也可以。dialog會(huì)在3000毫秒后自動(dòng)消失 * @param title 標(biāo)題 * @param content 內(nèi)容 * @param listener 點(diǎn)擊的回調(diào) */ 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() } }
另外需要注意的點(diǎn)是,因?yàn)閐ialog是延遲關(guān)閉的,可能用戶立刻退出Activity,導(dǎo)致延遲時(shí)間到時(shí)dialog退出時(shí)報(bào)錯(cuò),解決辦法可以在BaseActivity的onDestroy方法中嘗試關(guān)閉Dialog:
override fun onDestroy() { super.onDestroy() NotificationControlManager.getInstance()?.dismissDialog() }
以上就是Android 全局通知彈窗示例分析詳解的詳細(xì)內(nèi)容,更多關(guān)于Android全局通知彈窗的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
仿餓了嗎點(diǎn)餐界面ListView聯(lián)動(dòng)的實(shí)現(xiàn)
這篇文章主要介紹了仿餓了嗎點(diǎn)餐界面ListView聯(lián)動(dòng)的實(shí)現(xiàn)的相關(guān)資料,本文介紹的非常詳細(xì),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09Android實(shí)現(xiàn)手勢(shì)滑動(dòng)和簡(jiǎn)單動(dòng)畫(huà)效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)手勢(shì)滑動(dòng)和簡(jiǎn)單動(dòng)畫(huà)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05Android spinner下垃菜單用法實(shí)例詳解
這篇文章主要介紹了Android spinner下垃菜單用法,詳細(xì)分析了spinner下垃菜單的定義、布局及功能實(shí)現(xiàn)相關(guān)技巧,需要的朋友可以參考下2016-07-07Android無(wú)障礙監(jiān)聽(tīng)通知的實(shí)戰(zhàn)過(guò)程
開(kāi)發(fā)微動(dòng)手勢(shì)的時(shí)候,做了一個(gè)通知觸發(fā)的功能,就是在收到某個(gè)預(yù)設(shè)的通知的時(shí)候,自動(dòng)觸發(fā)某個(gè)動(dòng)作,因此需要監(jiān)聽(tīng)通知消息,這篇文章主要給大家介紹了關(guān)于Android無(wú)障礙監(jiān)聽(tīng)通知的相關(guān)資料,需要的朋友可以參考下2022-07-07Android簡(jiǎn)單實(shí)現(xiàn)計(jì)算器功能
這篇文章主要為大家詳細(xì)介紹了Android簡(jiǎn)單實(shí)現(xiàn)計(jì)算器功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08android studio 新建項(xiàng)目報(bào)錯(cuò)的解決之路
這篇文章主要介紹了android studio 新建工程報(bào)錯(cuò),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03安卓輸入框被虛擬鍵盤擋住的問(wèn)題(微信開(kāi)發(fā))
這篇文章主要介紹了安卓輸入框被虛擬鍵盤擋住的問(wèn)題(微信開(kāi)發(fā))的相關(guān)資料,需要的朋友可以參考下2016-04-04