完成封装弹窗框架

This commit is contained in:
董宏宇
2021-08-05 20:49:36 +08:00
parent 0f49a9ab9f
commit 43324d926d
16 changed files with 844 additions and 63 deletions

View File

@@ -4,7 +4,7 @@
<application>
<receiver android:name=".receiver.TestWarningBroadcastReceiver">
<receiver android:name=".receiver.WarningBroadcastReceiver">
<intent-filter>
<action android:name="com.v2x.control" />

View File

@@ -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)
}

View File

@@ -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<Int, Int, Boolean> {
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
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -1,4 +1,4 @@
package com.mogo.module.hmi.warning
package com.mogo.module.hmi.enums
/**
* author : donghongyu

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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)
}
}
}

View File

@@ -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")
}
}

View File

@@ -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<String, View>()
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)]
}

View File

@@ -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<Int, Int> = Pair(0, 0),
// 固定的初始坐标,左上角坐标
var locationPair: Pair<Int, Int> = 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,
)

View File

@@ -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 {

View File

@@ -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<MoGoWarningContract.View?, WaringPresent
}
override fun showWarningV2X(v2xType: Int, alertMessage: String, tag: String) {
WarningNotificationManager.show(activity)
activity?.let {
WarningFloat.with(it)
.setTag(tag)
.setLayout(R.layout.notification_v2x_msg_vr)
.setSidePattern(SidePattern.TOP)
.setGravity(Gravity.CENTER_HORIZONTAL, offsetY = 110)
.setAnimator(object : DefaultAnimator() {
override fun enterAnim(
view: View,
params: WindowManager.LayoutParams,
windowManager: WindowManager,
sidePattern: SidePattern
): Animator? =
super.enterAnim(view, params, windowManager, sidePattern)?.apply {
interpolator = OvershootInterpolator()
}
override fun exitAnim(
view: View,
params: WindowManager.LayoutParams,
windowManager: WindowManager,
sidePattern: SidePattern
): Animator? =
super.exitAnim(view, params, windowManager, sidePattern)?.setDuration(200)
})
.show()
}
}
override fun disableWarningV2X(tag: String) {

View File

@@ -0,0 +1,39 @@
package com.mogo.module.hmi.ui.widget
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
import com.mogo.module.hmi.notification.WarningNotificationConfig
/**
* @author: liuzhenfeng
* @function: 系统浮窗的父布局对touch事件进行了重新分发
* @date: 2019-07-10 14:16
*/
@SuppressLint("ViewConstructor")
internal class ParentFrameLayout(
context: Context,
private val config: WarningNotificationConfig,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
var layoutListener: OnLayoutListener? = null
private var isCreated = false
// 布局绘制完成的接口用于通知外部做一些View操作不然无法获取view宽高
interface OnLayoutListener {
fun onLayout()
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
// 初次绘制完成的时候,需要设置对齐方式、坐标偏移量、入场动画
if (!isCreated) {
isCreated = true
layoutListener?.onLayout()
}
}
}

View File

@@ -1,56 +0,0 @@
package com.mogo.module.hmi.warning.notification;
import android.app.Service;
import android.content.Context;
import android.graphics.PixelFormat;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import com.mogo.module.hmi.R;
/**
* @author xiaoyuzhou
* @date 2021/8/3 5:50 下午
* 通知管理
*/
public class WarningNotificationManager {
// 这里采用添加View的方式,配合动画完成弹窗的效果,仅限应用内部弹窗,伴随着Activity后台也会隐藏
private static WindowManager mWindowManager;
private static WindowManager.LayoutParams mLayoutParams;
private static View mView;
public static void show(Context context) {
// 获取 WindowManager
mWindowManager = (WindowManager) context.getSystemService(Service.WINDOW_SERVICE);
mLayoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
0, 0,
PixelFormat.TRANSPARENT);
// flag 设置 Window 属性,悬浮窗的行为,比如说不可聚焦,非模态对话框等等
mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
// type 设置 Window 类别(层级),悬浮窗的类型一般设为2002表示在所有应用程序之上但在状态栏之下
mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
// 悬浮窗的对齐方式
mLayoutParams.gravity = Gravity.TOP;
//
mLayoutParams.format = PixelFormat.RGBA_8888;
// 悬浮窗X的位置
mLayoutParams.x = 0;
//悬浮窗Y的位置此时需要减去statusBar的高度
mLayoutParams.y = 110;
//添加动画
mLayoutParams.windowAnimations = R.style.notice_dialog_anim_bottom2top;
if (mView != null) {
mWindowManager.removeView(mView);
}
mView = LayoutInflater.from(context).inflate(R.layout.notification_v2x_msg_vr, null, false);
mWindowManager.addView(mView, mLayoutParams);
}
}