package com.xscm.modulemain.widget import android.annotation.SuppressLint import android.content.Context import android.graphics.Canvas import android.util.Log import android.view.Gravity import android.view.View import android.view.ViewGroup import android.widget.LinearLayout import androidx.annotation.Nullable import com.google.android.flexbox.FlexboxLayout import com.hjq.toast.ToastUtils import com.opensource.svgaplayer.SVGAImageView import com.opensource.svgaplayer.SVGAParser import com.opensource.svgaplayer.SVGAVideoEntity import com.xscm.moduleutil.R import com.xscm.moduleutil.bean.RoomMessageEvent import com.xscm.moduleutil.bean.room.RoomPitBean import com.xscm.moduleutil.widget.CircularImage import com.xscm.moduleutil.widget.GifAvatarOvalView import com.xscm.moduleutil.widget.RoomMakeWheatView import com.xscm.moduleutil.widget.RoomSingSongWheatView import kotlin.math.roundToInt /** * 二卡八显示布局管理器(单例模式,支持预绘制和动态添加) */ class WheatLayoutSingManager private constructor( private val appContext: Context ) { // 内部根容器(预创建并提前绘制) private var rootContainer: LinearLayout = LinearLayout(appContext).apply { orientation = LinearLayout.VERTICAL layoutParams = FlexboxLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ).apply { // 添加居中设置 gravity = Gravity.CENTER_HORIZONTAL } setLayerType(View.LAYER_TYPE_HARDWARE, null) } // 麦位数据 private var pitList: List? = null // 单麦模式标记 private var isSingleMode = false private var currentSinglePit = -1 private var singleWheatView: RoomSingSongWheatView? = null private val multiWheatViews = mutableListOf() // 麦位索引映射 private val pitIndexMap = intArrayOf(9, 10, 1, 2, 3, 4, 5, 6, 7, 8) // 预加载时的尺寸(基于屏幕尺寸预估) private val preloadWidth: Int by lazy { appContext.resources.displayMetrics.widthPixels - appContext.resources.getDimensionPixelSize(R.dimen.dp_5) * 2 } private val preloadHeight: Int by lazy { appContext.resources.displayMetrics.heightPixels / 2 // 预估高度为屏幕一半 } // 初始化时立即执行预绘制 init { preDrawContainer() } /** * 预绘制容器(核心预加载逻辑) * 手动触发测量、布局、绘制流程,生成缓存 */ private fun preDrawContainer() { // 1. 测量:使用预估尺寸,AT_MOST模式适配wrap_content rootContainer.measure( View.MeasureSpec.makeMeasureSpec(preloadWidth, View.MeasureSpec.AT_MOST), View.MeasureSpec.makeMeasureSpec(preloadHeight, View.MeasureSpec.AT_MOST) ) // 2. 布局:指定位置(左上角为0,0) rootContainer.layout( 0, 0, rootContainer.measuredWidth, rootContainer.measuredHeight ) // 3. 绘制:强制触发绘制,生成缓存 rootContainer.draw(Canvas()) } /** * 获取预绘制好的根容器,供外部添加到FlexboxLayout */ fun getRootContainer(): LinearLayout = rootContainer /** * 设置麦位数据并刷新视图 */ fun setWheatData(pitList: List?) { this.pitList = pitList restoreMultiWheat() // 数据更新后重新预绘制,确保缓存同步 preDrawContainer() } /** * 为麦位视图设置点击事件(确保使用最新的 wheatClickListener) */ private fun setupViewListeners(wheatView: RoomSingSongWheatView, pitNumber: Int) { // 头像点击事件 val avatarView = wheatView.mRiv as CircularImage avatarView.setOnClickListener { // 直接使用当前的 wheatClickListener(可能已被外部设置) wheatClickListener?.onWheatClick(wheatView, pitNumber) } // 魅力值点击事件 val charmView = wheatView.mCharmView charmView.setOnClickListener { ToastUtils.show("点击了麦位") wheatClickListener?.onMeilingClick(wheatView, pitNumber) } // 整体点击事件 wheatView.setOnClickListener { wheatClickListener?.onMeilingClick(wheatView, wheatView.pitNumber.toInt()) } } /** * 设置麦位点击监听器(关键修改:设置后为已有视图重新绑定事件) */ fun setOnWheatClickListener(@Nullable listener: OnWheatClickListener?) { this.wheatClickListener = listener // 为已创建的所有麦位视图重新绑定点击事件(此时 listener 已生效) multiWheatViews.forEach { view -> val pitNumber = view.pitNumber.toIntOrNull() ?: return@forEach setupViewListeners(view, pitNumber) } } /** * 恢复多麦模式布局 */ private fun restoreMultiWheat() { // 清空现有视图(保留缓存逻辑) val screenWidth = getScreenWidth() val itemWidth = screenWidth / 4.2 var row = createHorizontalRow() if (multiWheatViews.size == 10) { // 复用已有视图,仅更新数据 multiWheatViews.forEachIndexed { i, view -> if (pitList != null) { view.setData(pitList!![pitIndexMap[i] - 1]) } } } else { // 创建新视图 for (i in 0 until 10) { val pitNumber = pitIndexMap[i] val wheatView = RoomSingSongWheatView(appContext).apply { this.pitNumber = pitNumber.toString() if (pitList != null) { setData(pitList!![pitNumber - 1]) } } multiWheatViews.add(wheatView) var params: LinearLayout.LayoutParams? = null if (i == 0) { val fixedHeightInDp = 110 // 固定高度为 100dp val fixedHeightInPx = dpToPx(fixedHeightInDp) // 调用已有的 dpToPx 方法 // 第一个控件:左边距 86dp,右边距 100dp params = LinearLayout.LayoutParams(itemWidth.toInt(), fixedHeightInPx) params.rightMargin = dpToPx(50) } else if (i == 1) { val fixedHeightInDp = 110 // 固定高度为 100dp val fixedHeightInPx = dpToPx(fixedHeightInDp) // 调用已有的 dpToPx 方法 // 第二个控件:右边距 86dp params = LinearLayout.LayoutParams(itemWidth.toInt(), fixedHeightInPx) } else { val fixedHeightInDp = 90 // 固定高度为 100dp val fixedHeightInPx = dpToPx(fixedHeightInDp) // 调用已有的 dpToPx 方法 params = LinearLayout.LayoutParams(itemWidth.toInt() - 30, fixedHeightInPx + 30) // 其他控件保持原有逻辑 params.setMargins(0, 0, 0, 0) // 不设右边距,由 row padding 控制 } // val params = getLayoutParams(i, itemWidth.toInt()) wheatView.layoutParams = params // 设置点击事件 setupViewListeners(wheatView, pitNumber) row.addView(wheatView) // 换行逻辑 handleRowBreak(i, row) { newRow -> row = newRow } } // 添加最后一行剩余视图 if (row.childCount > 0) { rootContainer.addView(row) } isSingleMode = false currentSinglePit = -1 } } /** * 恢复PK模式下的多麦布局 */ fun restoreMultiWheatPk(layoutType: Int) { try { if (layoutType == 1) { rootContainer.removeAllViews() } } catch (e: Exception) { return } val pitList = this.pitList ?: return if (pitList.size < 10) return val screenWidth = getScreenWidth() val itemWidth = screenWidth / 8 var row = createHorizontalRow() // 调整前两个麦位顺序 val (firstPit, secondPit) = when (layoutType) { 1 -> Pair(10, 9) 2 -> Pair(9, 10) else -> Pair(9, 10) } // 添加前两个麦位 addWheatViewItem(row, firstPit, itemWidth * 2, layoutType) addWheatViewItem(row, secondPit, itemWidth * 2, layoutType) rootContainer.addView(row) row = createHorizontalRow() // 添加剩余8个麦位 for (i in 2 until 10) { val pitNumber = pitIndexMap[i] addWheatViewItem(row, pitNumber, itemWidth, layoutType) if (i > 1 && (i - 2) % 4 == 3) { rootContainer.addView(row) row = createHorizontalRow() } } if (row.childCount > 0) { rootContainer.addView(row) } isSingleMode = false currentSinglePit = -1 // 预绘制更新 preDrawContainer() } /** * 添加单个麦位视图到行布局 */ private fun addWheatViewItem( row: LinearLayout, pitNumber: Int, itemWidth: Int, layoutType: Int ) { val pitList = this.pitList ?: return val wheatView = RoomSingSongWheatView(appContext).apply { this.pitNumber = pitNumber.toString() setData(pitList[pitNumber - 1]) } val params = when (pitNumber) { 9, 10 -> { val fixedHeight = appContext.resources.getDimensionPixelSize(R.dimen.dp_90) if (pitNumber == 9) { LinearLayout.LayoutParams(itemWidth - 40, fixedHeight).apply { if (layoutType == 1) { rightMargin = appContext.resources.getDimensionPixelSize(R.dimen.dp_1) setMargins(20, -30, -20, 0) } else { leftMargin = appContext.resources.getDimensionPixelSize(R.dimen.dp_1) setMargins(-30, -20, 0, 0) } } } else { LinearLayout.LayoutParams(itemWidth - 80, fixedHeight).apply { if (layoutType == 1) { setMargins(-30, 10, 0, 0) } else { setMargins(0, 10, -30, 0) } } } } else -> { val fixedHeight = appContext.resources.getDimensionPixelSize(R.dimen.dp_60) LinearLayout.LayoutParams(itemWidth + 15, fixedHeight + 20).apply { setMargins(-20, -20, -20, 0) } } } wheatView.layoutParams = params wheatView.setOnClickListener { wheatClickListener?.let { listener -> val pitNum = wheatView.pitNumber.toInt() if (layoutType == 1) { listener.onWheatClick(wheatView, pitNum) } else { listener.onMakeWheatClick(wheatView, pitNum) } } } row.addView(wheatView) } /** * 创建水平方向的行布局 */ private fun createHorizontalRow(): LinearLayout { return LinearLayout(appContext).apply { orientation = LinearLayout.HORIZONTAL layoutParams = LinearLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT ).apply { // 添加行内居中 gravity = Gravity.CENTER_HORIZONTAL } } } /** * 获取麦位视图的布局参数 */ private fun getLayoutParams(index: Int, itemWidth: Int): LinearLayout.LayoutParams { return when (index) { 0 -> { LinearLayout.LayoutParams((itemWidth * 1.7).toInt(), dpToPx(110)).apply { // 移除右间距,通过容器居中自动平衡 rightMargin = (getScreenWidth() - ((itemWidth * 1.7).toInt() * 2)) / 2 } } 1 -> { LinearLayout.LayoutParams((itemWidth * 1.7).toInt(), dpToPx(110)) } else -> { val margin = (getScreenWidth() - (itemWidth * 4)) / 4 / 2 // 统一边距逻辑 LinearLayout.LayoutParams((itemWidth *1.3).toInt() , dpToPx(110)).apply { setMargins(0, 0, 0, 0) } } } } /** * 处理换行逻辑 */ private fun handleRowBreak( index: Int, currentRow: LinearLayout, newRowCallback: (LinearLayout) -> Unit ) { when { index == 1 -> { rootContainer.addView(currentRow) newRowCallback(createHorizontalRow()) } index > 1 && (index - 2) % 4 == 3 -> { rootContainer.addView(currentRow) newRowCallback(createHorizontalRow()) } } } /** * 创建麦位视图 */ private fun createWheatView(pitNumber: Int): RoomSingSongWheatView { val pitList = this.pitList ?: throw IllegalArgumentException("pitList is null") return RoomSingSongWheatView(appContext).apply { this.pitNumber = pitNumber.toString() setData(pitList[pitNumber - 1]) } } /** * 创建申请麦位视图 */ private fun createRoomMakeWheatView(pitNumber: Int): RoomMakeWheatView { val pitList = this.pitList ?: throw IllegalArgumentException("pitList is null") return RoomMakeWheatView(appContext).apply { this.pitNumber = pitNumber.toString() setData(pitList[pitNumber - 1]) } } /** * dp转px */ private fun dpToPx(dp: Int): Int { return (dp * appContext.resources.displayMetrics.density).roundToInt() } /** * 获取屏幕宽度 */ private fun getScreenWidth(): Int { return appContext.resources.displayMetrics.widthPixels } /** * 更新麦位交换数据 */ fun setUpData(event: RoomMessageEvent) { val fromPit = event.text.from_pit_number ?: return val toPitNumber = event.text.to_pit_number ?: return val fromWheatView = findWheatViewByPitNumber(fromPit.toInt()) val toWheatView = findWheatViewByPitNumber(toPitNumber.toInt()) if (fromWheatView == null || toWheatView == null) return // 交换麦位数据 val fromPitBean = fromWheatView.pitBean val toPitBean = toWheatView.pitBean val tmpNumber = fromPitBean.pit_number fromPitBean.pit_number = toPitBean.pit_number toPitBean.pit_number = tmpNumber toWheatView.setData(fromPitBean) fromWheatView.setData(toPitBean) // 清空原麦位数据 multiWheatViews.forEach { view -> if (view.pitBean.user_id == event.text.fromUserInfo.user_id.toString() && view.pitBean.pit_number != toPitNumber ) { view.pitBean.apply { charm = "" user_id = "" dress = "" avatar = "" nickname = "" sex = "" user_code = "" dress_picture = "" } view.setData(view.pitBean) } } // 预绘制更新 preDrawContainer() } /** * 更新单个麦位信息 */ fun updateSingleWheat(pitBean: RoomPitBean, pitNumber: Int) { if (pitNumber < 1 || pitNumber > 10) return if (isSingleMode && currentSinglePit != pitNumber) return findWheatViewByPitNumber(pitNumber)?.setData(pitBean) // 预绘制更新 preDrawContainer() } /** * 更新麦位魅力值 */ fun upDataCharm(pitBean: RoomPitBean, pitNumber: Int) { if (pitNumber < 1 || pitNumber > 10) return if (isSingleMode && currentSinglePit != pitNumber) return findWheatViewByPitNumber(pitNumber)?.setCharm(pitBean.charm) // 预绘制更新 preDrawContainer() } /** * 根据麦位号查找视图 */ @Nullable private fun findWheatViewByPitNumber(pitNumber: Int): RoomSingSongWheatView? { for (i in 0 until rootContainer.childCount) { val row = rootContainer.getChildAt(i) when (row) { is LinearLayout -> { for (j in 0 until row.childCount) { val child = row.getChildAt(j) if (child is RoomSingSongWheatView && child.pitNumber.toInt() == pitNumber) { return child } } } is RoomSingSongWheatView -> { if (row.pitNumber.toInt() == pitNumber) { return row } } } } return null } /** * 释放资源 */ fun release() { try { // 释放子视图资源 multiWheatViews.forEach { it.releaseResources() } multiWheatViews.clear() // 清空容器 rootContainer.removeAllViews() // 从父布局移除自身 (rootContainer.parent as? ViewGroup)?.removeView(rootContainer) // 清除绘制缓存 rootContainer.setLayerType(View.LAYER_TYPE_NONE, null) } catch (e: Exception) { // 忽略异常 } // 清空数据引用 pitList = null singleWheatView = null wheatClickListener = null } /** * 麦位点击事件接口 */ interface OnWheatClickListener { fun onWheatClick(view: RoomSingSongWheatView, pitNumber: Int) fun onMakeWheatClick(view: RoomSingSongWheatView, pitNumber: Int) fun onMeilingClick(view: RoomSingSongWheatView, pitNumber: Int) } @Nullable private var wheatClickListener: OnWheatClickListener? = null /** * 单例实现 */ companion object { @SuppressLint("StaticFieldLeak") @Volatile private var instance: WheatLayoutSingManager? = null /** * 初始化单例(预加载视图) * 建议在Application.onCreate()中调用 */ fun init(context: Context) { if (instance == null) { synchronized(WheatLayoutSingManager::class.java) { if (instance == null) { instance = WheatLayoutSingManager(context.applicationContext) } } } } /** * 获取单例实例 * 必须先调用init()初始化 */ fun getInstance(): WheatLayoutSingManager { return instance ?: throw IllegalStateException("请先调用init()初始化WheatLayoutSingManager") } /** * 销毁单例(退出应用时调用) */ fun destroyInstance() { instance?.release() instance = null } } private var svgaVideoItem: SVGAVideoEntity? = null fun bindSvga(view: SVGAImageView, context: Context, assetName: String) { if (svgaVideoItem == null) { val parser = SVGAParser(context) parser.decodeFromAssets(assetName, object : SVGAParser.ParseCompletion { override fun onComplete(videoItem: SVGAVideoEntity) { // videoItem 可能为 null,按需处理 videoItem.let { svgaVideoItem = it view.setVideoItem(it) view.startAnimation() } } override fun onError() { // 解析失败的处理 Log.e("SVGA", "decodeFromAssets error: $assetName") } }) } else { view.setVideoItem(svgaVideoItem) view.startAnimation() } } fun releaseFromParent() { rootContainer.let { container -> (container.parent as? ViewGroup)?.removeView(container) } } }