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

@@ -0,0 +1,174 @@
package com.xscm.moduleutil.bean
import com.google.gson.annotations.SerializedName
import java.util.ArrayList
/**
* 项目名称:羽声语音
* 时间2026/1/4 10:22
* 用途:装扮价格详情
*/
class DecorateDetailBean {
// 用户信息服务端返回的user_info字段
@SerializedName("user_info")
var userInfo: UserInfoDecorate? = UserInfoDecorate()
// 装饰商品核心信息服务端返回的decorate字段
@SerializedName("decorate")
var decorate: Decorate? = null
/**
* 用户信息内部类对应服务端user_info
*/
class UserInfoDecorate {
@SerializedName("user_id")
var userId: Int = 0
@SerializedName("user_coin")
var userCoin: String = ""
}
/**
* 装饰商品核心信息对应服务端decorate直接解析服务端返回数据
* 注意与之前适配器的Item模型解耦该类仅负责接收服务端数据不承担适配器布局类型职责
*/
class Decorate {
@SerializedName("title")
var title: String = "" // 商品名称(如“粉色花头”)
@SerializedName("price")
var price : String =""
@SerializedName("base_image")
var base_image : String =""
@SerializedName("price_list")
var priceList: List<PriceListBean> = ArrayList() // 价格/时长列表(服务端返回数组)
}
/**
* 价格/时长明细bean对应服务端price_list中的单个对象
* 可直接解析服务端返回的每个价格项数据同时适配适配器的PriceItem模型
*/
class PriceListBean {
@SerializedName("price")
var price: String = "" // 现价
@SerializedName("original_price")
var originalPrice: String = "" // 原价
@SerializedName("discount")
var discount: String = "" // 折扣如“5.0”)
@SerializedName("day")
var day: Int = 0 // 有效天数
@SerializedName("month")
var month: String = "" // 有效月数
@SerializedName("end_time")
var endTime: String = "" // 有效期截止时间
}
// ---------------------- 适配器适配相关:转换方法 + 适配器所需模型 ----------------------
/**
* 适配器的Item数据模型密封类区分单行/多选项)
* 与服务端数据模型解耦专门用于RecyclerView适配器布局
*/
sealed class DecorateAdapterItem {
// 单行信息类型(如“商品价格”“有效期至”)
data class SingleItem(
val label: String, // 左侧标签文字
val content: String // 右侧内容文字
) : DecorateAdapterItem()
// 购买时长多选项类型(承载所有时长选项)
data class MultiOptionItem(
val options: List<PriceListBean> // 直接复用PriceListBean已适配服务端数据
) : DecorateAdapterItem()
// type=12专用购买次数加减按钮
data class BuyCountItem(
val initialCount: Int,
val unitPrice: String
) : DecorateAdapterItem()
}
/**
* 转换方法将服务端数据Decorate转换为适配器所需数据列表List<DecorateAdapterItem>
* 实现服务端数据与适配器的桥接,方便适配器直接使用
* @param defaultSelectedPos 默认选中的时长选项下标默认0即第一个选项
*/
fun convertToAdapterData(
decorate: Decorate?,
defaultSelectedPos: Int = 0
): List<DecorateAdapterItem> {
val adapterDataList = mutableListOf<DecorateAdapterItem>()
if (decorate == null ) {
return adapterDataList
}
if ( decorate.priceList.isEmpty()){
// ---------- type=12解析单个字段无price_list新增购买次数、商品总价 ----------
val unitPrice = decorate.price// 直接取Decorate的singlePrice服务端返回的单价
val unitPriceStr = unitPrice // 格式化单价,避免小数异常
// 2. 商品单价单行项取decorate.singlePrice
adapterDataList.add(
DecorateAdapterItem.SingleItem(
label = "商品单价",
content = unitPriceStr
)
)
// 3. 购买次数type=12专用初始数量1传入单价用于计算总价
adapterDataList.add(
DecorateAdapterItem.BuyCountItem(
initialCount = 1,
unitPrice = unitPrice
)
)
// 4. 商品总价单行项初始单价×1
adapterDataList.add(
DecorateAdapterItem.SingleItem(
label = "商品总价",
content = unitPriceStr // 初始总价=单价×1
)
)
}else {
// 安全获取默认选中项(防止下标越界)
val selectedPos = if (defaultSelectedPos in decorate.priceList.indices) {
defaultSelectedPos
} else {
0
}
val selectedPriceItem = decorate.priceList[selectedPos]
// 1. 添加“商品价格”单行项(取选中项的现价)
adapterDataList.add(
DecorateAdapterItem.SingleItem(
label = "商品价格",
content = "${selectedPriceItem.price}" // 拼接货币符号,优化展示
)
)
// 2. 添加“有效期至”单行项(取选中项的截止时间)
adapterDataList.add(
DecorateAdapterItem.SingleItem(
label = "有效期至",
content = selectedPriceItem.endTime
)
)
// 3. 添加“购买时长”多选项(承载所有价格/时长列表)
adapterDataList.add(
DecorateAdapterItem.MultiOptionItem(
options = decorate.priceList
)
)
}
return adapterDataList
}
}

View File

@@ -14,7 +14,7 @@ class PersonaltyListBean {
var price: Int = 0 // 实际价格(金币)
var special_num: Int = 0 // 靓号
var original_price: Int = 0 // 原价
var discount: Int = 0 // 折扣
var discount: Double = 0.0 // 折扣
var discount_str: String = "" // 折扣字段

View File

@@ -13,7 +13,6 @@ import com.xscm.moduleutil.widget.Constants;
import java.util.List;
import io.reactivex.Observable;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Field;
@@ -470,6 +469,13 @@ public interface ApiServer {
@GET(Constants.GET_DECORATE)
Call<BaseModel<List<ZhuangBanShangChengBean>>> getDecorateList(@Query("type") String type);
@GET(Constants.GET_DECORATE_DETAIL)
Call<BaseModel<DecorateDetailBean>> getDecorateDetail(@Query("did") String id);
@FormUrlEncoded
@POST(Constants.POST_PAY_DECORATE)
Call<BaseModel<String>> payDecorate(@Field("did") String id, @Field("day") String day,@Field("num") String num);
@FormUrlEncoded
@POST(Constants.POST_GZ)
Call<BaseModel<String>> userGuanz(@Field("user_id") String userId, @Field("type") String type);

View File

@@ -41,7 +41,6 @@ import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EventListener;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -2721,6 +2720,53 @@ public class RetrofitClient {
});
}
public void payDecorate(String did, String day, String num, BaseObserver<String> observer) {
sApiServer.payDecorate(did, day, num).enqueue(new Callback<BaseModel<String>>() {
@Override
public void onResponse(Call<BaseModel<String>> call, Response<BaseModel<String>> response) {
onNextRetu(response, observer);
}
@Override
public void onFailure(Call<BaseModel<String>> call, Throwable t) {
LogUtils.e("payDecorate", t.getMessage());
}
});
}
public void getDecorateDetail(String did, BaseObserver<DecorateDetailBean> observer) {
sApiServer.getDecorateDetail(did).enqueue(new Callback<BaseModel<DecorateDetailBean>>() {
@Override
public void onResponse(Call<BaseModel<DecorateDetailBean>> call, Response<BaseModel<DecorateDetailBean>> response) {
if (response.code() == 200) {
BaseModel<DecorateDetailBean> listBaseModel = response.body();
if (listBaseModel.getCode() == 1) {
observer.onNext(listBaseModel.getData());
} else if (listBaseModel.getCode() == 301) {
setCode301(listBaseModel.getMsg());
} else if (listBaseModel.getCode() == 0) {
observer.onNext(new DecorateDetailBean());
ToastUtils.showShort(listBaseModel.getMsg());
}
} else {
observer.onNext(new DecorateDetailBean());
ToastUtils.showLong("请求装饰详情错误", response.code());
LogUtils.e("getDecorateDetail", response.message());
}
}
@Override
public void onFailure(Call<BaseModel<DecorateDetailBean>> call, Throwable t) {
LogUtils.e("getDecorateDetail", t.getMessage());
}
});
}
public void getDecorateList(String type, BaseObserver<List<ZhuangBanShangChengBean>> observer) {
sApiServer.getDecorateList(type).enqueue(new Callback<BaseModel<List<ZhuangBanShangChengBean>>>() {
@Override

View File

@@ -0,0 +1,131 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import com.xscm.moduleutil.R;
/**
* 项目名称:羽声语音
* 时间2026/1/4 16:09
* 用途:自定义组件:购买数量,带减少增加按钮
*/
public class AmountView extends LinearLayout implements View.OnClickListener, TextWatcher {
private static final String TAG = "AmountView";
private int amount = 1; //购买数量
private int goods_storage = Integer.MAX_VALUE; //商品库存
private OnAmountChangeListener mListener;
private EditText etAmount;
private Button btnDecrease;
private Button btnIncrease;
public AmountView(Context context) {
this(context, null);
}
public AmountView(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.view_amount, this);
etAmount = (EditText) findViewById(R.id.etAmount);
btnDecrease = (Button) findViewById(R.id.btnDecrease);
btnIncrease = (Button) findViewById(R.id.btnIncrease);
btnDecrease.setOnClickListener(this);
btnIncrease.setOnClickListener(this);
etAmount.addTextChangedListener(this);
TypedArray obtainStyledAttributes = getContext().obtainStyledAttributes(attrs, R.styleable.AmountView);
int btnWidth = obtainStyledAttributes.getDimensionPixelSize(R.styleable.AmountView_btnWidth, LayoutParams.WRAP_CONTENT);
int tvWidth = obtainStyledAttributes.getDimensionPixelSize(R.styleable.AmountView_tvWidth, 80);
int tvTextSize = obtainStyledAttributes.getDimensionPixelSize(R.styleable.AmountView_tvTextSize, 0);
int btnTextSize = obtainStyledAttributes.getDimensionPixelSize(R.styleable.AmountView_btnTextSize, 0);
obtainStyledAttributes.recycle();
LayoutParams btnParams = new LayoutParams(btnWidth, LayoutParams.MATCH_PARENT);
btnDecrease.setLayoutParams(btnParams);
btnIncrease.setLayoutParams(btnParams);
if (btnTextSize != 0) {
btnDecrease.setTextSize(TypedValue.COMPLEX_UNIT_PX, btnTextSize);
btnIncrease.setTextSize(TypedValue.COMPLEX_UNIT_PX, btnTextSize);
}
LayoutParams textParams = new LayoutParams(tvWidth, LayoutParams.MATCH_PARENT);
etAmount.setLayoutParams(textParams);
if (tvTextSize != 0) {
etAmount.setTextSize(tvTextSize);
}
}
public void setOnAmountChangeListener(OnAmountChangeListener onAmountChangeListener) {
this.mListener = onAmountChangeListener;
}
public void setGoods_storage(int goods_storage) {
this.goods_storage = goods_storage;
}
@Override
public void onClick(View v) {
int i = v.getId();
if (i == R.id.btnDecrease) {
if (amount > 1) {
amount--;
etAmount.setText(amount + "");
}
} else if (i == R.id.btnIncrease) {
if (amount < goods_storage) {
amount++;
etAmount.setText(amount + "");
}
}
etAmount.clearFocus();
if (mListener != null) {
mListener.onAmountChange(this, amount);
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (s.toString().isEmpty())
return;
amount = Integer.valueOf(s.toString());
if (amount > goods_storage) {
etAmount.setText(goods_storage + "");
return;
}
if (mListener != null) {
mListener.onAmountChange(this, amount);
}
}
public interface OnAmountChangeListener {
void onAmountChange(View view, int amount);
}
}

View File

@@ -259,6 +259,8 @@ public class Constants {
public static final String LIKE_ALBUM = "/api/User/like_album";//相册点赞
public static final String GET_PERSONALTY = "/api/Decorate/get_type_list";//装扮类型列表
public static final String GET_DECORATE = "/api/Decorate/user_decorate";//装扮详情
public static final String GET_DECORATE_DETAIL = "/api/Decorate/get_decorate_detail";//装饰价格详情
public static final String POST_PAY_DECORATE = "/api/Decorate/pay_decorate";//购买装扮
public static final String SET_USER_DECORATE = "/api/Decorate/set_user_decorate";//用户装扮
public static final String JOIN_ROOM = "/api/Room/join_room";//加入房间
public static final String BEFORE_JOIN_ROOM_CHECK = "/api/Room/before_join_room_check";//加入房间前检查

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FFFFFF" />
<stroke
android:width="1dp"
android:color="@color/divider" />
<padding
android:bottom="1dp"
android:left="1dp"
android:right="1dp"
android:top="1dp" />
</shape>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@color/divider" />
<item android:state_enabled="false" android:drawable="@color/divider" />
<item android:drawable="@android:color/white" />
</selector>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size
android:width="0.5dp"/>
<solid android:color="@color/divider"/>
</shape>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="true"
android:divider="@drawable/divider"
android:background="@drawable/bg_amount_layout"
android:showDividers="middle"
android:orientation="horizontal">
<Button
android:id="@+id/btnDecrease"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:background="@drawable/btn_amount"
android:text="-"/>
<EditText
android:id="@+id/etAmount"
android:layout_width="0dp"
android:layout_height="match_parent"
android:minWidth="60dp"
android:layout_weight="2"
android:background="@null"
android:inputType="number"
android:gravity="center"
android:text="1"/>
<Button
android:id="@+id/btnIncrease"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:background="@drawable/btn_amount"
android:text="+"/>
</LinearLayout>

View File

@@ -487,6 +487,14 @@
<attr name="tagMargin" format="dimension" />
</declare-styleable>
<declare-styleable name="AmountView">
<!-- 左右2边+-按钮的宽度 -->
<attr name="btnWidth" format="dimension"/>
<!-- 中间TextView的宽度 -->
<attr name="tvWidth" format="dimension"/>
<!-- <attr name="tvColor" format="color"/> -->
<attr name="tvTextSize" format="dimension"/>
<attr name="btnTextSize" format="dimension"/>
</declare-styleable>
</resources>

View File

@@ -12,7 +12,7 @@
<color name="color_DADADA">#DADADA</color>
<color name="color_BB8BE2">#BB8BE2</color>
<color name="color_999999">#999999</color>
<color name="divider">#ffd2d2d2</color>
<color name="colorPrimaryDark">#FFFFBB00</color>
<color name="colorAccent">#FFFFBB00</color>
<color name="color_666666">#666666</color>

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()
}
})
}
}

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 选中状态 -->
<item android:state_selected="true">
<shape android:shape="rectangle">
<!-- 红色背景 -->
<solid android:color="#F3FDF5"/>
<!-- 绿色边框 -->
<stroke
android:width="1dp"
android:color="#4CAF50"/>
<!-- 圆角 -->
<corners android:radius="10dp"/>
</shape>
</item>
<!-- 未选中状态 -->
<item android:state_selected="false">
<shape android:shape="rectangle">
<!-- 白色背景 -->
<solid android:color="#ffffff"/>
<!-- 绿色边框 -->
<stroke
android:width="1dp"
android:color="#4CAF50"/>
<!-- 圆角 -->
<corners android:radius="10dp"/>
</shape>
</item>
</selector>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- res/color/text_color_selector.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:color="#4CAF50"/>
<item android:color="#000000"/>
</selector>

View File

@@ -0,0 +1,171 @@
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_r16_fff"
android:paddingVertical="@dimen/dp_15">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/ll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/im_jsk"
android:layout_width="60dp"
android:layout_height="wrap_content"
android:scaleType="fitCenter"
android:visibility="gone"
tools:visibility="visible"
tools:src="@drawable/ic_launcher_background" />
<ImageView
android:id="@+id/im_qp"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:scaleType="fitCenter"
android:visibility="gone"
tools:visibility="visible"
tools:src="@drawable/ic_launcher_background" />
<com.xscm.moduleutil.widget.CircularImage
android:id="@+id/iv_outfits"
android:layout_width="@dimen/dp_70"
android:layout_height="@dimen/dp_70"
tools:src="@drawable/ic_launcher_background" />
<com.xscm.moduleutil.widget.AvatarFrameView
android:id="@+id/image_headPortrait"
android:layout_width="@dimen/dp_72"
android:layout_height="@dimen/dp_72"
app:layout_constraintTop_toTopOf="@+id/iv_outfits"
app:layout_constraintStart_toStartOf="@+id/iv_outfits"
app:layout_constraintEnd_toEndOf="@+id/iv_outfits"
app:layout_constraintBottom_toBottomOf="@+id/iv_outfits"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/tv_outfits_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dp_5"
android:textColor="@color/color_FF333333"
android:textSize="@dimen/sp_13"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ll"
tools:text="套装名称" />
<TextView
android:id="@+id/tv_commodity_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_26"
android:layout_marginTop="@dimen/dp_5"
android:text="商品名称"
android:textColor="@color/color_FF333333"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_outfits_name" />
<TextView
android:id="@+id/tv_outfits_n"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/dp_26"
android:textColor="@color/color_FF333333"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/tv_commodity_name"
tools:text="粉色头花" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_outfits"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/dp_10"
android:layout_marginVertical="@dimen/dp_10"
android:paddingBottom="@dimen/dp_10"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_commodity_name" />
<LinearLayout
android:id="@+id/ll_reward_gift"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rv_outfits">
<TextView
android:id="@+id/tv_reward_gift"
android:layout_width="wrap_content"
android:layout_height="@dimen/dp_16"
android:layout_marginStart="@dimen/dp_2"
android:drawableStart="@mipmap/jinb"
android:textColor="@color/color_FF333333"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rv_outfits"
tools:text="23" />
<TextView
android:id="@+id/cz"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_18"
android:text="去充值"
android:textColor="#0DFFB9"
android:textSize="@dimen/sp_12"
app:layout_constraintBottom_toBottomOf="@+id/tv_reward_gift"
app:layout_constraintStart_toEndOf="@+id/tv_reward_gift"
app:layout_constraintTop_toTopOf="@+id/tv_reward_gift" />
</LinearLayout>
<TextView
android:id="@+id/tv_outfits_qd"
android:layout_width="@dimen/dp_158"
android:layout_height="@dimen/dp_42"
android:layout_marginTop="@dimen/dp_10"
android:layout_marginBottom="@dimen/dp_14"
android:background="@drawable/bg_r53_0dffb9"
android:gravity="center"
android:text="确认支付"
android:textColor="@color/white"
android:textSize="@dimen/sp_14"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ll_reward_gift" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/ccl"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_350"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?><!-- 根布局横向排列适配RecyclerView列表项与item_single_info.xml布局风格保持一致 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingTop="12dp"
android:paddingEnd="16dp"
android:paddingBottom="12dp">
<!-- 左侧购买次数标签与item_single_info.xml的标签样式保持一致保证视觉统一 -->
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:text="购买次数"
android:textColor="#333333"
android:textSize="16sp" />
<!-- 右侧:加减按钮 + 数量显示(横向排列,居中对齐) -->
<com.xscm.moduleutil.widget.AmountView
android:id="@+id/amount_view"
app:btnTextSize="14sp"
app:btnWidth="36dp"
app:tvWidth="50dp"
android:layout_width="0dp"
android:layout_weight="0.7"
android:layout_height="36dp"
android:layout_centerInParent="true"
android:layout_gravity="right"
/>
</LinearLayout>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="购买时长"
android:textSize="16sp"
android:layout_marginBottom="8dp"/>
<LinearLayout
android:id="@+id/ll_options_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
/>
</LinearLayout>

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp">
<TextView
android:id="@+id/tv_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="商品价格"
android:textSize="16sp" />
<!-- 右侧内容 + 图标按钮(横向排列) -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<!-- 右侧内容文字 -->
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:textColor="@color/color_FF333333"
android:textSize="16sp"
tools:text="25"/>
<!-- 商品价格专属图标按钮(默认隐藏) -->
<ImageView
android:id="@+id/iv_price_icon"
android:layout_width="14dp"
android:layout_height="14dp"
android:src="@mipmap/jinb"
android:visibility="gone"
tools:visibility="visible"/> <!-- 默认隐藏 -->
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="80dp"
android:layout_height="40dp"
android:layout_marginHorizontal="@dimen/dp_10"
xmlns:tools="http://schemas.android.com/tools"
android:gravity="center"
tools:text="30天"
android:textSize="16sp"
android:textColor="@drawable/text_color_selector"
android:background="@drawable/bg_time_tag_selector"
android:clickable="true"
android:focusable="true" />