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>