1:将道具商城做成原生的,添加点击的时候展示动效
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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 = "" // 折扣字段
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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";//加入房间前检查
|
||||
|
||||
12
BaseModule/src/main/res/drawable/bg_amount_layout.xml
Normal file
12
BaseModule/src/main/res/drawable/bg_amount_layout.xml
Normal 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>
|
||||
7
BaseModule/src/main/res/drawable/btn_amount.xml
Normal file
7
BaseModule/src/main/res/drawable/btn_amount.xml
Normal 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>
|
||||
8
BaseModule/src/main/res/drawable/divider.xml
Normal file
8
BaseModule/src/main/res/drawable/divider.xml
Normal 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>
|
||||
39
BaseModule/src/main/res/layout/view_amount.xml
Normal file
39
BaseModule/src/main/res/layout/view_amount.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user