From 43324d926d743333529ec9f19ef8824f0a0b25cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AE=8F=E5=AE=87?= Date: Thu, 5 Aug 2021 20:49:36 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=B0=81=E8=A3=85=E5=BC=B9?= =?UTF-8?q?=E7=AA=97=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/AndroidManifest.xml | 2 +- .../mogo/module/hmi/anim/AnimatorManager.kt | 25 ++ .../mogo/module/hmi/anim/DefaultAnimator.kt | 146 ++++++++++ .../com/mogo/module/hmi/enums/ShowPattern.kt | 12 + .../com/mogo/module/hmi/enums/SidePattern.kt | 20 ++ .../hmi/{warning => enums}/WarningTypeEnum.kt | 2 +- .../module/hmi/interfaces/OnFloatAnimator.kt | 29 ++ .../module/hmi/interfaces/OnFloatCallbacks.kt | 42 +++ .../module/hmi/notification/WarningFloat.kt | 144 ++++++++++ .../notification/WarningFloatWindowHelper.kt | 251 ++++++++++++++++++ .../notification/WarningFloatWindowManager.kt | 39 +++ .../notification/WarningNotificationConfig.kt | 55 ++++ ...eceiver.kt => WarningBroadcastReceiver.kt} | 6 +- .../mogo/module/hmi/ui/MoGoWarningFragment.kt | 39 ++- .../module/hmi/ui/widget/ParentFrameLayout.kt | 39 +++ .../WarningNotificationManager.java | 56 ---- 16 files changed, 844 insertions(+), 63 deletions(-) create mode 100644 modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/anim/AnimatorManager.kt create mode 100644 modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/anim/DefaultAnimator.kt create mode 100644 modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/enums/ShowPattern.kt create mode 100644 modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/enums/SidePattern.kt rename modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/{warning => enums}/WarningTypeEnum.kt (98%) create mode 100644 modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/interfaces/OnFloatAnimator.kt create mode 100644 modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/interfaces/OnFloatCallbacks.kt create mode 100644 modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/notification/WarningFloat.kt create mode 100644 modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/notification/WarningFloatWindowHelper.kt create mode 100644 modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/notification/WarningFloatWindowManager.kt create mode 100644 modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/notification/WarningNotificationConfig.kt rename modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/receiver/{TestWarningBroadcastReceiver.kt => WarningBroadcastReceiver.kt} (93%) create mode 100644 modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/ui/widget/ParentFrameLayout.kt delete mode 100644 modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/warning/notification/WarningNotificationManager.java diff --git a/modules/mogo-module-hmi/src/main/AndroidManifest.xml b/modules/mogo-module-hmi/src/main/AndroidManifest.xml index 37adc97a0a..492a8f95d9 100644 --- a/modules/mogo-module-hmi/src/main/AndroidManifest.xml +++ b/modules/mogo-module-hmi/src/main/AndroidManifest.xml @@ -4,7 +4,7 @@ - + diff --git a/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/anim/AnimatorManager.kt b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/anim/AnimatorManager.kt new file mode 100644 index 0000000000..15199d11ed --- /dev/null +++ b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/anim/AnimatorManager.kt @@ -0,0 +1,25 @@ +package com.mogo.module.hmi.anim + +import android.animation.Animator +import android.view.View +import android.view.WindowManager +import com.mogo.module.hmi.notification.WarningNotificationConfig + +/** + * @author: donghongyu + * @function: App浮窗的出入动画管理类,只需传入具体的动画实现类(策略模式) + * @date: 2019-07-22 16:44 + */ +internal class AnimatorManager( + private val view: View, + private val params: WindowManager.LayoutParams, + private val windowManager: WindowManager, + private val config: WarningNotificationConfig +) { + + fun enterAnim(): Animator? = + config.floatAnimator?.enterAnim(view, params, windowManager, config.sidePattern) + + fun exitAnim(): Animator? = + config.floatAnimator?.exitAnim(view, params, windowManager, config.sidePattern) +} \ No newline at end of file diff --git a/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/anim/DefaultAnimator.kt b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/anim/DefaultAnimator.kt new file mode 100644 index 0000000000..01eacd459a --- /dev/null +++ b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/anim/DefaultAnimator.kt @@ -0,0 +1,146 @@ +package com.mogo.module.hmi.anim + +import android.animation.Animator +import android.animation.ValueAnimator +import android.graphics.Rect +import android.view.View +import android.view.WindowManager +import com.mogo.module.hmi.enums.SidePattern +import com.mogo.module.hmi.interfaces.OnFloatAnimator +import com.mogo.utils.WindowUtils +import kotlin.math.min + +/** + * @author: donghongyu + * @function: 系统浮窗的默认效果,选择靠近左右侧的一边进行出入 + * @date: 2019-07-22 17:22 + */ +open class DefaultAnimator : OnFloatAnimator { + + override fun enterAnim( + view: View, + params: WindowManager.LayoutParams, + windowManager: WindowManager, + sidePattern: SidePattern + ): Animator? = getAnimator(view, params, windowManager, sidePattern, false) + + override fun exitAnim( + view: View, + params: WindowManager.LayoutParams, + windowManager: WindowManager, + sidePattern: SidePattern + ): Animator? = getAnimator(view, params, windowManager, sidePattern, true) + + private fun getAnimator( + view: View, + params: WindowManager.LayoutParams, + windowManager: WindowManager, + sidePattern: SidePattern, + isExit: Boolean + ): Animator { + val triple = initValue(view, params, windowManager, sidePattern) + // 退出动画的起始值、终点值,与入场动画相反 + val start = if (isExit) triple.second else triple.first + val end = if (isExit) triple.first else triple.second + return ValueAnimator.ofInt(start, end).apply { + addUpdateListener { + try { + val value = it.animatedValue as Int + if (triple.third) params.x = value else params.y = value + // 动画执行过程中页面关闭,出现异常 + windowManager.updateViewLayout(view, params) + } catch (e: Exception) { + cancel() + } + } + } + } + + /** + * 计算边距,起始坐标等 + */ + private fun initValue( + view: View, + params: WindowManager.LayoutParams, + windowManager: WindowManager, + sidePattern: SidePattern + ): Triple { + val parentRect = Rect() + windowManager.defaultDisplay.getRectSize(parentRect) + // 浮窗各边到窗口边框的距离 + val leftDistance = params.x + val rightDistance = parentRect.right - (leftDistance + view.right) + val topDistance = params.y + val bottomDistance = parentRect.bottom - (topDistance + view.bottom) + // 水平、垂直方向的距离最小值 + val minX = min(leftDistance, rightDistance) + val minY = min(topDistance, bottomDistance) + + val isHorizontal: Boolean + val endValue: Int + val startValue: Int = when (sidePattern) { + SidePattern.LEFT, SidePattern.RESULT_LEFT -> { + // 从左侧到目标位置,右移 + isHorizontal = true + endValue = params.x + -view.right + } + SidePattern.RIGHT, SidePattern.RESULT_RIGHT -> { + // 从右侧到目标位置,左移 + isHorizontal = true + endValue = params.x + parentRect.right + } + SidePattern.TOP, SidePattern.RESULT_TOP -> { + // 从顶部到目标位置,下移 + isHorizontal = false + endValue = params.y + -view.bottom + } + SidePattern.BOTTOM, SidePattern.RESULT_BOTTOM -> { + // 从底部到目标位置,上移 + isHorizontal = false + endValue = params.y + parentRect.bottom + getCompensationHeight(view, params) + } + + SidePattern.DEFAULT, SidePattern.AUTO_HORIZONTAL, SidePattern.RESULT_HORIZONTAL -> { + // 水平位移,哪边距离屏幕近,从哪侧移动 + isHorizontal = true + endValue = params.x + if (leftDistance < rightDistance) -view.right else parentRect.right + } + SidePattern.AUTO_VERTICAL, SidePattern.RESULT_VERTICAL -> { + // 垂直位移,哪边距离屏幕近,从哪侧移动 + isHorizontal = false + endValue = params.y + if (topDistance < bottomDistance) -view.bottom + else parentRect.bottom + getCompensationHeight(view, params) + } + + else -> if (minX <= minY) { + isHorizontal = true + endValue = params.x + if (leftDistance < rightDistance) -view.right else parentRect.right + } else { + isHorizontal = false + endValue = params.y + if (topDistance < bottomDistance) -view.bottom + else parentRect.bottom + getCompensationHeight(view, params) + } + } + return Triple(startValue, endValue, isHorizontal) + } + + /** + * 单页面浮窗(popupWindow),坐标从顶部计算,需要加上状态栏的高度 + */ + private fun getCompensationHeight(view: View, params: WindowManager.LayoutParams): Int { + val location = IntArray(2) + // 获取在整个屏幕内的绝对坐标 + view.getLocationOnScreen(location) + // 绝对高度和相对高度相等,说明是单页面浮窗(popupWindow),计算底部动画时需要加上状态栏高度 + return if (location[1] == params.y) WindowUtils.getStatusBarHeight(view.context.applicationContext) else 0 + } + +} \ No newline at end of file diff --git a/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/enums/ShowPattern.kt b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/enums/ShowPattern.kt new file mode 100644 index 0000000000..ed2165d89a --- /dev/null +++ b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/enums/ShowPattern.kt @@ -0,0 +1,12 @@ +package com.mogo.module.hmi.enums + +/** + * @author: donghongyu + * @function: 浮窗显示类别 + * @date: 2019-07-08 17:05 + */ +enum class ShowPattern { + + // 只在当前Activity显示、仅应用前台时显示、仅应用后台时显示,一直显示(不分前后台) + CURRENT_ACTIVITY, FOREGROUND, BACKGROUND, ALL_TIME +} \ No newline at end of file diff --git a/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/enums/SidePattern.kt b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/enums/SidePattern.kt new file mode 100644 index 0000000000..898fb3dccc --- /dev/null +++ b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/enums/SidePattern.kt @@ -0,0 +1,20 @@ +package com.mogo.module.hmi.enums + +/** + * @author: donghongyu + * @function: 浮窗的贴边模式 + * @date: 2019-07-01 17:34 + */ +enum class SidePattern { + + // 默认不贴边,跟随手指移动 + DEFAULT, + // 左、右、上、下四个方向固定(一直吸附在该方向边缘,只能在该方向的边缘移动) + LEFT, RIGHT, TOP, BOTTOM, + // 根据手指到屏幕边缘的距离,自动选择水平方向的贴边、垂直方向的贴边、四周方向的贴边 + AUTO_HORIZONTAL, AUTO_VERTICAL, AUTO_SIDE, + // 拖拽时跟随手指移动,结束时贴边 + RESULT_LEFT, RESULT_RIGHT, RESULT_TOP, RESULT_BOTTOM, + RESULT_HORIZONTAL, RESULT_VERTICAL, RESULT_SIDE + +} \ No newline at end of file diff --git a/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/warning/WarningTypeEnum.kt b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/enums/WarningTypeEnum.kt similarity index 98% rename from modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/warning/WarningTypeEnum.kt rename to modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/enums/WarningTypeEnum.kt index f30618b99e..e16edc6dfb 100644 --- a/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/warning/WarningTypeEnum.kt +++ b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/enums/WarningTypeEnum.kt @@ -1,4 +1,4 @@ -package com.mogo.module.hmi.warning +package com.mogo.module.hmi.enums /** * author : donghongyu diff --git a/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/interfaces/OnFloatAnimator.kt b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/interfaces/OnFloatAnimator.kt new file mode 100644 index 0000000000..3c62f04e93 --- /dev/null +++ b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/interfaces/OnFloatAnimator.kt @@ -0,0 +1,29 @@ +package com.mogo.module.hmi.interfaces + +import android.animation.Animator +import android.view.View +import android.view.WindowManager +import com.mogo.module.hmi.enums.SidePattern + +/** + * @author: liuzhenfeng + * @function: 系统浮窗的出入动画 + * @date: 2019-07-22 16:40 + */ +interface OnFloatAnimator { + + fun enterAnim( + view: View, + params: WindowManager.LayoutParams, + windowManager: WindowManager, + sidePattern: SidePattern + ): Animator? = null + + fun exitAnim( + view: View, + params: WindowManager.LayoutParams, + windowManager: WindowManager, + sidePattern: SidePattern + ): Animator? = null + +} \ No newline at end of file diff --git a/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/interfaces/OnFloatCallbacks.kt b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/interfaces/OnFloatCallbacks.kt new file mode 100644 index 0000000000..f58f7044f9 --- /dev/null +++ b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/interfaces/OnFloatCallbacks.kt @@ -0,0 +1,42 @@ +package com.mogo.module.hmi.interfaces + +import android.view.MotionEvent +import android.view.View + +/** + * @author: liuzhenfeng + * @function: 浮窗的一些状态回调 + * @date: 2019-07-16 14:11 + */ +interface OnFloatCallbacks { + + /** + * 浮窗的创建结果,是否创建成功 + * + * @param isCreated 是否创建成功 + * @param msg 失败返回的结果 + * @param view 浮窗xml布局 + */ + fun createdResult(isCreated: Boolean, msg: String?, view: View?) + + /** + * 显示 + */ + fun show(view: View) + + /** + * 隐藏 + */ + fun hide(view: View) + + /** + * 关闭 + */ + fun dismiss() + + /** + * 触摸事件的回调 + */ + fun touchEvent(view: View, event: MotionEvent) + +} \ No newline at end of file diff --git a/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/notification/WarningFloat.kt b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/notification/WarningFloat.kt new file mode 100644 index 0000000000..a61c5d3ca3 --- /dev/null +++ b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/notification/WarningFloat.kt @@ -0,0 +1,144 @@ +package com.mogo.module.hmi.notification + +import android.content.Context +import android.view.View +import com.mogo.module.hmi.enums.SidePattern +import com.mogo.module.hmi.interfaces.OnFloatAnimator +import com.mogo.utils.WindowUtils +import com.mogo.utils.logger.Logger + +/** + * @author xiaoyuzhou + * @date 2021/8/3 5:50 下午 + * 预警通知管理 + */ +class WarningFloat { + companion object { + var TAG = "WarningNotificationManager" + + @JvmStatic + fun with(activity: Context): Builder = Builder(activity) + + + /** + * 关闭当前浮窗 + * @param tag 浮窗标签 + * @param force 立即关闭,有退出动画也不执行 + */ + @JvmStatic + @JvmOverloads + fun dismiss(tag: String? = null, force: Boolean = false) = + WarningFloatWindowManager.dismiss(tag, force) + + + } + + /** + * 浮窗的属性构建类,支持链式调用 + */ + class Builder(private val activity: Context) { + // 创建浮窗数据类,方便管理配置 + private val config = WarningNotificationConfig() + + /** + * 设置浮窗的吸附模式 + * @param sidePattern 浮窗吸附模式 + */ + fun setSidePattern(sidePattern: SidePattern) = apply { config.sidePattern = sidePattern } + + + /** + * 设置浮窗的布局文件,以及布局的操作接口 + * @param layoutId 布局文件的资源Id + */ + @JvmOverloads + fun setLayout(layoutId: Int) = apply { + config.layoutId = layoutId + } + + /** + * 设置浮窗的布局视图,以及布局的操作接口 + * @param layoutView 自定义的布局视图 + */ + @JvmOverloads + fun setLayout(layoutView: View) = apply { + config.layoutView = layoutView + } + /** + * 设置浮窗的对齐方式,以及偏移量 + * @param gravity 对齐方式 + * @param offsetX 目标坐标的水平偏移量 + * @param offsetY 目标坐标的竖直偏移量 + */ + @JvmOverloads + fun setGravity(gravity: Int, offsetX: Int = 0, offsetY: Int = 0) = apply { + config.gravity = gravity + config.offsetPair = Pair(offsetX, offsetY) + } + + /** + * 设置浮窗的起始坐标,优先级高于setGravity + * @param x 起始水平坐标 + * @param y 起始竖直坐标 + */ + fun setLocation(x: Int, y: Int) = apply { + config.locationPair = Pair(x, y) + } + + /** + * 设置浮窗的拖拽边距值 + * @param left 浮窗左侧边距 + * @param top 浮窗顶部边距 + * @param right 浮窗右侧边距 + * @param bottom 浮窗底部边距 + */ + @JvmOverloads + fun setBorder( + left: Int = 0, + top: Int = -WindowUtils.getStatusBarHeight(activity), + right: Int = WindowUtils.getScreenWidth(activity), + bottom: Int = WindowUtils.getScreenHeight(activity) + ) = apply { + config.leftBorder = left + config.topBorder = top + config.rightBorder = right + config.bottomBorder = bottom + } + + /** + * 设置浮窗的标签:只有一个浮窗时,可以不设置; + * 有多个浮窗必须设置不容的浮窗,不然没法管理,所以禁止创建相同标签的浮窗 + * @param floatTag 浮窗标签 + */ + fun setTag(floatTag: String?) = apply { + config.floatTag = floatTag + } + + /** + * 设置浮窗是否状态栏沉浸 + * @param immersionStatusBar 是否状态栏沉浸 + */ + fun setImmersionStatusBar(immersionStatusBar: Boolean) = apply { + config.immersionStatusBar = immersionStatusBar + } + + /** + * 设置浮窗的出入动画 + * @param floatAnimator 浮窗的出入动画,为空时不执行动画 + */ + fun setAnimator(floatAnimator: OnFloatAnimator?) = + apply { config.floatAnimator = floatAnimator } + + /** + * 创建浮窗,包括Activity浮窗和系统浮窗,如若系统浮窗无权限,先进行权限申请 + */ + fun show() = when { + // 未设置浮窗布局文件/布局视图,不予创建 + config.layoutId == null && config.layoutView == null -> + Logger.e(TAG, "需要传入 layoutId 或 layoutView ") + // 申请浮窗权限 + else -> WarningFloatWindowManager.create(activity, config) + } + + } +} \ No newline at end of file diff --git a/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/notification/WarningFloatWindowHelper.kt b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/notification/WarningFloatWindowHelper.kt new file mode 100644 index 0000000000..7794ea9a91 --- /dev/null +++ b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/notification/WarningFloatWindowHelper.kt @@ -0,0 +1,251 @@ +package com.mogo.module.hmi.notification + +import android.animation.Animator +import android.annotation.SuppressLint +import android.app.Activity +import android.app.Service +import android.content.Context +import android.graphics.PixelFormat +import android.graphics.Rect +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.WindowManager +import com.mogo.module.hmi.anim.AnimatorManager +import com.mogo.module.hmi.enums.ShowPattern +import com.mogo.module.hmi.ui.widget.ParentFrameLayout +import com.mogo.utils.WindowUtils +import com.mogo.utils.logger.Logger + +/** + * @author xiaoyuzhou + * @date 2021/8/5 5:29 下午 + */ +internal class WarningFloatWindowHelper( + val context: Context, + var config: WarningNotificationConfig +) { + + var TAG = "WarningFloatWindowHelper" + + // 这里采用添加View的方式,配合动画完成弹窗的效果,仅限应用内部弹窗,伴随着Activity后台也会隐藏 + lateinit var windowManager: WindowManager + lateinit var params: WindowManager.LayoutParams + var frameLayout: ParentFrameLayout? = null + private var enterAnimator: Animator? = null + + fun createWindow() { + if (context is Activity) { + initParams() + addView() + config.isShow = true + } else { + Logger.e(TAG, "activity 必须是 Activity 的实现") + } + } + + private fun initParams() { + // 获取 WindowManager + windowManager = context.getSystemService(Service.WINDOW_SERVICE) as WindowManager + params = WindowManager.LayoutParams().apply { + // 设置窗口类型为应用子窗口,和PopupWindow同类型 + type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL + format = PixelFormat.RGBA_8888 + gravity = Gravity.START or Gravity.TOP + // 设置浮窗以外的触摸事件可以传递给后面的窗口、不自动获取焦点 + flags = if (config.immersionStatusBar) + // 没有边界限制,允许窗口扩展到屏幕外 + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + else WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + width = WindowManager.LayoutParams.WRAP_CONTENT + height = WindowManager.LayoutParams.WRAP_CONTENT + + // 如若设置了固定坐标,直接定位 + if (config.locationPair != Pair(0, 0)) { + x = config.locationPair.first + y = config.locationPair.second + } + } + } + + /** + * 设置浮窗的对齐方式,支持上下左右、居中、上中、下中、左中和右中,默认左上角 + * 支持手动设置的偏移量 + */ + @SuppressLint("RtlHardcoded") + private fun setGravity(view: View?) { + if (config.locationPair != Pair(0, 0) || view == null) return + val parentRect = Rect() + // 获取浮窗所在的矩形 + windowManager.defaultDisplay.getRectSize(parentRect) + val location = IntArray(2) + // 获取在整个屏幕内的绝对坐标 + view.getLocationOnScreen(location) + // 通过绝对高度和相对高度比较,判断包含顶部状态栏 + val statusBarHeight = + if (location[1] > params.y) WindowUtils.getStatusBarHeight(view.context.applicationContext) else 0 + val parentBottom = + WindowUtils.getScreenHeight(view.context.applicationContext) - statusBarHeight + when (config.gravity) { + // 右上 + Gravity.END, Gravity.END or Gravity.TOP, Gravity.RIGHT, Gravity.RIGHT or Gravity.TOP -> + params.x = parentRect.right - view.width + // 左下 + Gravity.START or Gravity.BOTTOM, Gravity.BOTTOM, Gravity.LEFT or Gravity.BOTTOM -> + params.y = parentBottom - view.height + // 右下 + Gravity.END or Gravity.BOTTOM, Gravity.RIGHT or Gravity.BOTTOM -> { + params.x = parentRect.right - view.width + params.y = parentBottom - view.height + } + // 居中 + Gravity.CENTER -> { + params.x = (parentRect.right - view.width).shr(1) + params.y = (parentBottom - view.height).shr(1) + } + // 上中 + Gravity.CENTER_HORIZONTAL, Gravity.TOP or Gravity.CENTER_HORIZONTAL -> + params.x = (parentRect.right - view.width).shr(1) + // 下中 + Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL -> { + params.x = (parentRect.right - view.width).shr(1) + params.y = parentBottom - view.height + } + // 左中 + Gravity.CENTER_VERTICAL, Gravity.START or Gravity.CENTER_VERTICAL, Gravity.LEFT or Gravity.CENTER_VERTICAL -> + params.y = (parentBottom - view.height).shr(1) + // 右中 + Gravity.END or Gravity.CENTER_VERTICAL, Gravity.RIGHT or Gravity.CENTER_VERTICAL -> { + params.x = parentRect.right - view.width + params.y = (parentBottom - view.height).shr(1) + } + // 其他情况,均视为左上 + else -> { + } + } + + // 设置偏移量 + params.x += config.offsetPair.first + params.y += config.offsetPair.second + + if (config.immersionStatusBar) { + if (config.showPattern != ShowPattern.CURRENT_ACTIVITY) { + params.y -= statusBarHeight + } + } else { + if (config.showPattern == ShowPattern.CURRENT_ACTIVITY) { + params.y += statusBarHeight + } + } + // 更新浮窗位置信息 + windowManager.updateViewLayout(view, params) + } + + /** + * 添加视图 + */ + private fun addView() { + // 创建一个frameLayout作为浮窗布局的父容器,因为需要做动画,以及管理滑动关闭事件 + frameLayout = ParentFrameLayout(context, config) + frameLayout?.tag = config.floatTag + // 将浮窗布局文件添加到父容器frameLayout中,并返回该浮窗文件 + val floatingView = config.layoutView?.also { frameLayout?.addView(it) } + ?: LayoutInflater.from(context).inflate(config.layoutId!!, frameLayout, true) + // 为了避免创建的时候闪一下,我们先隐藏视图,不能直接设置GONE,否则定位会出现问题 + floatingView.visibility = View.INVISIBLE + // 将frameLayout添加到系统windowManager中 + windowManager.addView(frameLayout, params) + + // 在浮窗绘制完成的时候,设置初始坐标、执行入场动画 + frameLayout?.layoutListener = object : ParentFrameLayout.OnLayoutListener { + override fun onLayout() { + setGravity(frameLayout) + config.apply { + enterAnim(floatingView) + // 设置callbacks + layoutView = floatingView + callbacks?.createdResult(true, null, floatingView) + } + } + } + } + + + /** + * 入场动画 + */ + private fun enterAnim(floatingView: View) { + if (frameLayout == null || config.isAnim) return + enterAnimator = AnimatorManager(frameLayout!!, params, windowManager, config) + .enterAnim()?.apply { + // 可以延伸到屏幕外,动画结束按需去除该属性,不然旋转屏幕可能置于屏幕外部 + params.flags = + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + + addListener(object : Animator.AnimatorListener { + override fun onAnimationRepeat(animation: Animator?) {} + + override fun onAnimationEnd(animation: Animator?) { + config.isAnim = false + if (!config.immersionStatusBar) { + // 不需要延伸到屏幕外了,防止屏幕旋转的时候,浮窗处于屏幕外 + params.flags = + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + } + } + + override fun onAnimationCancel(animation: Animator?) {} + + override fun onAnimationStart(animation: Animator?) { + floatingView.visibility = View.VISIBLE + config.isAnim = true + } + }) + start() + } + if (enterAnimator == null) { + floatingView.visibility = View.VISIBLE + windowManager.updateViewLayout(floatingView, params) + } + } + + /** + * 退出动画 + */ + fun exitAnim() { + if (frameLayout == null || (config.isAnim && enterAnimator == null)) return + enterAnimator?.cancel() + val animator: Animator? = + AnimatorManager(frameLayout!!, params, windowManager, config).exitAnim() + if (animator == null) remove() else { + // 二次判断,防止重复调用引发异常 + if (config.isAnim) return + config.isAnim = true + params.flags = + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + animator.addListener(object : Animator.AnimatorListener { + override fun onAnimationRepeat(animation: Animator?) {} + + override fun onAnimationEnd(animation: Animator?) = remove() + + override fun onAnimationCancel(animation: Animator?) {} + + override fun onAnimationStart(animation: Animator?) {} + }) + animator.start() + } + } + + /** + * 退出动画执行结束/没有退出动画,进行回调、移除等操作 + */ + fun remove(force: Boolean = false) = try { + config.isAnim = false + WarningFloatWindowManager.remove(config.floatTag) + // removeView是异步删除,在Activity销毁的时候会导致窗口泄漏,所以使用removeViewImmediate直接删除view + windowManager.run { if (force) removeViewImmediate(frameLayout) else removeView(frameLayout) } + } catch (e: Exception) { + Logger.e(TAG, "浮窗关闭出现异常:$e") + } + +} \ No newline at end of file diff --git a/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/notification/WarningFloatWindowManager.kt b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/notification/WarningFloatWindowManager.kt new file mode 100644 index 0000000000..6de0c254d0 --- /dev/null +++ b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/notification/WarningFloatWindowManager.kt @@ -0,0 +1,39 @@ +package com.mogo.module.hmi.notification + +import android.content.Context +import android.view.View +import java.util.concurrent.ConcurrentHashMap + +/** + * @author xiaoyuzhou + * @date 2021/8/5 5:17 下午 + */ +internal object WarningFloatWindowManager { + private const val DEFAULT_TAG = "default" + + val windowMap = ConcurrentHashMap() + + fun create(activity: Context, config: WarningNotificationConfig) { + WarningFloatWindowHelper(activity, config).createWindow() + } + + + fun dismiss(tag: String? = null, force: Boolean) { + + } + + /** + * 移除当条浮窗信息,在退出完成后调用 + */ + fun remove(floatTag: String?) = windowMap.remove(getTag(floatTag)) + + /** + * 获取浮窗tag,为空则使用默认值 + */ + private fun getTag(tag: String?) = tag ?: DEFAULT_TAG + + /** + * 获取具体的系统浮窗管理类 + */ + fun getHelper(tag: String?) = windowMap[getTag(tag)] +} \ No newline at end of file diff --git a/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/notification/WarningNotificationConfig.kt b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/notification/WarningNotificationConfig.kt new file mode 100644 index 0000000000..b10f7f70fb --- /dev/null +++ b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/notification/WarningNotificationConfig.kt @@ -0,0 +1,55 @@ +package com.mogo.module.hmi.notification + +import android.view.View +import com.mogo.module.hmi.anim.DefaultAnimator +import com.mogo.module.hmi.enums.ShowPattern +import com.mogo.module.hmi.enums.SidePattern +import com.mogo.module.hmi.interfaces.OnFloatAnimator +import com.mogo.module.hmi.interfaces.OnFloatCallbacks + +/** + * @author xiaoyuzhou + * @date 2021/8/5 3:02 下午 + * 预警通知 配置信息 + */ +data class WarningNotificationConfig( + // 浮窗的xml布局文件 + var layoutId: Int? = null, + var layoutView: View? = null, + + // 当前浮窗的tag + var floatTag: String? = null, + // 是否正在执行动画 + var isAnim: Boolean = false, + // 是否显示 + var isShow: Boolean = false, + // 状态栏沉浸 + var immersionStatusBar: Boolean = false, + + // 浮窗的吸附方式(默认不吸附,拖到哪里是哪里) + var sidePattern: SidePattern = SidePattern.DEFAULT, + + // 浮窗显示类型(默认只在当前页显示) + var showPattern: ShowPattern = ShowPattern.CURRENT_ACTIVITY, + + // 浮窗的摆放方式,使用系统的Gravity属性 + var gravity: Int = 0, + // 坐标的偏移量 + var offsetPair: Pair = Pair(0, 0), + // 固定的初始坐标,左上角坐标 + var locationPair: Pair = Pair(0, 0), + // ps:优先使用固定坐标,若固定坐标不为原点坐标,gravity属性和offset属性无效 + + // 四周边界值 + var leftBorder: Int = 0, + var topBorder: Int = -999, + var rightBorder: Int = 9999, + var bottomBorder: Int = 9999, + + // 出入动画 + var floatAnimator: OnFloatAnimator? = DefaultAnimator(), + + // Callbacks + var callbacks: OnFloatCallbacks? = null, + + ) \ No newline at end of file diff --git a/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/receiver/TestWarningBroadcastReceiver.kt b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/receiver/WarningBroadcastReceiver.kt similarity index 93% rename from modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/receiver/TestWarningBroadcastReceiver.kt rename to modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/receiver/WarningBroadcastReceiver.kt index 434d57384d..bf65894e50 100644 --- a/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/receiver/TestWarningBroadcastReceiver.kt +++ b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/receiver/WarningBroadcastReceiver.kt @@ -5,7 +5,7 @@ import android.content.Context import android.content.Intent import com.alibaba.android.arouter.launcher.ARouter import com.mogo.module.hmi.WaringConst -import com.mogo.module.hmi.warning.WarningTypeEnum +import com.mogo.module.hmi.enums.WarningTypeEnum import com.mogo.service.IMogoServiceApis import com.mogo.service.MogoServicePaths import com.mogo.service.warning.IMoGoWaringProvider @@ -13,11 +13,11 @@ import com.mogo.utils.logger.Logger import kotlin.random.Random /** - * V2X 测试面板广播接收,目的是可以通过广播调用起来面板 + * V2X 预警广播接收。用于跨应用,跨进程,内部也可以通过这种方式弹出预警提示框 * * @author donghongyu */ -class TestWarningBroadcastReceiver : BroadcastReceiver() { +class WarningBroadcastReceiver : BroadcastReceiver() { private var mContext: Context? = null override fun onReceive(context: Context, intent: Intent) { try { diff --git a/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/ui/MoGoWarningFragment.kt b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/ui/MoGoWarningFragment.kt index 28c051610b..3f06dffff8 100644 --- a/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/ui/MoGoWarningFragment.kt +++ b/modules/mogo-module-hmi/src/main/java/com/mogo/module/hmi/ui/MoGoWarningFragment.kt @@ -1,8 +1,15 @@ package com.mogo.module.hmi.ui +import android.animation.Animator +import android.view.Gravity +import android.view.View +import android.view.WindowManager +import android.view.animation.OvershootInterpolator import com.mogo.commons.mvp.MvpFragment import com.mogo.module.hmi.R -import com.mogo.module.hmi.warning.notification.WarningNotificationManager +import com.mogo.module.hmi.anim.DefaultAnimator +import com.mogo.module.hmi.enums.SidePattern +import com.mogo.module.hmi.notification.WarningFloat /** * @author xiaoyuzhou @@ -23,7 +30,35 @@ class MoGoWarningFragment : MvpFragment