抽离hmi到core

Signed-off-by: 董宏宇 <martindhy@gmail.com>
This commit is contained in:
董宏宇
2021-09-17 15:34:51 +08:00
parent dc2c292163
commit 36335d28c0
47 changed files with 121 additions and 127 deletions

View File

@@ -43,9 +43,20 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation rootProject.ext.dependencies.kotlinstdlibjdk7
implementation rootProject.ext.dependencies.androidxccorektx
implementation rootProject.ext.dependencies.androidxappcompat
implementation rootProject.ext.dependencies.androidxconstraintlayout
implementation rootProject.ext.dependencies.arouter
implementation rootProject.ext.dependencies.rxandroid
kapt rootProject.ext.dependencies.aroutercompiler
if (Boolean.valueOf(RELEASE)) {
} else {
api project(':services:mogo-service-api')
implementation project(':modules:mogo-module-common')
implementation project(':core:mogo-core-data')
implementation project(':core:mogo-core-utils')
implementation project(':core:mogo-core-function-api')

View File

@@ -1,3 +1,3 @@
GROUP=com.mogo.eagle.core
POM_ARTIFACT_ID=function-impl
POM_ARTIFACT_ID=function-hmi
VERSION_CODE=1

View File

@@ -2,4 +2,30 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mogo.eagle.core.function.hmi">
<application>
<receiver android:name="com.mogo.eagle.core.function.hmi.receiver.V2XWarningBroadcastReceiver">
<intent-filter>
<action android:name="com.hmi.v2x.notification" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver android:name="com.mogo.eagle.core.function.hmi.receiver.V2XTrafficLightBroadcastReceiver">
<intent-filter>
<action android:name="com.hmi.v2x.trafficlight" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver android:name="com.mogo.eagle.core.function.hmi.receiver.V2XLimitingVelocityBroadcastReceiver">
<intent-filter>
<action android:name="com.hmi.v2x.limitingvelocity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
</application>
</manifest>

View File

@@ -0,0 +1,34 @@
package com.mogo.eagle.core.function.hmi;
/**
* @author xiaoyuzhou
* @date 2021/8/3 4:26 下午
*/
public class WaringConst {
public static String MODULE_NAME = "MODULE_LEFT_PANEL";
// V2X 弹窗预警
// 是否展示true-展示false-关闭
public static String BROADCAST_V2X_IS_SHOW_EXTRA_KEY = "v2xIsShow";
// 预警类型:@EventTypeEnum
public static String BROADCAST_V2X_TYPE_EXTRA_KEY = "v2xType";
// 预警弹窗文字内容
public static String BROADCAST_V2X_ALERT_CONTENT_EXTRA_KEY = "alertContent";
// 预警弹窗TTS播报
public static String BROADCAST_V2X_TTS_CONTENT_EXTRA_KEY = "ttsContent";
// 弹窗标志,一个弹窗绑定一个标志,不可重复
public static String BROADCAST_V2X_TAG_EXTRA_KEY = "tag";
// 交通的灯
// 是否展示true-展示false-关闭
public static String BROADCAST_V2X_TRAFFIC_LIGHT_IS_SHOW__EXTRA_KEY = "trafficLightIsShow";
// 选中的交通等类型red、yellow、green
public static String BROADCAST_V2X_TRAFFIC_LIGHT_CHECK_TYPE_EXTRA_KEY = "trafficLightCheckType";
// 限速标志
// 是否展示true-展示false-关闭
public static String BROADCAST_V2X_LIMITING_VELOCITY_IS_SHOW__EXTRA_KEY = "limitingVelocityIsShow";
// 限速的速度
public static String BROADCAST_V2X_LIMITING_VELOCITY_SPEED__EXTRA_KEY = "limitingVelocitySpeed";
}

View File

@@ -0,0 +1,164 @@
package com.mogo.eagle.core.function.hmi.notification
import android.content.Context
import android.view.View
import com.mogo.eagle.core.function.hmi.notification.enums.SidePattern
import com.mogo.eagle.core.function.hmi.notification.interfaces.OnFloatAnimator
import com.mogo.eagle.core.function.api.hmi.warning.WarningStatusListener
import com.mogo.utils.WindowUtils
import com.mogo.utils.logger.Logger
/**
* @author donghongyu
* @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) {
// 创建浮窗数据类,方便管理配置
val config = WarningNotificationConfig()
/**
* 设置浮窗的吸附模式
* @param sidePattern 浮窗吸附模式
*/
fun setSidePattern(sidePattern: SidePattern) = apply { config.sidePattern = sidePattern }
/**
* 设置倒计时关闭
* @param countDownTime 倒计时时间
*/
fun setCountDownTime(countDownTime: Long) = apply { config.countDownTime = countDownTime }
/**
* 设置浮窗的布局文件,以及布局的操作接口
* @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 }
/**
* 设置视图状态监听showdismiss
* @param listener 设置视图状态监听
*/
fun addWarningStatusListener(listener: WarningStatusListener?) =
apply {
if (listener != null) {
config.statusListenerMap.add(listener)
}
}
/**
* 创建浮窗包括Activity浮窗和系统浮窗如若系统浮窗无权限先进行权限申请
*/
fun show() = apply {
when {
// 未设置浮窗布局文件/布局视图,不予创建
config.layoutId == null && config.layoutView == null ->
Logger.e(TAG, "需要传入 layoutId 或 layoutView ")
// 申请浮窗权限
else -> WarningFloatWindowManager.create(activity, config)
}
}
}
}

View File

@@ -0,0 +1,292 @@
package com.mogo.eagle.core.function.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.util.Log
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
import com.mogo.eagle.core.function.hmi.notification.anim.AnimatorManager
import com.mogo.eagle.core.function.hmi.notification.enums.ShowPattern
import com.mogo.eagle.core.function.hmi.notification.widget.ParentFrameLayout
import com.mogo.utils.WindowUtils
import com.mogo.utils.logger.Logger
/**
* @author donghongyu
* @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
private val closeWarningTask: Runnable = Runnable {
exitAnim()
}
fun createWindow(): Boolean {
return if (context is Activity) {
initParams()
addView()
config.isShow = true
true
} else {
Logger.e(TAG, "activity 必须是 Activity 的实现")
false
}
}
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
}
}
}
/**
* 添加视图
*/
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)
frameLayout?.postDelayed({
config.apply {
enterAnim(floatingView)
// 设置callbacks
layoutView = floatingView
callbacks?.createdResult(true, null, floatingView)
}
}, 100)
}
}
// 设置触摸监听,直接关掉弹窗
frameLayout?.setOnTouchListener { _, _ ->
exitAnim()
false
}
// 设置倒计时指定秒数后自动关闭Window
if (config.countDownTime > 0) {
frameLayout?.postDelayed(closeWarningTask, config.countDownTime)
}
}
/**
* 重置倒计时
*/
fun resetDownTime() {
// 设置倒计时指定秒数后自动关闭Window
if (config.countDownTime > 0) {
Log.d(TAG, "重置弹窗时常")
frameLayout?.removeCallbacks(closeWarningTask)
frameLayout?.postDelayed(closeWarningTask, config.countDownTime)
}
}
/**
* 入场动画
*/
private fun enterAnim(floatingView: View) {
config.statusListenerMap.forEach { listener ->
listener.onShow()
}
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() {
config.statusListenerMap.forEach { listener ->
listener.onDismiss()
}
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 {
frameLayout?.removeCallbacks(closeWarningTask)
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")
}
/**
* 设置浮窗的对齐方式,支持上下左右、居中、上中、下中、左中和右中,默认左上角
* 支持手动设置的偏移量
*/
@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)
}
}

View File

@@ -0,0 +1,62 @@
package com.mogo.eagle.core.function.hmi.notification
import android.content.Context
import android.util.Log
import java.util.concurrent.ConcurrentHashMap
/**
* @author xiaoyuzhou
* @date 2021/8/5 5:17 下午
*/
internal object WarningFloatWindowManager {
private const val TAG = "WarningFloat"
private const val DEFAULT_TAG = "default"
private val windowMap = ConcurrentHashMap<String, WarningFloatWindowHelper>()
/**
* 创建浮窗tag不存在创建tag存在创建失败
* 创建结果通过tag添加到相应的map进行管理
*/
fun create(context: Context, config: WarningNotificationConfig) {
if (!checkTag(config)) {
val helper = WarningFloatWindowHelper(context, config)
if (helper.createWindow()) windowMap[config.floatTag!!] = helper
} else {
Log.w(TAG, "存在相同的tag延长弹窗时间")
// 存在相同的tag直接创建失败
config.callbacks?.createdResult(false, "存在相同的tag延长弹窗时间", null)
windowMap[config.floatTag!!]?.resetDownTime()
}
}
/**
* 关闭浮窗,执行浮窗的退出动画
*/
fun dismiss(tag: String? = null, force: Boolean = false) =
getHelper(tag)?.run { if (force) remove(force) else exitAnim() }
/**
* 移除当条浮窗信息,在退出完成后调用
*/
fun remove(floatTag: String?) = windowMap.remove(getTag(floatTag))
/**
* 获取浮窗tag为空则使用默认值
*/
private fun getTag(tag: String?) = tag ?: DEFAULT_TAG
/**
* 检测浮窗的tag是否有效不同的浮窗必须设置不同的tag
*/
private fun checkTag(config: WarningNotificationConfig): Boolean {
// 如果未设置tag设置默认tag
config.floatTag = getTag(config.floatTag)
return windowMap.containsKey(config.floatTag!!)
}
/**
* 获取具体的系统浮窗管理类
*/
fun getHelper(tag: String?) = windowMap[getTag(tag)]
}

View File

@@ -0,0 +1,62 @@
package com.mogo.eagle.core.function.hmi.notification
import android.view.View
import com.mogo.eagle.core.function.hmi.notification.anim.DefaultAnimator
import com.mogo.eagle.core.function.hmi.notification.enums.ShowPattern
import com.mogo.eagle.core.function.hmi.notification.enums.SidePattern
import com.mogo.eagle.core.function.hmi.notification.interfaces.OnFloatAnimator
import com.mogo.eagle.core.function.hmi.notification.interfaces.OnFloatCallbacks
import com.mogo.eagle.core.function.api.hmi.warning.WarningStatusListener
/**
* @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,
// 倒计时关闭window-1 表示不自动关闭, 单位毫秒。1s = 1000ms
var countDownTime: Long = 0,
// 浮窗的摆放方式使用系统的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(),
// 设置视图状态监听showdismiss
var statusListenerMap: ArrayList< WarningStatusListener> = ArrayList(),
// Callbacks
var callbacks: OnFloatCallbacks? = null,
)

View File

@@ -0,0 +1,25 @@
package com.mogo.eagle.core.function.hmi.notification.anim
import android.animation.Animator
import android.view.View
import android.view.WindowManager
import com.mogo.eagle.core.function.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.eagle.core.function.hmi.notification.anim
import android.animation.Animator
import android.animation.ValueAnimator
import android.graphics.Rect
import android.view.View
import android.view.WindowManager
import com.mogo.eagle.core.function.hmi.notification.enums.SidePattern
import com.mogo.eagle.core.function.hmi.notification.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.eagle.core.function.hmi.notification.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.eagle.core.function.hmi.notification.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

@@ -0,0 +1,29 @@
package com.mogo.eagle.core.function.hmi.notification.interfaces
import android.animation.Animator
import android.view.View
import android.view.WindowManager
import com.mogo.eagle.core.function.hmi.notification.enums.SidePattern
/**
* @author: donghongyu
* @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.eagle.core.function.hmi.notification.interfaces
import android.view.MotionEvent
import android.view.View
/**
* @author: donghongyu
* @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,39 @@
package com.mogo.eagle.core.function.hmi.notification.widget
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
import com.mogo.eagle.core.function.hmi.notification.WarningNotificationConfig
/**
* @author: donghongyu
* @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

@@ -0,0 +1,76 @@
package com.mogo.eagle.core.function.hmi.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.alibaba.android.arouter.launcher.ARouter
import com.mogo.eagle.core.function.hmi.WaringConst
import com.mogo.service.IMogoServiceApis
import com.mogo.service.MogoServicePaths
import com.mogo.eagle.core.function.api.hmi.warning.IMoGoWaringProvider
import com.mogo.utils.logger.Logger
/**
* V2X 预警广播接收。用于跨应用,跨进程,内部也可以通过这种方式 控制限速标志
*
* @author donghongyu
*/
class V2XLimitingVelocityBroadcastReceiver : BroadcastReceiver() {
private var mContext: Context? = null
companion object {
private const val TAG = "V2XLimitingVelocityBroadcastReceiver"
private var mMogoServiceApis: IMogoServiceApis? = null
private var mIMoGoWaringProvider: IMoGoWaringProvider? = null
}
override fun onReceive(context: Context, intent: Intent) {
try {
mMogoServiceApis = ARouter.getInstance().build(MogoServicePaths.PATH_SERVICE_APIS)
.navigation(context) as IMogoServiceApis
mIMoGoWaringProvider = mMogoServiceApis!!.waringProviderApi
mContext = context
val limitingVelocityIsShow =
intent.getBooleanExtra(
WaringConst.BROADCAST_V2X_LIMITING_VELOCITY_IS_SHOW__EXTRA_KEY,
false
)
val limitingVelocitySpeed =
intent.getIntExtra(
WaringConst.BROADCAST_V2X_LIMITING_VELOCITY_SPEED__EXTRA_KEY,
0
)
Logger.d(
TAG,
"limitingVelocityIsShow:$limitingVelocityIsShow limitingVelocitySpeed:$limitingVelocitySpeed"
)
if (limitingVelocityIsShow) {
// 分发场景
dispatchShowWaring(limitingVelocitySpeed)
} else {
dispatchCloseWaring()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 展示限速标志
*
* @param limitingVelocitySpeed 限速速度
*/
private fun dispatchShowWaring(limitingVelocitySpeed: Int) {
mIMoGoWaringProvider!!.showLimitingVelocity(limitingVelocitySpeed)
}
/**
* 关闭限速标志
*/
private fun dispatchCloseWaring() {
mIMoGoWaringProvider!!.disableLimitingVelocity()
}
}

View File

@@ -0,0 +1,76 @@
package com.mogo.eagle.core.function.hmi.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.alibaba.android.arouter.launcher.ARouter
import com.mogo.eagle.core.function.hmi.WaringConst
import com.mogo.service.IMogoServiceApis
import com.mogo.service.MogoServicePaths
import com.mogo.eagle.core.function.api.hmi.warning.IMoGoWaringProvider
import com.mogo.utils.logger.Logger
/**
* V2X 预警广播接收。用于跨应用,跨进程,内部也可以通过这种方式 触发红绿灯场景
*
* @author donghongyu
*/
class V2XTrafficLightBroadcastReceiver : BroadcastReceiver() {
private var mContext: Context? = null
companion object {
private const val TAG = "V2XTrafficLightBroadcastReceiver"
private var mMogoServiceApis: IMogoServiceApis? = null
private var mIMoGoWaringProvider: IMoGoWaringProvider? = null
}
override fun onReceive(context: Context, intent: Intent) {
try {
mMogoServiceApis = ARouter.getInstance().build(MogoServicePaths.PATH_SERVICE_APIS)
.navigation(context) as IMogoServiceApis
mIMoGoWaringProvider = mMogoServiceApis!!.waringProviderApi
mContext = context
val trafficLightIsShow =
intent.getBooleanExtra(
WaringConst.BROADCAST_V2X_TRAFFIC_LIGHT_IS_SHOW__EXTRA_KEY,
false
)
val trafficLightCheckType =
intent.getIntExtra(
WaringConst.BROADCAST_V2X_TRAFFIC_LIGHT_CHECK_TYPE_EXTRA_KEY,
0
)
Logger.d(
TAG,
"trafficLightIsShow:$trafficLightIsShow trafficLightCheckType:$trafficLightCheckType"
)
if (trafficLightIsShow) {
// 分发场景
dispatchShowWaring(trafficLightCheckType)
} else {
dispatchCloseWaring()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 展示交通灯
*
* @param trafficLightCheckType 选中的交通的灯 0-都是默认1-红2-黄3-绿
*/
private fun dispatchShowWaring(trafficLightCheckType: Int) {
mIMoGoWaringProvider!!.showWarningTrafficLight(trafficLightCheckType)
}
/**
* 关闭交通灯
*/
private fun dispatchCloseWaring() {
mIMoGoWaringProvider!!.disableWarningTrafficLight()
}
}

View File

@@ -0,0 +1,93 @@
package com.mogo.eagle.core.function.hmi.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.alibaba.android.arouter.launcher.ARouter
import com.mogo.module.common.enums.EventTypeEnum
import com.mogo.eagle.core.function.hmi.WaringConst
import com.mogo.service.IMogoServiceApis
import com.mogo.service.MogoServicePaths
import com.mogo.eagle.core.function.api.hmi.warning.IMoGoWaringProvider
import com.mogo.utils.logger.Logger
/**
* V2X 预警广播接收。用于跨应用,跨进程,内部也可以通过这种方式弹出预警提示框
*
* @author donghongyu
*/
class V2XWarningBroadcastReceiver : BroadcastReceiver() {
private var mContext: Context? = null
companion object {
private const val TAG = "V2XWarningBroadcastReceiver"
private var mMogoServiceApis: IMogoServiceApis? = null
private var mIMoGoWaringProvider: IMoGoWaringProvider? = null
}
override fun onReceive(context: Context, intent: Intent) {
try {
mMogoServiceApis = ARouter.getInstance().build(MogoServicePaths.PATH_SERVICE_APIS)
.navigation(context) as IMogoServiceApis
mIMoGoWaringProvider = mMogoServiceApis!!.waringProviderApi
mContext = context
val v2xIsShow =
intent.getBooleanExtra(WaringConst.BROADCAST_V2X_IS_SHOW_EXTRA_KEY, false)
val v2xType =
intent.getIntExtra(WaringConst.BROADCAST_V2X_TYPE_EXTRA_KEY, 0)
val alertContent =
intent.getStringExtra(WaringConst.BROADCAST_V2X_ALERT_CONTENT_EXTRA_KEY)
val ttsContent =
intent.getStringExtra(WaringConst.BROADCAST_V2X_TTS_CONTENT_EXTRA_KEY)
val tag =
intent.getStringExtra(WaringConst.BROADCAST_V2X_TAG_EXTRA_KEY)
Logger.d(
TAG,
"v2xType:$v2xType alertContent:$alertContent ttsContent:$ttsContent tag:$tag"
)
if (v2xIsShow) {
// 分发场景
dispatchShowWaring(v2xType, alertContent, ttsContent, tag)
} else {
dispatchCloseWaring(tag)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 分发处理关闭场景
*/
private fun dispatchCloseWaring(tag: String?) {
mIMoGoWaringProvider!!.disableWarningV2X(
tag
)
}
/**
* 分发处理展示场景
*
* @param v2xType V2X类型
* @param alertContent 提醒文本
* @param ttsContent tts语音播报消息
* @param tag tag绑定弹窗的标志
*/
private fun dispatchShowWaring(
v2xType: Int,
alertContent: String?,
ttsContent: String?,
tag: String?
) {
if (EventTypeEnum.TYPE_USECASE_ID_IVP.poiType == v2xType.toString()) {
mIMoGoWaringProvider!!.showLimitingVelocity(1)
}
mIMoGoWaringProvider!!.showWarningV2X(
v2xType,
alertContent,
ttsContent,
tag,
null
)
}
}

View File

@@ -0,0 +1,99 @@
package com.mogo.eagle.core.function.hmi.ui
import com.mogo.commons.mvp.IView
import com.mogo.eagle.core.data.enums.WarningDirectionEnum
import com.mogo.eagle.core.function.api.hmi.warning.WarningStatusListener
/**
*@author xiaoyuzhou
*@date 2021/8/4 3:38 下午
*/
interface MoGoWarningContract {
interface View : IView {
/**
* 展示VR下V2X预警
*
* @param v2xType V2X类型
* @param alertContent 提醒文本
* @param ttsContent tts语音播报消息
* @param tag tag绑定弹窗的标志
*/
fun showWarningV2X(
v2xType: Int,
alertContent: String?,
ttsContent: String?,
tag: String?,
listener: WarningStatusListener?
)
/**
* 关闭指定floatTag 的 VR下V2X预警弹窗
* @param tag 弹窗标识
*/
fun disableWarningV2X(tag: String)
/**
* 展示红绿灯预警
*
* @param checkLightId 0-都是默认1-红2-黄3-绿
*/
fun showWarningTrafficLight(checkLightId: Int)
/**
* 关闭红绿灯预警
*/
fun disableWarningTrafficLight()
/**
* 修改红灯倒计时
*/
fun changeCountdownRed(redNum: Int)
/**
* 修改黄灯倒计时
*/
fun changeCountdownYellow(yellowNum: Int)
/**
* 修改绿灯倒计时
*/
fun changeCountdownGreen(greenNum: Int)
/**
* @param readNum 红灯倒计时
* @param yellowNum 黄灯倒计时
* @param greenNum 绿灯倒计时
*/
fun changeCountdownTrafficLightNum(readNum: Int, yellowNum: Int, greenNum: Int)
/**
* 展示限速预警
*
* @param limitingSpeed 限速速度
*/
fun showLimitingVelocity(limitingSpeed: Int)
/**
* 关闭限速预警
*/
fun disableLimitingVelocity()
/**
* 展示指定方位上的红框预警
* @param direction
* @see WarningDirectionEnum
*/
fun showWarning(direction: WarningDirectionEnum)
/**
* 展示指定方位上的红框预警
* @param direction
* @see WarningDirectionEnum
* @param closeTime 倒计时
*/
fun showWarning(direction: WarningDirectionEnum, closeTime: Long)
}
}

View File

@@ -0,0 +1,205 @@
package com.mogo.eagle.core.function.hmi.ui
import android.animation.Animator
import android.text.TextUtils
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.commons.voice.AIAssist
import com.mogo.module.common.enums.EventTypeEnum
import com.mogo.eagle.core.data.enums.WarningDirectionEnum
import com.mogo.eagle.core.function.hmi.notification.WarningFloat
import com.mogo.eagle.core.function.hmi.notification.anim.DefaultAnimator
import com.mogo.eagle.core.function.hmi.notification.enums.SidePattern
import com.mogo.eagle.core.function.hmi.ui.widget.V2XNotificationView
import com.mogo.eagle.core.function.api.hmi.warning.WarningStatusListener
import com.mogo.eagle.core.function.hmi.R
import com.mogo.utils.logger.Logger
import kotlinx.android.synthetic.main.fragment_warning.*
/**
* @author xiaoyuzhou
* @date 2021/8/3 2:40 下午
* 预警图层
*/
class MoGoWarningFragment : MvpFragment<MoGoWarningContract.View?, WaringPresenter?>(),
MoGoWarningContract.View {
var mWarningFloat: WarningFloat.Builder? = null
override fun initViews() {}
override fun getLayoutId(): Int {
return R.layout.fragment_warning
}
override fun createPresenter(): WaringPresenter {
return WaringPresenter(this)
}
/**
* 展示VR下V2X预警
*
* @param v2xType V2X类型
* @param alertContent 提醒文本
* @param ttsContent tts语音播报消息
* @param tag tag绑定弹窗的标志
*/
override fun showWarningV2X(
v2xType: Int,
alertContent: String?,
ttsContent: String?,
tag: String?,
listener: WarningStatusListener?
) {
activity?.let {
val notificationView = V2XNotificationView(it)
notificationView.setWarningIcon(EventTypeEnum.getWarningIcon(v2xType.toString()))
notificationView.setWarningContent(
alertContent ?: EventTypeEnum.getWarningContent(
v2xType.toString()
)
)
if (mWarningFloat != null && mWarningFloat!!.config.floatTag != tag) {
WarningFloat.dismiss(mWarningFloat!!.config.floatTag, true)
}
mWarningFloat = WarningFloat.with(it)
.setTag(tag)
.setLayout(notificationView)
.setSidePattern(SidePattern.TOP)
.setCountDownTime(10000)
.setGravity(Gravity.CENTER_HORIZONTAL, offsetY = 110)
.setImmersionStatusBar(true)
.addWarningStatusListener(listener)
.addWarningStatusListener(object : WarningStatusListener {
override fun onShow() {
// 创建弹窗成功才进行TTS播报
Logger.d(
"MoGoWarningFragment",
"mWarningFloat = $mWarningFloat---ttsContent = $ttsContent"
)
if (mWarningFloat != null && !TextUtils.isEmpty(ttsContent)) {
Logger.d("MoGoWarningFragment", "---> ttsContent = $ttsContent")
AIAssist.getInstance(activity)
.speakTTSVoice(ttsContent)
}
}
})
.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()
}
}
/**
* 关闭指定floatTag 的 VR下V2X预警弹窗
* @param tag 弹窗标识
*/
override fun disableWarningV2X(tag: String) {
activity?.let {
WarningFloat.dismiss(tag)
}
}
/**
* 展示红绿灯预警
*
* @param checkLightId 0-都是默认1-红2-黄3-绿
*/
override fun showWarningTrafficLight(checkLightId: Int) {
viewTrafficLightVr.showWarningTrafficLight(checkLightId)
}
/**
* 关闭红绿灯预警展示,并重制灯态
*/
override fun disableWarningTrafficLight() {
viewTrafficLightVr.disableWarningTrafficLight()
}
override fun changeCountdownRed(redNum: Int) {
viewTrafficLightVr.changeCountdownRed(redNum)
}
override fun changeCountdownYellow(yellowNum: Int) {
viewTrafficLightVr.changeCountdownYellow(yellowNum)
}
override fun changeCountdownGreen(greenNum: Int) {
viewTrafficLightVr.changeCountdownGreen(greenNum)
}
/**
* @param readNum 红灯倒计时
* @param yellowNum 黄灯倒计时
* @param greenNum 绿灯倒计时
*/
override fun changeCountdownTrafficLightNum(readNum: Int, yellowNum: Int, greenNum: Int) {
viewTrafficLightVr.changeCountdownTrafficLightNum(readNum, yellowNum, greenNum)
}
/**
* 控制展示限速标志及内容
*/
override fun showLimitingVelocity(limitingSpeed: Int) {
if (limitingSpeed > 0) {
tvLimitingVelocity.visibility = View.VISIBLE
tvLimitingVelocity.text = "$limitingSpeed"
} else {
tvLimitingVelocity.visibility = View.INVISIBLE
tvLimitingVelocity.text = "0"
}
}
/**
* 控制关闭限速标志及内容
*/
override fun disableLimitingVelocity() {
tvLimitingVelocity.visibility = View.GONE
tvLimitingVelocity.text = "0"
}
/**
* 展示指定方位上的红框预警
* @param direction
* @see WarningDirectionEnum
*/
override fun showWarning(direction: WarningDirectionEnum) {
flV2XWarningView.showWarning(direction)
}
/**
* 展示指定方位上的红框预警
* @param direction
* @see WarningDirectionEnum
* @param closeTime 倒计时
*/
override fun showWarning(direction: WarningDirectionEnum, closeTime: Long) {
flV2XWarningView.showWarning(direction, closeTime)
}
}

View File

@@ -0,0 +1,13 @@
package com.mogo.eagle.core.function.hmi.ui
import com.mogo.commons.mvp.Presenter
/**
* @author xiaoyuzhou
* @date 2021/8/3 3:55 下午
*/
class WaringPresenter(view: MoGoWarningContract.View?) :
Presenter<MoGoWarningContract.View?>(view) {
}

View File

@@ -0,0 +1,184 @@
package com.mogo.eagle.core.function.hmi.ui.widget;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import com.mogo.eagle.core.function.hmi.R;
/**
* created by wujifei on 2021/3/24 16:20
* describe:
*/
public class SpeedChartView extends View {
//中心的文字描述
private String mDes = "KM/H";
//根据数据显示的圆弧Paint
private Paint mArcPaint;
//圆弧颜色
private int mArcColor;
//圆弧的画笔的宽度
private float mStrokeWith = getResources().getDimension(R.dimen.module_ext_arcView_stroke_with);
//文字描述的paint
private Paint mTextPaint;
//当前进度夹角大小
private float mIncludedAngle = 0;
//当前数据
private int currentValue;
//最大数据
private int maxValue = 240;
//圆弧背景的开始和结束间的夹角大小
private float mAngle = 270;
//上次绘制圆弧夹角
private float lastAngle = 0;
public SpeedChartView(Context context) {
this(context, null);
}
public SpeedChartView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SpeedChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//初始化paint
initPaint();
//绘制弧度
drawArc(canvas);
//绘制文本
drawText(canvas);
}
private void drawText(Canvas canvas) {
Rect mRect = new Rect();
String mValue = String.valueOf(currentValue);
mTextPaint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
//绘制中心的数值
mTextPaint.getTextBounds(mValue, 0, mValue.length(), mRect);
canvas.drawText(mValue, getWidth() / 2, getHeight() / 2 + mRect.height() / 2 - 10, mTextPaint);
mTextPaint.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));
//绘制中心文字描述
mTextPaint.setTextSize(getResources().getDimension(R.dimen.module_ext_arcView_des_text_size));
mTextPaint.getTextBounds(mDes, 0, mDes.length(), mRect);
canvas.drawText(mDes, getWidth() / 2, getHeight() * 17 / 20 + mRect.height() / 2, mTextPaint);
}
private void drawArc(Canvas canvas) {
//绘制圆弧背景
RectF mRectF = new RectF(mStrokeWith, mStrokeWith, getWidth() - mStrokeWith, getHeight() - mStrokeWith);
canvas.drawArc(mRectF, 135, mAngle, false, mArcPaint);
//绘制当前数值对应的圆弧
mArcPaint.setColor(mArcColor);
//根据当前数据绘制对应的圆弧
canvas.drawArc(mRectF, 135, mIncludedAngle, false, mArcPaint);
}
private void initPaint() {
//圆弧的paint
mArcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
//抗锯齿
mArcPaint.setAntiAlias(true);
mArcPaint.setColor(Color.parseColor("#151D4C"));
//设置透明度数值为0-255
mArcPaint.setAlpha(100);
//设置画笔的画出的形状
mArcPaint.setStrokeJoin(Paint.Join.ROUND);
mArcPaint.setStrokeCap(Paint.Cap.ROUND);
//设置画笔类型
mArcPaint.setStyle(Paint.Style.STROKE);
//画笔宽度
mArcPaint.setStrokeWidth(mStrokeWith);
//中心文字的paint
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setColor(Color.parseColor("#FFFFFF"));
//设置文本的对齐方式
mTextPaint.setTextAlign(Paint.Align.CENTER);
//mTextPaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.dp_12));
mTextPaint.setTextSize(getResources().getDimension(R.dimen.module_ext_arcView_center_text_size));
}
/**
* 为绘制弧度及数据设置动画
*
* @param startAngle 开始的弧度
* @param currentAngle 需要绘制的弧度
* @param time 动画执行的时长
*/
private void setAnimation(float startAngle, float currentAngle, int time) {
//绘制当前数据对应的圆弧的动画效果
ValueAnimator progressAnimator = ValueAnimator.ofFloat(startAngle, currentAngle);
progressAnimator.setDuration(time);
progressAnimator.setTarget(mIncludedAngle);
progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mIncludedAngle = (float) animation.getAnimatedValue();
//重新绘制,不然不会出现效果
postInvalidate();
}
});
//开始执行动画
progressAnimator.start();
}
/**
* 设置弧形颜色
*
* @param value 颜色值
*/
public void setArcColor(int value) {
mArcColor = value;
}
/**
* 设置数据
*
* @param value 当前绘制的值
*/
public void setValues(int value) {
//完全覆盖
if (value > maxValue) {
value = maxValue;
}
if (value < 0) {
value = 0;
}
currentValue = value;
//计算弧度比重
float scale = (float) currentValue / maxValue;
//计算弧度
float currentAngle = scale * mAngle;
//开始执行动画
setAnimation(lastAngle, currentAngle, 1000);
lastAngle = currentAngle;
//重新绘制
postInvalidate();
}
}

View File

@@ -0,0 +1,110 @@
package com.mogo.eagle.core.function.hmi.ui.widget
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.location.Location
import android.util.AttributeSet
import android.util.Log
import android.view.Gravity
import android.view.View
import android.widget.FrameLayout
import com.alibaba.android.arouter.launcher.ARouter
import com.mogo.commons.debug.DebugConfig
import com.mogo.eagle.core.function.hmi.R
import com.mogo.map.MogoLatLng
import com.mogo.map.navi.IMogoCarLocationChangedListener2
import com.mogo.module.common.MogoApisHandler
import com.mogo.service.IMogoServiceApis
import com.mogo.service.MogoServicePaths
import com.mogo.service.statusmanager.IMogoStatusChangedListener
import com.mogo.service.statusmanager.StatusDescriptor
/**
* @author xiaoyuzhou
* @date 2021/8/25 8:25 下午
*/
class SpeedPanelView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr),
IMogoCarLocationChangedListener2,
IMogoStatusChangedListener {
val TAG = "SpeedPanelView"
private var mMogoServiceApis: IMogoServiceApis =
ARouter.getInstance().build(MogoServicePaths.PATH_SERVICE_APIS)
.navigation(context) as IMogoServiceApis
var mContext: Context
var mSpeedChartView: SpeedChartView
init {
setBackgroundResource(R.drawable.yi_biao_pan_bg_nor)
mContext = context
mSpeedChartView = SpeedChartView(context)
val layoutParams = LayoutParams(
resources.getDimension(R.dimen.module_ext_arcView_width).toInt(),
resources.getDimension(R.dimen.module_ext_arcView_height).toInt()
)
layoutParams.gravity = Gravity.CENTER
mSpeedChartView.layoutParams = layoutParams
addView(mSpeedChartView)
if (DebugConfig.isDebug()) {
mSpeedChartView.isLongClickable = true
mSpeedChartView.setOnLongClickListener { v ->
Log.d(TAG, "长按显示状态工具栏")
val intent = Intent()
intent.putExtra("oper", 52)
MogoApisHandler.getInstance().apis.intentManagerApi
.invoke("com.mogo.mock", intent)
true
}
}
// 注册位置回调
mMogoServiceApis.registerCenterApi
.registerCarLocationChangedListener(TAG, this)
// 注册VR模式回调
mMogoServiceApis.statusManagerApi
.registerStatusChangedListener(TAG, StatusDescriptor.VR_MODE, this)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
// 解除注册
mMogoServiceApis.registerCenterApi
.unregisterMogoLocationListener(TAG)
mMogoServiceApis.statusManagerApi
.unregisterStatusChangedListener(TAG, StatusDescriptor.VR_MODE, this)
}
override fun onCarLocationChanged(latLng: MogoLatLng?) {
}
override fun onCarLocationChanged2(latLng: Location) {
val speed = (latLng.speed * 3.6f).toInt()
mSpeedChartView.setArcColor(Color.parseColor(if (speed > 60) "#DB3137" else "#3E77F6"))
mSpeedChartView.setValues(speed)
setBackgroundResource(if (speed > 60) R.drawable.yi_biao_pan_bg_speeding else R.drawable.yi_biao_pan_bg_nor)
}
override fun onStatusChanged(descriptor: StatusDescriptor?, isTrue: Boolean) {
if (descriptor == StatusDescriptor.VR_MODE) {
try {
visibility = if (isTrue) {
View.VISIBLE
} else {
View.GONE
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}

View File

@@ -0,0 +1,108 @@
package com.mogo.eagle.core.function.hmi.ui.widget
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import com.mogo.eagle.core.function.hmi.R
import kotlinx.android.synthetic.main.view_traffic_light_vr.view.*
/**
* @author xiaoyuzhou
* @date 2021/8/4 3:16 下午
* 红绿灯控件
*/
class TrafficLightView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
init {
LayoutInflater.from(context).inflate(R.layout.view_traffic_light_vr, this, true)
}
/**
* 展示红绿灯预警
*
* @param checkLightId 0-都是默认1-红2-黄3-绿
*/
fun showWarningTrafficLight(checkLightId: Int) {
visibility = View.VISIBLE
when (checkLightId) {
0 -> {
ctvRedTrafficLight.isChecked = false
ctvYellowTrafficLight.isChecked = false
ctvGreenTrafficLight.isChecked = false
}
1 -> {
ctvRedTrafficLight.isChecked = true
ctvYellowTrafficLight.isChecked = false
ctvGreenTrafficLight.isChecked = false
}
2 -> {
ctvRedTrafficLight.isChecked = false
ctvYellowTrafficLight.isChecked = true
ctvGreenTrafficLight.isChecked = false
}
3 -> {
ctvRedTrafficLight.isChecked = false
ctvYellowTrafficLight.isChecked = false
ctvGreenTrafficLight.isChecked = true
}
}
}
/**
* 关闭红绿灯预警展示,并重制灯态
*/
fun disableWarningTrafficLight() {
visibility = View.GONE
ctvRedTrafficLight.isChecked = false
ctvYellowTrafficLight.isChecked = false
ctvGreenTrafficLight.isChecked = false
ctvRedTrafficLight.text = ""
ctvYellowTrafficLight.text = ""
ctvGreenTrafficLight.text = ""
}
/**
* @param readNum 红灯倒计时
* @param yellowNum 黄灯倒计时
* @param greenNum 绿灯倒计时
*/
fun changeCountdownTrafficLightNum(readNum: Int, yellowNum: Int, greenNum: Int) {
changeCountdownGreen(readNum)
changeCountdownYellow(yellowNum)
changeCountdownRed(greenNum)
}
fun changeCountdownGreen(greenNum: Int) {
if (greenNum > 0) {
ctvGreenTrafficLight.text = "$greenNum"
} else {
ctvGreenTrafficLight.text = ""
}
}
fun changeCountdownYellow(yellowNum: Int) {
if (yellowNum > 0) {
ctvYellowTrafficLight.text = "$yellowNum"
} else {
ctvYellowTrafficLight.text = ""
}
}
fun changeCountdownRed(redNum: Int) {
if (redNum > 0) {
ctvRedTrafficLight.text = "$redNum"
} else {
ctvRedTrafficLight.text = ""
}
}
}

View File

@@ -0,0 +1,44 @@
package com.mogo.eagle.core.function.hmi.ui.widget
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.annotation.DrawableRes
import androidx.annotation.Nullable
import androidx.annotation.StringRes
import androidx.constraintlayout.widget.ConstraintLayout
import com.mogo.eagle.core.function.hmi.R
import kotlinx.android.synthetic.main.notification_v2x_msg_vr.view.*
/**
*@author xiaoyuzhou
*@date 2021/8/6 12:25 下午
*/
class V2XNotificationView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
init {
LayoutInflater.from(context).inflate(R.layout.notification_v2x_msg_vr, this, true)
}
fun setWarningIcon(@DrawableRes warningIcon: Int) {
ivWaringIcon.setImageResource(warningIcon)
}
fun setWarningIcon(@Nullable drawable: Drawable) {
ivWaringIcon.setImageDrawable(drawable)
}
fun setWarningContent(@Nullable warningContent: String) {
tvWaringContent.text = warningContent
}
fun setWarningContent(@StringRes warningContentId: Int) {
tvWaringContent.setText(warningContentId)
}
}

View File

@@ -0,0 +1,120 @@
package com.mogo.eagle.core.function.hmi.ui.widget
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.RelativeLayout
import com.mogo.eagle.core.data.enums.WarningDirectionEnum
import com.mogo.eagle.core.function.hmi.R
import com.mogo.utils.logger.Logger
import kotlinx.android.synthetic.main.module_hmi_warning_v2x.view.*
/**
*@author xiaoyuzhou
*@date 2021/9/10 7:44 下午
*/
class V2XWarningView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : RelativeLayout(context, attrs, defStyleAttr) {
private val ALL_CLOSE_TIMER = 10000L
private val closeWarningTask: Runnable = Runnable {
Logger.d("V2XWarningView", "预警红边:倒计时结束")
showWarning(WarningDirectionEnum.ALERT_WARNING_NON)
}
init {
LayoutInflater.from(context).inflate(R.layout.module_hmi_warning_v2x, this, true)
}
/**
* 展示指定方位上的红框预警
* @param direction
* @see WarningDirectionEnum
*/
fun showWarning(direction: WarningDirectionEnum) {
showWarning(direction, ALL_CLOSE_TIMER)
}
/**
* 展示指定方位上的红框预警
* @param direction
* @see WarningDirectionEnum
* @param closeTime 倒计时
*/
fun showWarning(direction: WarningDirectionEnum, closeTime: Long) {
Logger.d("V2XWarningView", "预警红边:预警方向->$direction 预警倒计时->$closeTime")
// 如果传入的不是关闭显示,则设置倒计时,定时关闭红框警示
if (direction != WarningDirectionEnum.ALERT_WARNING_NON) {
removeCallbacks(closeWarningTask)
postDelayed(closeWarningTask, closeTime)
}
when (direction) {
WarningDirectionEnum.ALERT_WARNING_NON -> {
removeCallbacks(closeWarningTask)
hmiWarningTopImg.visibility = View.GONE
hmiWarningRightImg.visibility = View.GONE
hmiWarningBottomImg.visibility = View.GONE
hmiWarningLeftImg.visibility = View.GONE
}
WarningDirectionEnum.ALERT_WARNING_TOP -> {
hmiWarningTopImg.visibility = View.VISIBLE
hmiWarningRightImg.visibility = View.GONE
hmiWarningBottomImg.visibility = View.GONE
hmiWarningLeftImg.visibility = View.GONE
}
WarningDirectionEnum.ALERT_WARNING_RIGHT -> {
hmiWarningTopImg.visibility = View.GONE
hmiWarningRightImg.visibility = View.VISIBLE
hmiWarningBottomImg.visibility = View.GONE
hmiWarningLeftImg.visibility = View.GONE
}
WarningDirectionEnum.ALERT_WARNING_BOTTOM -> {
hmiWarningTopImg.visibility = View.GONE
hmiWarningRightImg.visibility = View.GONE
hmiWarningBottomImg.visibility = View.VISIBLE
hmiWarningLeftImg.visibility = View.GONE
}
WarningDirectionEnum.ALERT_WARNING_LEFT -> {
hmiWarningTopImg.visibility = View.GONE
hmiWarningRightImg.visibility = View.GONE
hmiWarningBottomImg.visibility = View.GONE
hmiWarningLeftImg.visibility = View.VISIBLE
}
WarningDirectionEnum.ALERT_WARNING_TOP_RIGHT -> {
hmiWarningTopImg.visibility = View.VISIBLE
hmiWarningRightImg.visibility = View.VISIBLE
hmiWarningBottomImg.visibility = View.GONE
hmiWarningLeftImg.visibility = View.GONE
}
WarningDirectionEnum.ALERT_WARNING_BOTTOM_RIGHT -> {
hmiWarningTopImg.visibility = View.GONE
hmiWarningRightImg.visibility = View.VISIBLE
hmiWarningBottomImg.visibility = View.VISIBLE
hmiWarningLeftImg.visibility = View.GONE
}
WarningDirectionEnum.ALERT_WARNING_BOTTOM_LEFT -> {
hmiWarningTopImg.visibility = View.GONE
hmiWarningRightImg.visibility = View.GONE
hmiWarningBottomImg.visibility = View.VISIBLE
hmiWarningLeftImg.visibility = View.VISIBLE
}
WarningDirectionEnum.ALERT_WARNING_TOP_LEFT -> {
hmiWarningTopImg.visibility = View.VISIBLE
hmiWarningRightImg.visibility = View.GONE
hmiWarningBottomImg.visibility = View.GONE
hmiWarningLeftImg.visibility = View.VISIBLE
}
WarningDirectionEnum.ALERT_WARNING_ALL -> {
hmiWarningTopImg.visibility = View.VISIBLE
hmiWarningRightImg.visibility = View.VISIBLE
hmiWarningBottomImg.visibility = View.VISIBLE
hmiWarningLeftImg.visibility = View.VISIBLE
}
}
}
}

View File

@@ -0,0 +1,117 @@
package com.mogo.eagle.core.function.hmi.warning;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.alibaba.android.arouter.facade.annotation.Route;
import com.mogo.eagle.core.data.enums.WarningDirectionEnum;
import com.mogo.eagle.core.function.api.hmi.warning.IMoGoWaringProvider;
import com.mogo.eagle.core.function.api.hmi.warning.WarningStatusListener;
import com.mogo.eagle.core.function.hmi.WaringConst;
import com.mogo.eagle.core.function.hmi.ui.MoGoWarningFragment;
import com.mogo.service.MogoServicePaths;
import com.mogo.utils.logger.Logger;
/**
* @author xiaoyuzhou
* @date 2021/8/2 5:52 下午
* 预警模块
*/
@Route(path = MogoServicePaths.PATH_V2X_WARNING)
public class MoGoWarningProvider implements IMoGoWaringProvider {
private String TAG = "MoGoWarningProvider";
private MoGoWarningFragment mMoGoWarningFragment;
private Context mContext;
@Override
public void init(Context context) {
Logger.d(TAG, "初始化蘑菇预警模块 ……");
mContext = context;
}
@Override
public Fragment createCoverage(Context context, Bundle data) {
Logger.d(TAG, "初始化蘑菇预警模块 Fragment……");
mMoGoWarningFragment = new MoGoWarningFragment();
return mMoGoWarningFragment;
}
@NonNull
@Override
public String getFunctionName() {
return WaringConst.MODULE_NAME;
}
@Override
public void showWarningTrafficLight(int checkLightId) {
mMoGoWarningFragment.showWarningTrafficLight(checkLightId);
}
@Override
public void disableWarningTrafficLight() {
mMoGoWarningFragment.disableWarningTrafficLight();
}
@Override
public void showLimitingVelocity(int limitingSpeed) {
mMoGoWarningFragment.showLimitingVelocity(limitingSpeed);
}
@Override
public void disableLimitingVelocity() {
mMoGoWarningFragment.disableLimitingVelocity();
}
@Override
public void changeCountdownRed(int redNum) {
mMoGoWarningFragment.changeCountdownRed(redNum);
}
@Override
public void changeCountdownYellow(int yellowNum) {
mMoGoWarningFragment.changeCountdownYellow(yellowNum);
}
@Override
public void changeCountdownGreen(int greenNum) {
mMoGoWarningFragment.changeCountdownGreen(greenNum);
}
@Override
public void changeCountdownTrafficLightNum(int readNum, int yellowNum, int greenNum) {
mMoGoWarningFragment.changeCountdownTrafficLightNum(readNum, yellowNum, greenNum);
}
@Override
public void showWarningV2X(int v2xType, @Nullable String alertContent,
@Nullable String ttsContent, @Nullable String tag,
@Nullable WarningStatusListener listener) {
mMoGoWarningFragment.showWarningV2X(v2xType, alertContent, ttsContent, tag, listener);
}
@Override
public void disableWarningV2X(String tag) {
mMoGoWarningFragment.disableWarningV2X(tag);
}
@Override
public void showWarning(@NonNull WarningDirectionEnum direction) {
mMoGoWarningFragment.showWarning(direction);
}
@Override
public void showWarning(@NonNull WarningDirectionEnum direction, long closeTime) {
mMoGoWarningFragment.showWarning(direction, closeTime);
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy");
}
}

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.mogo.eagle.core.function.hmi.ui.widget.V2XWarningView
android:id="@+id/flV2XWarningView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.mogo.eagle.core.function.hmi.ui.widget.SpeedPanelView
android:id="@+id/flSpeedChartView"
android:layout_width="@dimen/module_ext_speed_width"
android:layout_height="@dimen/module_ext_speed_height"
android:layout_marginLeft="@dimen/dp_40"
android:layout_marginTop="@dimen/dp_40"
android:elevation="@dimen/dp_10"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.mogo.eagle.core.function.hmi.ui.widget.TrafficLightView
android:id="@+id/viewTrafficLightVr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40px"
android:layout_marginRight="40px"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<TextView
android:id="@+id/tvLimitingVelocity"
android:layout_width="130px"
android:layout_height="130px"
android:layout_marginTop="30px"
android:background="@drawable/bg_waring_limiting_velocity"
android:gravity="center"
android:text="60"
android:textColor="#FFFFFF"
android:textSize="74px"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="@+id/viewTrafficLightVr"
app:layout_constraintTop_toBottomOf="@+id/viewTrafficLightVr"
app:layout_goneMarginEnd="40px"
app:layout_goneMarginTop="40px"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--四个方向,碰撞预警-->
<ImageView
android:id="@+id/hmiWarningTopImg"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_290"
android:background="@drawable/module_hmi_warning_bkg_top"
android:visibility="gone"
tools:visibility="visible" />
<ImageView
android:id="@+id/hmiWarningLeftImg"
android:layout_width="@dimen/dp_290"
android:layout_height="match_parent"
android:background="@drawable/module_hmi_warning_bkg_left"
android:visibility="gone"
tools:visibility="visible" />
<ImageView
android:id="@+id/hmiWarningRightImg"
android:layout_width="@dimen/dp_290"
android:layout_height="match_parent"
android:layout_gravity="end"
android:background="@drawable/module_hmi_warning_bkg_right"
android:visibility="gone"
tools:visibility="visible" />
<ImageView
android:id="@+id/hmiWarningBottomImg"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_290"
android:layout_gravity="bottom"
android:background="@drawable/module_hmi_warning_bkg_bottom"
android:visibility="gone"
tools:visibility="visible" />
</FrameLayout>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_gravity="center_horizontal"
android:background="@drawable/bg_warning_bg"
android:paddingStart="38px"
android:paddingTop="27px"
android:paddingEnd="38px"
android:paddingBottom="27px">
<ImageView
android:id="@+id/ivWaringIcon"
android:layout_width="132px"
android:layout_height="132px"
android:src="@drawable/icon_warning_v2x_abnormal_vehicle"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvWaringContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="26px"
android:text="预警信息"
android:textColor="#FFFFFF"
android:textSize="42px"
app:layout_constraintBottom_toBottomOf="@+id/ivWaringIcon"
app:layout_constraintLeft_toRightOf="@+id/ivWaringIcon"
app:layout_constraintTop_toTopOf="@+id/ivWaringIcon"
tools:text="前车碰撞预警" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/clTrafficLight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_waring_traffic_light_vr"
android:paddingStart="10px"
android:paddingTop="10px"
android:paddingEnd="10px"
android:paddingBottom="10px"
tools:visibility="visible">
<CheckedTextView
android:id="@+id/ctvRedTrafficLight"
android:layout_width="110px"
android:layout_height="110px"
android:background="@drawable/icon_waring_traffic_light_red_vr"
android:gravity="center"
android:textAlignment="center"
android:textColor="#FFFFFF"
android:textSize="38px"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/ctvYellowTrafficLight"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:checked="true"
tools:text="99" />
<CheckedTextView
android:id="@+id/ctvYellowTrafficLight"
android:layout_width="110px"
android:layout_height="110px"
android:layout_marginStart="90px"
android:background="@drawable/icon_waring_traffic_light_yellow_vr"
android:gravity="center"
android:singleLine="true"
android:textAlignment="center"
android:textColor="#FFFFFF"
android:textSize="38px"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/ctvRedTrafficLight"
app:layout_constraintTop_toTopOf="parent"
tools:checked="true"
tools:text="99" />
<CheckedTextView
android:id="@+id/ctvGreenTrafficLight"
android:layout_width="110px"
android:layout_height="110px"
android:layout_marginStart="90px"
android:background="@drawable/icon_waring_traffic_light_green_vr"
android:gravity="center"
android:textAlignment="center"
android:textColor="#FFFFFF"
android:textSize="38px"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/ctvYellowTrafficLight"
app:layout_constraintTop_toTopOf="parent"
tools:checked="true"
tools:text="99" />
</androidx.constraintlayout.widget.ConstraintLayout>