package com.xw.yunwei.ui.task.list
|
|
import android.annotation.SuppressLint
|
import android.os.Handler
|
import android.os.Looper
|
import android.os.Message
|
import android.view.View
|
import androidx.core.view.isVisible
|
import androidx.databinding.DataBindingUtil
|
import androidx.recyclerview.widget.RecyclerView
|
import com.amap.api.maps.model.LatLng
|
import com.chad.library.adapter.base.BaseQuickAdapter
|
import com.chad.library.adapter.base.viewholder.BaseViewHolder
|
import com.xw.common.base.TwoItemElementClickListener
|
import com.xw.common.utils.BindUtils.getResColor
|
import com.xw.common.utils.CalculatorLineDistance
|
import com.xw.common.utils.LogUtils
|
import com.xw.yunwei.MyApp
|
import com.xw.yunwei.R
|
import com.xw.yunwei.databinding.ItemTaskContentBinding
|
import com.xw.yunwei.model.TaskModel
|
import com.xw.yunwei.model.taskPlaceHolder
|
import java.time.Duration
|
import java.time.LocalDateTime
|
import java.time.format.DateTimeFormatter
|
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.TimeUnit
|
import kotlin.math.max
|
|
/**
|
* 任务列表适配器
|
*
|
* 负责展示任务列表项,支持以下功能:
|
* - 根据任务紧急程度显示不同等级图标
|
* - 对事故类待到达任务进行倒计时展示
|
* - 根据当前定位计算与车辆距离,控制"到达现场"按钮可用状态
|
* - 支持列表项点击、操作按钮点击及导航点击事件
|
*
|
* @property listener 列表项及子元素点击事件回调
|
*/
|
class TaskListAdapter(var listener: TwoItemElementClickListener<TaskModel>) :
|
BaseQuickAdapter<TaskModel, BaseViewHolder>(R.layout.item_task_content) {
|
|
/** 当前纬度,用于计算与任务车辆的距离 */
|
var lat: Double = 0.0
|
|
/** 当前经度,用于计算与任务车辆的距离 */
|
var lng: Double = 0.0
|
|
/**
|
* 设置当前定位坐标
|
*
|
* @param lat 纬度,为空时默认为 0.0
|
* @param lng 经度,为空时默认为 0.0
|
* @param refresh 是否立即刷新列表,默认为 true
|
*/
|
@SuppressLint("NotifyDataSetChanged")
|
fun setLocation(lat: Double?, lng: Double?, refresh: Boolean = true) {
|
this.lat = lat ?: 0.0
|
this.lng = lng ?: 0.0
|
if (refresh) {
|
notifyDataSetChanged()
|
}
|
}
|
|
/** 记录需要倒计时的 holder,key 为 holder.hashCode(),用于每秒刷新倒计时 UI */
|
private val countdownHolders = ConcurrentHashMap<Int, ItemTaskContentBinding>()
|
|
/** 倒计时 Handler,主线程每秒发送消息更新所有可见倒计时 */
|
private val countdownHandler = object : Handler(Looper.getMainLooper()) {
|
override fun handleMessage(msg: Message) {
|
if (msg.what == MSG_COUNTDOWN) {
|
if (countdownHolders.isEmpty()) {
|
stopCountdown()
|
} else {
|
updateAllCountdowns()
|
// 每秒刷新一次
|
sendEmptyMessageDelayed(MSG_COUNTDOWN, 1000)
|
}
|
}
|
}
|
}
|
|
/**
|
* 更新所有可见项的倒计时 UI
|
*
|
* 遍历当前已注册的倒计时 holder,计算剩余时间并更新显示;
|
* 若倒计时结束(时分秒均为 0),则标记为"已超时"并从注册表中移除。
|
*/
|
private fun updateAllCountdowns() {
|
if (!recyclerView.isAttachedToWindow) {
|
return
|
}
|
val timeComplete = mutableListOf<Int>()
|
|
countdownHolders.forEach { (code, binding) ->
|
if (!binding.root.isAttachedToWindow) {
|
return@forEach
|
}
|
// 获取当前绑定的数据
|
val position = binding.root.tag as? Int ?: return@forEach
|
val item = getItemOrNull(position) ?: return@forEach
|
|
// 仅对待到达状态(status=0)的事故任务(taskCategory=2)进行倒计时
|
if ((item.status == 0 || item.status == 4) && item.taskCategory == 2) {
|
val list = calculateTimeDifference(item.endTime)
|
updateTime(binding, list)
|
if (list.all { it == 0L }) { // 全0 此项移除
|
binding.countdownTips.text = "已超时"
|
timeComplete.add(code)
|
}
|
}
|
}
|
|
// 移除已超时的 holder,避免无效刷新
|
timeComplete.forEach {
|
countdownHolders.remove(it)
|
}
|
}
|
|
/**
|
* 开始倒计时循环
|
*
|
* 发送倒计时消息,触发每秒刷新机制。
|
*/
|
fun startCountdown() {
|
countdownHandler.removeMessages(MSG_COUNTDOWN)
|
countdownHandler.sendEmptyMessage(MSG_COUNTDOWN)
|
}
|
|
/**
|
* 停止倒计时循环
|
*
|
* 移除 Handler 中待处理的倒计时消息。
|
*/
|
fun stopCountdown() {
|
countdownHandler.removeMessages(MSG_COUNTDOWN)
|
}
|
|
@SuppressLint("SetTextI18n")
|
override fun convert(holder: BaseViewHolder, item: TaskModel) {
|
if (!recyclerView.isAttachedToWindow) {
|
return
|
}
|
if (holder.bindingAdapterPosition == RecyclerView.NO_POSITION) {
|
return
|
}
|
DataBindingUtil.bind<ItemTaskContentBinding>(holder.itemView)?.let { bind ->
|
// 保存 position 到 tag,用于后续更新时获取数据
|
bind.root.tag = holder.bindingAdapterPosition
|
bind.clContent.visibility = if (item == taskPlaceHolder) View.GONE else View.VISIBLE
|
bind.tips.visibility = if (item == taskPlaceHolder) View.VISIBLE else View.GONE
|
if (item == taskPlaceHolder) {
|
return
|
}
|
// 根据任务紧急程度设置左侧等级图标
|
val levelId = when (item.taskUrgencyLevel) {
|
0 -> R.mipmap.ic_task_item_left_one
|
1 -> R.mipmap.ic_task_item_left_two
|
2 -> R.mipmap.ic_task_item_left_three
|
3 -> R.mipmap.ic_task_item_left_four
|
else -> null
|
}
|
levelId?.let { bind.level.setImageResource(levelId) }
|
|
// 事故类待到达任务显示倒计时布局
|
if ((item.status == 0 || item.status == 4) && item.taskCategory == 2) {
|
bind.countdownLayout.isVisible = true
|
val list = calculateTimeDifference(item.endTime)
|
updateTime(bind, list)
|
if (list.all { it == 0L }) { // 全0 表示已超时
|
bind.countdownTips.text = "已超时"
|
} else { // 注册到倒计时列表,启动每秒刷新
|
bind.countdownTips.text = "倒计时结束前,务必到达现场"
|
countdownHolders[holder.hashCode()] = bind
|
startCountdown()
|
}
|
} else {
|
bind.countdownLayout.isVisible = false
|
// 如果不是倒计时项,从列表移除,避免内存泄漏
|
countdownHolders.remove(holder.hashCode())
|
}
|
|
// 设置任务类型文本:事故或常规任务类型名称
|
if (item.isAccidentTask()) {
|
bind.type.text = "事故"
|
} else if (item.isRoutineTask()) {
|
bind.type.text = item.getTaskType()
|
}
|
// 先按"与 type 同行"展示车牌号;待布局完成后检测是否被省略,
|
// 若被省略则把车牌号下移到 type 的下一行整行显示。
|
bind.plateNumber.text = item.plateNumber
|
bind.plateNumberFull.text = item.plateNumber
|
bind.plateNumber.isVisible = true
|
bind.plateNumberFull.isVisible = false
|
bind.plateNumber.post {
|
val layout = bind.plateNumber.layout ?: return@post
|
val ellipsized = (0 until layout.lineCount).any {
|
layout.getEllipsisCount(it) > 0
|
}
|
if (ellipsized) {
|
bind.plateNumber.visibility = View.INVISIBLE
|
bind.plateNumberFull.isVisible = true
|
} else {
|
bind.plateNumber.isVisible = true
|
bind.plateNumberFull.isVisible = false
|
}
|
}
|
|
// 初始化状态文本颜色与背景
|
val setStatus: (Int, Int) -> Unit = { text, bg ->
|
bind.status.setTextColor(text)
|
bind.status.setBackgroundResource(bg)
|
}
|
setStatus(
|
getResColor(com.xw.common.R.color.gray_8),
|
com.xw.common.R.color.gray_3
|
)
|
// 0 = 待到达 1 = 待处理 2 = 已完成 3 = 已取消 4 = 改派中
|
when (item.status) {
|
0 -> bind.status.text = "待到达"
|
1 -> bind.status.text = "待处理"
|
2 -> bind.status.text = "已处理"
|
3 -> bind.status.text = "已取消"
|
4 -> bind.status.text = "改派中"
|
}
|
bind.ivArrow.visibility = if (item.status == 4) View.GONE else View.VISIBLE
|
bind.address.text = item.vehicleLocation
|
bind.llNotice.visibility = if (item.status == 4) View.VISIBLE else View.GONE
|
// 控制"操作按钮"是否可点击
|
var canClick = true
|
|
// 仅对待到达或待处理状态的事故任务显示操作按钮
|
bind.tvControl.isVisible = item.status in arrayOf(0, 1) && item.taskCategory == 2
|
if (item.status == 0) {
|
// 检查是否有"到达事故现场"权限
|
bind.tvControl.isVisible =
|
MyApp.findPermissionByName("到达事故现场")?.visible == "0"
|
bind.tvControl.text = "我已到达现场"
|
canClick = false
|
// 当定位与车辆位置均有效时,计算距离并判断是否在允许到达范围内
|
if (lat != 0.0 && lng != 0.0 && (item.vehicleLat ?: 0.0) != 0.0 && (item.vehicleLon
|
?: 0.0) != 0.0
|
) {
|
val dis = CalculatorLineDistance.calculate(
|
LatLng(lat, lng),
|
LatLng(item.vehicleLat!!, item.vehicleLon!!)
|
)
|
canClick = (item.canArriveRange ?: 0) > dis.toInt()
|
LogUtils.i(
|
"zmc-test-adapter",
|
"${item.plateNumber} dis = $dis canArriveRange = ${item.canArriveRange} canClick = $canClick"
|
)
|
}
|
} else if (item.status == 1) {
|
bind.tvControl.text = "处理事故"
|
}
|
|
// 根据按钮可用状态设置不同背景样式
|
if (canClick) {
|
bind.tvControl.setBackgroundResource(R.drawable.shape_blue_5_bg_4)
|
} else {
|
bind.tvControl.setBackgroundResource(R.drawable.shape_blue_1_bg_4)
|
}
|
|
// 设置点击事件:整项点击、操作按钮点击、导航点击
|
bind.clContent.setOnClickListener {
|
if (item.status == 4) {
|
return@setOnClickListener
|
}
|
listener.onItemClick(item)
|
}
|
bind.tvControl.setOnClickListener {
|
if (item.status == 4) {
|
return@setOnClickListener
|
}
|
if (canClick) listener.oneItemElementClick(item)
|
}
|
bind.nav.setOnClickListener {
|
if (item.status == 4) {
|
return@setOnClickListener
|
}
|
listener.twoItemElementClick(item)
|
}
|
}
|
}
|
|
/**
|
* 更新倒计时时间显示及样式
|
*
|
* @param bind 列表项视图绑定对象
|
* @param list 包含时、分、秒的列表
|
*/
|
private fun updateTime(bind: ItemTaskContentBinding, list: List<Long>) {
|
|
bind.hour.text = if (list[0] < 10) "0${list[0]}" else "${list[0]}"
|
bind.minutes.text = if (list[1] < 10) "0${list[1]}" else "${list[1]}"
|
bind.second.text = if (list[2] < 10) "0${list[2]}" else "${list[2]}"
|
|
// 当剩余时间不足3分钟时,切换为红色警示样式
|
val showRed = list[0] == 0L && list[1] <= 2L
|
|
val countdownId =
|
if (showRed) R.mipmap.img_task_item_top_bg_red else R.mipmap.img_task_item_top_bg_blue
|
bind.countdownLayout.setBackgroundResource(countdownId)
|
|
val colorId =
|
if (showRed) getResColor(com.xw.common.R.color.red_6) else getResColor(com.xw.common.R.color.blue_5)
|
bind.countdownTips.setTextColor(colorId)
|
|
val bgId = if (showRed) R.drawable.shape_fa3e3e_bg_4 else R.drawable.shape_blue_5_bg_4
|
bind.hour.setBackgroundResource(bgId)
|
bind.minutes.setBackgroundResource(bgId)
|
bind.second.setBackgroundResource(bgId)
|
|
val imgId = if (showRed) R.mipmap.ic_task_item_colon_r else R.mipmap.ic_task_item_colon_b
|
bind.colon1.setImageResource(imgId)
|
bind.colon2.setImageResource(imgId)
|
|
// 倒计时全部归零时隐藏时间数字与冒号
|
val hideTime = list[0] == 0L && list[1] == 0L && list[2] == 0L
|
|
bind.hour.isVisible = !hideTime
|
bind.minutes.isVisible = !hideTime
|
bind.second.isVisible = !hideTime
|
bind.colon1.isVisible = !hideTime
|
bind.colon2.isVisible = !hideTime
|
}
|
|
/**
|
* 计算目标时间与当前时间的差值,返回时分秒格式
|
*
|
* @param targetTimeStr 目标时间字符串,格式为 yyyy-MM-dd HH:mm:ss
|
* @return 包含 [时, 分, 秒] 的列表;若输入为空则返回 [0, 0, 0]
|
*/
|
fun calculateTimeDifference(targetTimeStr: String?): List<Long> {
|
targetTimeStr?.let {
|
// 定义输入格式
|
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
// 解析目标时间
|
val targetTime = LocalDateTime.parse(targetTimeStr, formatter)
|
// 获取当前时间
|
val now = LocalDateTime.now()
|
// 计算时间差(毫秒)
|
val duration = Duration.between(now, targetTime)
|
val totalSeconds = max(duration.seconds, 0L)
|
// 转换为时分秒
|
val hours = TimeUnit.SECONDS.toHours(totalSeconds)
|
val minutes = TimeUnit.SECONDS.toMinutes(totalSeconds) % 60
|
val seconds = totalSeconds % 60
|
return listOf(hours, minutes, seconds)
|
}
|
return listOf(0L, 0L, 0L)
|
}
|
|
/**
|
* View 被回收时清理
|
*
|
* 移除该 holder 在倒计时注册表中的记录,防止对已回收视图进行刷新。
|
*/
|
override fun onViewRecycled(holder: BaseViewHolder) {
|
countdownHolders.remove(holder.hashCode())
|
super.onViewRecycled(holder)
|
}
|
|
/**
|
* Adapter 从 RecyclerView 分离时清理所有倒计时
|
*
|
* 停止 Handler 消息循环并清空 holder 注册表,防止内存泄漏。
|
*/
|
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
|
super.onDetachedFromRecyclerView(recyclerView)
|
stopCountdown()
|
countdownHolders.clear()
|
}
|
|
companion object {
|
/** 倒计时消息标识 */
|
const val MSG_COUNTDOWN = 1001
|
}
|
}
|