1:将道具商城做成原生的,添加点击的时候展示动效

This commit is contained in:
2026-01-04 17:59:52 +08:00
parent b473751913
commit 3efe9c9146
23 changed files with 1308 additions and 17 deletions

View File

@@ -58,10 +58,11 @@ public class PersonalityActivity extends BaseMvpActivity<PersonalityPresenter, A
mBinding.topBar.setRightText(getResources().getString(com.xscm.moduleutil.R.string.shopping));
mBinding.topBar.setRightColor(ColorManager.getInstance().getPrimaryColorInt());
mBinding.topBar.getTvRight().setOnClickListener(v -> {
Intent intent=new Intent(this, WebViewActivity.class);
intent.putExtra("url", String.format(WebUrlConstants.INSTANCE.getWEB_PROP_MALL_URL(),SpUtil.getToken()));
intent.putExtra("title", "道具商城");
startActivity(intent);
// Intent intent=new Intent(this, WebViewActivity.class);
// intent.putExtra("url", String.format(WebUrlConstants.INSTANCE.getWEB_PROP_MALL_URL(),SpUtil.getToken()));
// intent.putExtra("title", "道具商城");
// startActivity(intent);
startActivity(new Intent(this, PropMallActivity.class));
});
}

View File

@@ -2,19 +2,13 @@ package com.xscm.modulemain.activity.user.activity.ui.main
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.stx.xhb.xbanner.XBanner
import com.blankj.utilcode.util.ActivityUtils
import com.xscm.modulemain.R
import com.xscm.modulemain.activity.user.conacts.PersonalityConacts
import com.xscm.modulemain.activity.user.presenter.PersonalityPresenter
import com.xscm.modulemain.databinding.FragmentPropMallBinding
import com.xscm.modulemain.dialog.PurchaseOutfitsDialog
import com.xscm.moduleutil.base.BaseMvpFragment
import com.xscm.moduleutil.bean.BannerModel
import com.xscm.moduleutil.bean.PersonaltyBean
@@ -31,6 +25,7 @@ class PlaceholderFragment : BaseMvpFragment<PersonalityPresenter,FragmentPropMal
private lateinit var personaltyAdapter: PersonaltyAdapter
var type: String =""
var purchaseOutfitsDialog: PurchaseOutfitsDialog?=null
// 标记数据是否已加载
private var isDataLoaded = false
@@ -89,6 +84,10 @@ class PlaceholderFragment : BaseMvpFragment<PersonalityPresenter,FragmentPropMal
personaltyAdapter.setOnItemClickListener(object : PersonaltyAdapter.OnItemClickListener{
override fun onItemClick(item: PersonaltyListBean?) {
if (purchaseOutfitsDialog==null){
purchaseOutfitsDialog = PurchaseOutfitsDialog(ActivityUtils.getTopActivity())
}
purchaseOutfitsDialog?.show( item)
}
});

View File

@@ -0,0 +1,338 @@
package com.xscm.modulemain.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import com.xscm.modulemain.R
import com.xscm.moduleutil.bean.DecorateDetailBean
import com.xscm.moduleutil.bean.DecorateDetailBean.PriceListBean
import com.xscm.moduleutil.widget.AmountView
import com.xscm.moduleutil.widget.AmountView.OnAmountChangeListener
/**
* 项目名称:羽声语音
* 时间2026/1/4 11:16
* 用途:装饰价格详情列表(兼容普通类型/type=12类型
*/
class PurchaseOutfitsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
// 定义Item类型补充TYPE_BUY_COUNT对应购买次数项
private companion object {
const val TYPE_SINGLE = 1
const val TYPE_MULTI_OPTION = 2
const val TYPE_BUY_COUNT = 3 // 新增购买次数项类型type=12专用
}
// 适配器数据列表
private var dataList: MutableList<DecorateDetailBean.DecorateAdapterItem> = mutableListOf()
// 记录选中的时长选项位置(普通类型专用,默认选中第一个)
private var selectedOptionPos = 0
// 新增购买次数相关变量type=12专用支撑加减逻辑和总价计算
private var currentBuyCount = 1 // 当前购买次数最小值1
private var currentUnitPrice = "" // 商品单价type=12专用从实体类获取
private var currentDecorateType = false // 标记是否为type=12true=type12false=普通类型)
// ---------------------- 新增:定义回调接口 ----------------------
/**
* 时长选项点击回调接口(普通类型专用)
* 用于将选中的整体数据传递给调用者Activity/Fragment
*/
interface OnDurationOptionSelectedListener {
/**
* 时长选项被选中时的回调方法
* @param selectedPos 选中的选项下标
* @param selectedPriceItem 选中的完整价格/时长数据(核心整体数据)
* @param allPriceOptions 所有价格/时长选项列表(可选,满足调用者额外需求)
*/
fun onDurationSelected(
selectedPos: Int,
selectedPriceItem: PriceListBean,
allPriceOptions: List<PriceListBean>,
)
fun onBuyCountChange(count: Int)
}
// 暴露回调实例(可空)
private var mListener: OnDurationOptionSelectedListener? = null
/**
* 给调用者提供的setter方法用于绑定回调
*/
fun setOnDurationOptionSelectedListener(listener: OnDurationOptionSelectedListener?) {
this.mListener = listener
}
// 新增:获取当前选中的价格/时长项(安全判空,防止数组越界,普通类型专用)
private fun getSelectedPriceItem(): PriceListBean? {
// 先找到MultiOptionItem承载所有options再根据selectedOptionPos获取选中项
val multiItem = dataList.find { it is DecorateDetailBean.DecorateAdapterItem.MultiOptionItem }
as? DecorateDetailBean.DecorateAdapterItem.MultiOptionItem
val options = multiItem?.options ?: return null
return if (selectedOptionPos in options.indices) {
options[selectedOptionPos]
} else {
// 兜底返回第一个选项
options.firstOrNull()
}
}
// ---------------------- 新增:获取所有价格/时长选项(普通类型专用) ----------------------
private fun getAllPriceOptions(): List<DecorateDetailBean.PriceListBean> {
val multiItem = dataList.find { it is DecorateDetailBean.DecorateAdapterItem.MultiOptionItem }
as? DecorateDetailBean.DecorateAdapterItem.MultiOptionItem
return multiItem?.options ?: emptyList()
}
// 新增根据选中项更新dataList中的“商品价格”“有效期至”普通类型专用
// 优化仅精准更新第0、1项不遍历整个dataList提升效率
private fun updateSingleItemData() {
val selectedItem = getSelectedPriceItem() ?: return
// 1. 精准更新第0项商品价格直接定位无需遍历
if (dataList.size > 0 && dataList[0] is DecorateDetailBean.DecorateAdapterItem.SingleItem) {
val priceItem = dataList[0] as DecorateDetailBean.DecorateAdapterItem.SingleItem
dataList[0] = DecorateDetailBean.DecorateAdapterItem.SingleItem(
label = priceItem.label,
content = "${selectedItem.price}"
)
}
// 2. 精准更新第1项有效期至直接定位无需遍历
if (dataList.size > 1 && dataList[1] is DecorateDetailBean.DecorateAdapterItem.SingleItem) {
val endTimeItem = dataList[1] as DecorateDetailBean.DecorateAdapterItem.SingleItem
dataList[1] = DecorateDetailBean.DecorateAdapterItem.SingleItem(
label = endTimeItem.label,
content = selectedItem.endTime
)
}
}
// 更新数据从外部传入数据源组装成Adapter需要的Item兼容type=12
fun setData(decorateData: MutableList<DecorateDetailBean.DecorateAdapterItem> , type: Boolean) {
dataList.clear()
dataList.addAll(decorateData)
currentDecorateType = type // 记录当前类型区分是否为type=12
// 仅普通类型非12即type=false校准购买时长的默认选中位置依赖price_list
if (!type) {
val multiItem = dataList.find { it is DecorateDetailBean.DecorateAdapterItem.MultiOptionItem }
as? DecorateDetailBean.DecorateAdapterItem.MultiOptionItem
val optionsSize = multiItem?.options?.size ?: 0
selectedOptionPos = if (optionsSize > 0) 0 else 0
} else {
// type=12重置购买次数和单价为初始值
currentBuyCount = 1
currentUnitPrice = ""
}
notifyDataSetChanged()
}
// 完善补充TYPE_BUY_COUNT的视图类型判断
override fun getItemViewType(position: Int): Int {
return when (dataList[position]) {
is DecorateDetailBean.DecorateAdapterItem.SingleItem -> TYPE_SINGLE
is DecorateDetailBean.DecorateAdapterItem.MultiOptionItem -> TYPE_MULTI_OPTION
is DecorateDetailBean.DecorateAdapterItem.BuyCountItem -> TYPE_BUY_COUNT // 新增:匹配购买次数项
}
}
// 完善补充TYPE_BUY_COUNT的ViewHolder创建加载对应的布局
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
TYPE_SINGLE -> {
val view = inflater.inflate(R.layout.item_single_info, parent, false)
SingleInfoViewHolder(view)
}
TYPE_MULTI_OPTION -> {
val view = inflater.inflate(R.layout.item_multi_option, parent, false)
MultiOptionViewHolder(view)
}
TYPE_BUY_COUNT -> {
// 新增:加载购买次数专属布局(需确保项目中有该布局文件)
val view = inflater.inflate(R.layout.item_buy_count, parent, false)
BuyCountViewHolder(view)
}
else -> throw IllegalArgumentException("未知的Item类型$viewType")
}
}
// 完善补充BuyCountViewHolder的绑定逻辑兼容三种ViewHolder类型
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is SingleInfoViewHolder -> {
val item = dataList[position] as DecorateDetailBean.DecorateAdapterItem.SingleItem
holder.tvLabel.text = item.label
holder.tvContent.text = item.content
// 核心修改仅第0项商品价格/商品单价)显示图标,其他项隐藏
holder.ivPriceIcon.visibility = if (position == 0) {
View.VISIBLE
} else {
View.GONE
}
}
is MultiOptionViewHolder -> {
val item = dataList[position] as DecorateDetailBean.DecorateAdapterItem.MultiOptionItem
holder.bindOptions(item.options)
}
is BuyCountViewHolder -> {
// 新增绑定购买次数数据type=12专用
val item = dataList[position] as DecorateDetailBean.DecorateAdapterItem.BuyCountItem
holder.bindData(item.initialCount, item.unitPrice)
}
}
}
override fun getItemCount(): Int = dataList.size
// 单行信息的ViewHolder保持原有逻辑已绑定价格图标
inner class SingleInfoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val tvLabel: TextView = itemView.findViewById(R.id.tv_label)
val tvContent: TextView = itemView.findViewById(R.id.tv_content)
val ivPriceIcon: ImageView = itemView.findViewById(R.id.iv_price_icon) // 价格图标仅第0项显示
}
// ---------------------- 完善补全BuyCountViewHolder实现加减交互和总价联动 ----------------------
inner class BuyCountViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val ivMinus: AmountView = itemView.findViewById(R.id.amount_view) // 减号按钮
// val tvCount: TextView = itemView.findViewById(R.id.tv_count) // 次数显示
fun bindData(initialCount: Int, unitPrice: String) {
// 初始化数据单价直接从BuyCountItem传入来自decorate.singlePrice
currentBuyCount = initialCount
currentUnitPrice = unitPrice
// tvCount.text = currentBuyCount.toString()
ivMinus.setOnAmountChangeListener(object : OnAmountChangeListener {
override fun onAmountChange(view: View?, amount: Int) {
currentBuyCount = amount
// 联动更新商品总价
// tvCount.text = currentBuyCount.toString()
// 联动更新商品总价
updateTotalPrice()
this@PurchaseOutfitsAdapter.mListener?.onBuyCountChange(
currentBuyCount
)
}
})
}
}
/**
* 计算并更新商品总价(单价 × 购买次数兼容type=12无price_list
* 局部刷新,避免页面闪烁
*/
private fun updateTotalPrice() {
// 计算总价并格式化保留2位小数避免小数位数混乱
val totalPrice = currentUnitPrice.toInt() * currentBuyCount
val totalPriceStr =totalPrice
// 遍历找到“商品总价”项,更新数据源并局部刷新
dataList.forEachIndexed { index, adapterItem ->
if (adapterItem is DecorateDetailBean.DecorateAdapterItem.SingleItem
&& adapterItem.label == "商品总价") {
// 更新数据源替换SingleItem保持label不变更新content
dataList[index] = DecorateDetailBean.DecorateAdapterItem.SingleItem(
label = adapterItem.label,
content = totalPriceStr.toString()
)
// 局部刷新,仅更新总价项,避免页面闪烁
notifyItemChanged(index)
return // 找到后直接返回,无需继续遍历,提升效率
}
}
}
// 多选项购买时长的ViewHolder重构视图复用普通类型专用无闪烁
inner class MultiOptionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val llContainer: LinearLayout = itemView.findViewById(R.id.ll_options_container)
private val optionViews = mutableListOf<TextView>() // 缓存选项视图,实现复用
private var currentOptions: List<DecorateDetailBean.PriceListBean> = emptyList() // 缓存当前选项数据
// 重构bindOptions视图复用仅更新数据和状态不重建视图
fun bindOptions(options: List<DecorateDetailBean.PriceListBean>) {
currentOptions = options
// 1. 移除多余的视图(当新选项数量少于缓存视图数量时)
if (optionViews.size > options.size) {
val removeViews = optionViews.subList(options.size, optionViews.size)
llContainer.removeViews(options.size, removeViews.size)
removeViews.clear()
}
// 2. 复用已有视图,或创建新视图(仅当新选项数量多于缓存视图数量时)
options.forEachIndexed { index, priceItem ->
val optionView = if (index < optionViews.size) {
// 复用已有视图,避免重复创建
optionViews[index]
} else {
// 创建新视图并缓存,仅绑定一次点击事件
val newView = LayoutInflater.from(itemView.context)
.inflate(R.layout.item_time_tag, llContainer, false) as TextView
// 仅绑定一次点击事件(避免重复绑定导致多次触发)
newView.setOnClickListener {
onOptionItemClick(index)
}
optionViews.add(newView)
llContainer.addView(newView)
newView
}
// 3. 仅更新视图的文本和选中状态核心不重建视图只更新必要UI无闪烁
optionView.text = "${priceItem.day}"
optionView.isSelected = (index == selectedOptionPos)
}
}
// 抽取选项点击事件,统一处理(普通类型专用)
private fun onOptionItemClick(clickedIndex: Int) {
if (clickedIndex == selectedOptionPos) {
return // 点击已选中项,不做任何处理,避免无效刷新
}
// 1. 记录上一个选中位置用于仅更新上一个选中项的UI
val lastSelectedPos = selectedOptionPos
// 2. 更新当前选中位置
selectedOptionPos = clickedIndex
// 3. 仅更新上一个和当前选中项的UI局部刷新不刷新整个容器
updateOptionItemStatus(lastSelectedPos, false)
updateOptionItemStatus(clickedIndex, true)
// 4. 核心更新dataList中第0、1项的数据源仅更新对应数据不改动其他项
this@PurchaseOutfitsAdapter.updateSingleItemData()
// 5. 精准刷新仅刷新商品价格0和有效期至1不刷新其他视图
notifyItemChanged(0) // 仅刷新商品价格
notifyItemChanged(1) // 仅刷新有效期至
// ---------------------- 触发回调,传递整体数据(普通类型专用) ----------------------
val selectedItem = this@PurchaseOutfitsAdapter.getSelectedPriceItem()
val allOptions = this@PurchaseOutfitsAdapter.getAllPriceOptions()
// 非空判断:避免回调实例或选中数据为空导致崩溃
if (selectedItem != null && allOptions.isNotEmpty()) {
this@PurchaseOutfitsAdapter.mListener?.onDurationSelected(
selectedPos = clickedIndex,
selectedPriceItem = selectedItem,
allPriceOptions = allOptions,
)
}
}
// 仅更新单个选项的选中状态局部UI更新无视图重建
private fun updateOptionItemStatus(pos: Int, isSelected: Boolean) {
if (pos in optionViews.indices) {
optionViews[pos].isSelected = isSelected
}
}
}
}

View File

@@ -0,0 +1,200 @@
package com.xscm.modulemain.dialog
import android.app.Dialog
import android.content.Context
import android.content.Intent
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import com.blankj.utilcode.util.ActivityUtils.startActivity
import com.blankj.utilcode.util.ToastUtils
import com.xscm.modulemain.activity.user.activity.RechargeActivity
import com.xscm.modulemain.adapter.PurchaseOutfitsAdapter
import com.xscm.modulemain.databinding.DialogPurchaseOutfitsBinding
import com.xscm.moduleutil.bean.DecorateDetailBean
import com.xscm.moduleutil.bean.PersonaltyListBean
import com.xscm.moduleutil.color.ThemeableDrawableUtils
import com.xscm.moduleutil.http.BaseObserver
import com.xscm.moduleutil.http.RetrofitClient
import com.xscm.moduleutil.utils.ColorManager
import com.xscm.moduleutil.utils.ImageUtils
import com.xscm.moduleutil.widget.GiftAnimView
import io.reactivex.disposables.Disposable
/**
* 项目名称:羽声语音
* 时间2026/1/4 9:36
* 用途:装饰详情
*/
class PurchaseOutfitsDialog(context: Context) :
Dialog(context, com.xscm.moduleutil.R.style.BaseDialogStyleH) {
private var mBinding: DialogPurchaseOutfitsBinding =
DialogPurchaseOutfitsBinding.inflate(LayoutInflater.from(context))
var did: String = ""
var personaltyListBean: PersonaltyListBean? = null
var adapter: PurchaseOutfitsAdapter = PurchaseOutfitsAdapter()
private var mSelectedPriceBean: DecorateDetailBean.PriceListBean? = null
private var mCurrentBuyCount: String? = "1"
var imageBg2: GiftAnimView? = null
init {
setContentView(mBinding.root)
setupWindow()
}
private fun setupViews() {
RetrofitClient.getInstance()
.getDecorateDetail(did, object : BaseObserver<DecorateDetailBean>() {
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: DecorateDetailBean) {
if (t != null && t.decorate != null && t.userInfo != null) {
mBinding.tvRewardGift.text = t.userInfo!!.userCoin.toString()
mBinding.tvOutfitsN.text = t.decorate!!.title
// 2. 初始化RecyclerView
mBinding.rvOutfits.layoutManager = LinearLayoutManager(context)
val adapterDataList = t.convertToAdapterData(t.decorate, 0)
mBinding.rvOutfits.adapter = adapter
adapter.setData(
adapterDataList as MutableList<DecorateDetailBean.DecorateAdapterItem>,
t.decorate!!.priceList.isEmpty()
)
} else {
dismiss()
}
}
})
}
fun show(item: PersonaltyListBean?) {
personaltyListBean = item
did = item?.did.toString()
super.show()
initView()
setupViews()
}
private fun setupWindow() {
val window = window ?: return
window.setGravity(Gravity.BOTTOM)
window.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
window.setBackgroundDrawableResource(android.R.color.transparent)
val params = window.attributes
params.windowAnimations = com.xscm.moduleutil.R.style.BaseDialogStyleH
window.attributes = params
}
fun initView() {
setCancelable(true)
setCanceledOnTouchOutside(true)
ThemeableDrawableUtils.setThemeableRoundedBackground(
mBinding.tvOutfitsQd,
ColorManager.getInstance().primaryColorInt,
53
)
mBinding.tvOutfitsQd.setTextColor(ColorManager.getInstance().buttonColorInt)
imageBg2 = GiftAnimView(context)
val parentLayout = mBinding.ccl.parent as ViewGroup // 或者其他合适的父布局
parentLayout.post {
val parentWidth = parentLayout.width
val parentHeight = parentLayout.height
imageBg2!!.layoutParams = ViewGroup.LayoutParams(parentWidth, parentHeight)
}
imageBg2!!.visibility = View.GONE
parentLayout.addView(imageBg2)
// 设置确认按钮点击事件
mBinding.tvOutfitsQd.setOnClickListener {
RetrofitClient.getInstance().payDecorate(
did,
mSelectedPriceBean?.day.toString(),
mCurrentBuyCount,
object : BaseObserver<String>() {
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: String) {
ToastUtils.showLong("购买成功")
dismiss()
}
});
}
mBinding.cz.setOnClickListener {
//充值
startActivity(Intent(context, RechargeActivity::class.java))
}
if (personaltyListBean != null) {
if (personaltyListBean?.type == 12) {
mBinding.ivOutfits.visibility = View.GONE
mBinding.imJsk.visibility = View.VISIBLE
mBinding.imageHeadPortrait.visibility= View.GONE
mBinding.imQp.visibility=View.GONE
ImageUtils.loadHead(personaltyListBean?.base_image, mBinding.imJsk)
}else if (personaltyListBean?.type == 9) {
mBinding.ivOutfits.visibility = View.GONE
mBinding.imJsk.visibility = View.GONE
mBinding.imageHeadPortrait.visibility= View.GONE
mBinding.imQp.visibility=View.VISIBLE
ImageUtils.loadHead(personaltyListBean?.base_image, mBinding.imQp)
} else {
mBinding.ivOutfits.visibility = View.VISIBLE
mBinding.imJsk.visibility = View.GONE
mBinding.imQp.visibility=View.GONE
ImageUtils.loadHead(personaltyListBean?.base_image, mBinding.ivOutfits)
if (personaltyListBean?.type == 1) {
mBinding.imageHeadPortrait.visibility= View.VISIBLE
imageBg2!!.visibility = View.GONE
mBinding.imageHeadPortrait.stopAll()
mBinding.imageHeadPortrait.setSource(personaltyListBean?.play_image, 2)
}else if (personaltyListBean?.type == 2) {
mBinding.imageHeadPortrait.visibility= View.GONE
imageBg2!!.visibility = View.VISIBLE
imageBg2!!.previewEffectWith(personaltyListBean?.play_image)
}
}
mBinding.tvOutfitsName.text = personaltyListBean?.title
}
adapter.setOnDurationOptionSelectedListener(object :
PurchaseOutfitsAdapter.OnDurationOptionSelectedListener {
override fun onDurationSelected(
selectedPos: Int,
selectedPriceItem: DecorateDetailBean.PriceListBean,
allPriceOptions: List<DecorateDetailBean.PriceListBean>,
) {
mSelectedPriceBean = selectedPriceItem
}
override fun onBuyCountChange(count: Int) {
mCurrentBuyCount = count.toString()
}
})
}
}