完成封装弹窗框架
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
|
||||
<application>
|
||||
|
||||
<receiver android:name=".receiver.TestWarningBroadcastReceiver">
|
||||
<receiver android:name=".receiver.WarningBroadcastReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="com.v2x.control" />
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.mogo.module.hmi.warning
|
||||
package com.mogo.module.hmi.enums
|
||||
|
||||
/**
|
||||
* author : donghongyu
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)]
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
)
|
||||
@@ -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 {
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user