1:羽声新版本

This commit is contained in:
2025-10-24 17:55:15 +08:00
parent a809b02ebb
commit 529aae1fcf
821 changed files with 29411 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,24 @@
package com.xscm.moduleutil.base;
import android.app.Activity;
/**
*@author qx
*@data 2025/9/20
*@description: 模块之间的通讯接口
*/
public interface AppStateListener {
void onAppForeground();
void onAppBackground();
void onRoomActivityCreated(Activity roomActivity);
void onRoomActivityDestroyed();
boolean isRoomActivityActive();
void setFloatingWindowVisible(boolean visible);
boolean isFloatingWindowVisible();
// 新增方法
boolean shouldShowSplash();
void setShouldShowSplash(boolean shouldShow);
boolean isAppInBackground();
void setAppInBackground(boolean inBackground);
}

View File

@@ -0,0 +1,112 @@
package com.xscm.moduleutil.base;
import android.app.Activity;
import com.xscm.moduleutil.bean.room.RoomInfoResp;
import java.lang.ref.WeakReference;
/**
*@author qx
*@data 2025/9/20
*@description: 应用状态管理的单例类
*/
// 在 common 模块中
public class AppStateManager implements AppStateListener {
private static AppStateManager instance;
private boolean isAppInBackground = true;
private boolean shouldShowSplash = true;
private WeakReference<Activity> roomActivityRef;
private boolean isFloatingWindowVisible = false;
private boolean isRoomActivityMinimized = false;
private AppStateManager() {
// 私有构造函数
}
public static synchronized AppStateManager getInstance() {
if (instance == null) {
instance = new AppStateManager();
}
return instance;
}
@Override
public boolean shouldShowSplash() {
return shouldShowSplash;
}
@Override
public void setShouldShowSplash(boolean shouldShow) {
this.shouldShowSplash = shouldShow;
}
@Override
public boolean isAppInBackground() {
return isAppInBackground;
}
@Override
public void setAppInBackground(boolean inBackground) {
this.isAppInBackground = inBackground;
}
@Override
public void onRoomActivityCreated(Activity roomActivity) {
roomActivityRef = new WeakReference<>(roomActivity);
}
@Override
public void onRoomActivityDestroyed() {
roomActivityRef = null;
}
@Override
public boolean isRoomActivityActive() {
Activity activity = getRoomActivity();
return activity != null && !activity.isFinishing();
}
private Activity getRoomActivity() {
return roomActivityRef != null ? roomActivityRef.get() : null;
}
@Override
public void setFloatingWindowVisible(boolean visible) {
this.isFloatingWindowVisible = visible;
}
@Override
public boolean isFloatingWindowVisible() {
return isFloatingWindowVisible;
}
@Override
public void onAppForeground() {
// 应用进入前台时的处理
setAppInBackground(false);
}
@Override
public void onAppBackground() {
// 应用进入后台时的处理
setAppInBackground(true);
}
// 新增方法设置RoomActivity为最小化状态
public void setRoomActivityMinimized(boolean minimized) {
this.isRoomActivityMinimized = minimized;
}
// 新增方法检查RoomActivity是否处于最小化状态
public boolean isRoomActivityMinimized() {
return isRoomActivityMinimized;
}
private RoomInfoResp roomInfoResp;
public void setRoomInfo(RoomInfoResp roomInfoResp) {
// 处理RoomInfoResp对象
this.roomInfoResp = roomInfoResp;
}
public RoomInfoResp getRoomInfo() {
return roomInfoResp;
}
}

View File

@@ -0,0 +1,543 @@
package com.xscm.moduleutil.base;
import static android.app.PendingIntent.getActivity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import com.alibaba.android.arouter.launcher.ARouter;
import com.blankj.utilcode.util.ActivityUtils;
import com.blankj.utilcode.util.LogUtils;
import com.blankj.utilcode.util.ToastUtils;
import com.xscm.moduleutil.bean.room.RoomInfoResp;
import com.xscm.moduleutil.bean.room.RoomOnline;
import com.xscm.moduleutil.bean.room.RoomOnlineBean;
import com.xscm.moduleutil.event.RoomOutEvent;
import com.xscm.moduleutil.http.BaseObserver;
import com.xscm.moduleutil.http.RetrofitClient;
import com.xscm.moduleutil.listener.MessageListenerSingleton;
import com.xscm.moduleutil.rtc.AgoraManager;
import com.xscm.moduleutil.utils.ARouteConstants;
import com.xscm.moduleutil.utils.SpUtil;
import com.xscm.moduleutil.utils.logger.Logger;
import org.greenrobot.eventbus.EventBus;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import io.reactivex.disposables.Disposable;
/**
* 房间管理器
* 统一处理房间数据获取、进入房间和退出房间的逻辑
*/
public class RoomManager {
private static final String TAG = "RoomManager";
private static RoomManager instance;
// 房间数据缓存
private Map<String, RoomInfoResp> roomDataCache = new ConcurrentHashMap<>();
private Map<String, Long> cacheTimestamps = new ConcurrentHashMap<>();
// 缓存有效期5分钟
private static final long CACHE_DURATION = 5 * 60 * 1000;
private RoomManager() {
}
public static synchronized RoomManager getInstance() {
if (instance == null) {
instance = new RoomManager();
}
return instance;
}
/**
* 进入房间 - 自动获取房间数据
*
* @param context 上下文
* @param roomId 房间ID
*/
public void enterRoom(Context context, String roomId) {
enterRoom(context, roomId, null, null);
}
/**
* 进入房间 - 使用密码
*
* @param context 上下文
* @param roomId 房间ID
* @param password 房间密码
*/
public void enterRoom(Context context, String roomId, String password) {
enterRoom(context, roomId, password, null);
}
/**
* 进入房间 - 使用缓存数据
*
* @param context 上下文
* @param roomId 房间ID
* @param password 房间密码
* @param cachedData 缓存的房间数据
*/
public void enterRoom(Context context, String roomId, String password, RoomInfoResp cachedData) {
if (TextUtils.isEmpty(roomId)) {
ToastUtils.showShort("房间ID不能为空");
return;
}
// 检查是否有有效的缓存数据
RoomInfoResp roomInfo = cachedData != null ? cachedData : getCachedRoomData(roomId);
if (roomInfo != null) {
// 使用缓存数据直接进入房间
navigateToRoom(context, roomId, password, roomInfo, false);
} else {
// 获取房间数据后进入房间
fetchRoomDataAndEnter(context, roomId, password);
}
}
/**
* 获取房间数据并进入房间
*
* @param context 上下文
* @param roomId 房间ID
* @param password 房间密码
*/
public void fetchRoomDataAndEnter(Context context, String roomId, String password) {
// 显示加载提示
// 这里可以根据需要添加加载对话框
if (CommonAppContext.getInstance().isRoomJoininj){
return;
}
CommonAppContext.getInstance().isRoomJoininj=true;
// 检查是否有有效的缓存数据
// RoomInfoResp roomInfo = getCachedRoomData(roomId);
// 检查是否是当前房间且用户在线
// boolean isCurrentRoom = isCurrentRoom(roomId);
if (CommonAppContext.getInstance().playId == null) {
fetchAndJoinRoom(context, roomId, password);
} else {
if (!CommonAppContext.getInstance().playId.equals(roomId)) {
MessageListenerSingleton.getInstance().joinGroup(roomId);
exitRoom(CommonAppContext.getInstance().playId);
CommonAppContext.getInstance().isShow = false;
CommonAppContext.getInstance().isPlaying = false;
CommonAppContext.getInstance().isRoomJoininj=false;
EventBus.getDefault().post(new RoomOutEvent());
} else if (CommonAppContext.getInstance().lable_id.equals("6")) {
upInfo(context, roomId, password, true, null, true);
return;
}
isUserOnline(context, roomId, password, null);
}
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// Thread.currentThread().interrupt();
// }
// 如果是当前房间且用户在线,直接跳转到房间页面,仅更新数据
// // 获取房间数据
// MessageListenerSingleton.getInstance().joinGroup(roomId);
// // 等待一段时间确保退出完成
// try {
// Thread.sleep(500);
// } catch (InterruptedException e) {
// Thread.currentThread().interrupt();
// }
// RetrofitClient.getInstance().roomGetIn(roomId, password, new BaseObserver<RoomInfoResp>() {
//
// @Override
// public void onSubscribe(Disposable d) {
// }
//
// @Override
// public void onNext(RoomInfoResp resp) {
// String appId = CommonAppContext.getInstance().getCurrentEnvironment().getSwSdkAppId();
// String token = resp.getUser_info().getAgora_token(); // 如果启用了鉴权才需要
// String roomId = resp.getRoom_info().getRoom_id(); // 房间 ID
// String rtm_token=resp.getUser_info().getAgora_rtm_token();
// SpUtil.setRtmToken(rtm_token);
// int uid = SpUtil.getUserId(); // 0 表示由 Agora 自动生成 UID
// boolean enableMic = false; // 是否开启麦克风
// boolean enableJs=false; // 是否开启角色
// if (resp.getUser_info().getPit_number()!=0){
// enableJs=true;
// }
// LogUtils.e("token",token);
// LogUtils.e("roomId:",roomId);
//// 初始化 Agora 并加入房间
// AgoraManager.getInstance(context)
// .joinRoom(token, roomId, uid, enableMic,enableJs);
// cacheRoomData(roomId, resp);
// navigateToRoom(context, roomId, password, resp);
// }
// });
// 临时实现 - 直接跳转(因为缺少具体的网络请求代码)
// navigateToRoom(context, roomId, password, null);
}
private void upInfo(Context context, String roomId, String password, boolean isOnline, RoomInfoResp roomInfo, boolean isCurrentRoom) {
if (isOnline) {
navigateToRoom(context, roomId, password, roomInfo, isOnline);
} else {
// CommonAppContext.getInstance().isShow = false;
// CommonAppContext.getInstance().isPlaying = false;
// EventBus.getDefault().post(new RoomOutEvent());
// try {
// Thread.sleep(300);
// } catch (InterruptedException e) {
// Thread.currentThread().interrupt();
// }
fetchAndJoinRoom(context, roomId, password);
}
// if (isCurrentRoom&& isOnline) {
// if (roomInfo != null) {
// navigateToRoom(context, roomId, password, roomInfo);
// } else {
// // 即使在线,如果没有缓存数据,也需要获取数据
// fetchAndJoinRoom(context, roomId, password);
// }
// return;
// }
// 如果有缓存数据且用户在线,使用缓存数据进入房间
// if (roomInfo != null && isOnline) {
// RetrofitClient.getInstance().postRoomInfo(roomId, new BaseObserver<RoomInfoResp>() {
//
// @Override
// public void onSubscribe(Disposable d) {
//
// }
//
// @Override
// public void onNext(RoomInfoResp roomInfoResp) {
//// cacheRoomData(roomId, roomInfo);
// navigateToRoom(context, roomId, password, roomInfoResp);
// }
// });
// cacheRoomData(roomId, roomInfo);
// navigateToRoom(context, roomId, password, roomInfo);
return;
// }
// 其他情况,获取新的房间数据并加入房间
// fetchAndJoinRoom(context, roomId, password);
}
/**
* 获取新的房间数据并加入房间
*
* @param context 上下文
* @param roomId 房间ID
* @param password 房间密码
*/
private void fetchAndJoinRoom(Context context, String roomId, String password) {
// 获取房间数据
// 等待一段时间确保退出完成
try {
Thread.sleep(300);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
navigateToRoom(context, roomId, password, null, false);
// RetrofitClient.getInstance().roomGetIn(roomId, password, new BaseObserver<RoomInfoResp>() {
//
// @Override
// public void onSubscribe(Disposable d) {
// }
//
// @Override
// public void onNext(RoomInfoResp resp) {
// String appId = CommonAppContext.getInstance().getCurrentEnvironment().getSwSdkAppId();
// String token = resp.getUser_info().getAgora_token(); // 如果启用了鉴权才需要
// String roomId = resp.getRoom_info().getRoom_id(); // 房间 ID
// String rtm_token=resp.getUser_info().getAgora_rtm_token();
// SpUtil.setRtmToken(rtm_token);
// int uid = SpUtil.getUserId(); // 0 表示由 Agora 自动生成 UID
// boolean enableMic = false; // 是否开启麦克风
// boolean enableJs=false; // 是否开启角色
// if (resp.getUser_info().getPit_number()!=0){
// enableJs=true;
// }
// LogUtils.e("token",token);
// LogUtils.e("roomId:",roomId);
//// 初始化 Agora 并加入房间
// AgoraManager.getInstance(context)
// .joinRoom(token, roomId, uid, enableMic,enableJs);
// cacheRoomData(roomId, resp);
// navigateToRoom(context, roomId, password, resp);
// }
// });
}
/**
* 检查是否是当前房间
*
* @param roomId 房间ID
* @return true表示是当前房间false表示不是
*/
private boolean isCurrentRoom(String roomId) {
// 这里应该实现检查是否是当前房间的逻辑
// 可以通过检查当前Activity或者通过全局变量等方式实现
// 目前返回false需要根据实际需求实现具体逻辑
RoomInfoResp roomInfo = getCachedRoomData(roomId);
if (roomInfo != null) {
if (roomInfo.getRoom_info().getRoom_id().equals(roomId)) {
return true;
} else {
return false;
}
}
return false;
}
/**
* 跳转到房间页面
*
* @param context 上下文
* @param roomId 房间ID
* @param password 房间密码
* @param roomInfo 房间信息
*/
private void navigateToRoom(Context context, String roomId, String password, RoomInfoResp roomInfo, boolean isOnline) {
try {
// 构建跳转参数
Bundle bundle = new Bundle();
bundle.putString("roomId", roomId);
bundle.putBoolean("isOnline", isOnline);
if (!TextUtils.isEmpty(password)) {
bundle.putString("password", password);
}
if (roomInfo != null) {
// bundle.putSerializable("roomInfo", roomInfo);
}
// 使用ARouter跳转到房间页面
ARouter.getInstance()
.build(ARouteConstants.ROOM_DETAILS)
.with(bundle)
.navigation(context);
} catch (Exception e) {
Logger.e(TAG, "跳转房间页面失败: " + e.getMessage());
}
}
/**
* 缓存房间数据
*
* @param roomId 房间ID
* @param roomInfo 房间信息
*/
public void cacheRoomData(String roomId, RoomInfoResp roomInfo) {
if (TextUtils.isEmpty(roomId) || roomInfo == null) {
return;
}
// 清除所有现有的缓存数据
roomDataCache.clear();
cacheTimestamps.clear();
roomDataCache.put(roomId, roomInfo);
cacheTimestamps.put(roomId, System.currentTimeMillis());
}
/**
* 获取缓存的房间数据
*
* @param roomId 房间ID
* @return 房间信息如果缓存无效则返回null
*/
public RoomInfoResp getCachedRoomData(String roomId) {
if (TextUtils.isEmpty(roomId)) {
return null;
}
Long timestamp = cacheTimestamps.get(roomId);
if (timestamp == null) {
return null;
}
// 检查缓存是否过期
if (System.currentTimeMillis() - timestamp > CACHE_DURATION) {
// 缓存过期,清除数据
roomDataCache.remove(roomId);
cacheTimestamps.remove(roomId);
return null;
}
return roomDataCache.get(roomId);
}
/**
* 检查用户是否在线
*
* @param roomId 房间ID
* @return true表示用户在线false表示不在线
*/
private boolean isUserOnline(Context context, String roomId, String password, RoomInfoResp roomInfo) {
// 这里应该实现检查用户是否在线的逻辑
// 可以通过检查Agora是否还在房间中或者通过服务端接口查询用户状态等方式实现
// 目前返回false需要根据实际需求实现具体逻辑
// boolean isCurrentRoom=isCurrentRoom(roomId);
// try {
// Thread.sleep(300);
// } catch (InterruptedException e) {
// Thread.currentThread().interrupt();
// }
final boolean[] isOnline = {false};
RetrofitClient.getInstance().getRoomOnline(roomId, "1", "50", new BaseObserver<RoomOnline>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(RoomOnline roomOnline) {
try {
if (roomOnline != null) {
if (roomOnline.getOn_pit() != null) {
for (RoomOnlineBean roomOnlineBean : roomOnline.getOn_pit()) {
if (roomOnlineBean.getUser_id() == SpUtil.getUserId()) {
isOnline[0] = true;
break;
}
}
}
if (roomOnline.getOff_pit() != null) {
for (RoomOnlineBean roomOnlineBean : roomOnline.getOff_pit()) {
if (roomOnlineBean.getUser_id() == SpUtil.getUserId()) {
isOnline[0] = true;
break;
}
}
}
upInfo(context, roomId, password, isOnline[0], roomInfo, true);
} else {
isOnline[0] = false;
}
} catch (Exception e) {
// 捕获所有可能的异常,避免崩溃
e.printStackTrace();
isOnline[0] = false;
// 即使出现异常也继续执行
upInfo(context, roomId, password, isOnline[0], roomInfo, true);
}
}
});
return isOnline[0];
}
/**
* 清除指定房间的缓存数据
*
* @param roomId 房间ID
*/
public void clearRoomCache(String roomId) {
if (!TextUtils.isEmpty(roomId)) {
roomDataCache.remove(roomId);
cacheTimestamps.remove(roomId);
}
}
/**
* 清除所有房间缓存数据
*/
public void clearAllRoomCache() {
roomDataCache.clear();
cacheTimestamps.clear();
}
/**
* 退出房间
*
* @param roomId 房间ID
*/
public void exitRoom(String roomId) {
// 清除该房间的缓存数据
clearRoomCache(roomId);
// 可以在这里添加其他退出房间的逻辑
// 例如:通知服务器用户已退出、清理房间相关资源等
RetrofitClient.getInstance().quitRoom(roomId, SpUtil.getUserId() + "", new BaseObserver<String>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(String s) {
}
});
Logger.d(TAG, "退出房间: " + roomId);
}
/**
* 批量退出房间
*
* @param roomIds 房间ID列表
*/
public void exitRooms(String... roomIds) {
if (roomIds != null) {
for (String roomId : roomIds) {
exitRoom(roomId);
}
}
}
/**
* 获取房间缓存状态
*
* @param roomId 房间ID
* @return 缓存状态信息
*/
public String getRoomCacheStatus(String roomId) {
if (TextUtils.isEmpty(roomId)) {
return "无效的房间ID";
}
Long timestamp = cacheTimestamps.get(roomId);
if (timestamp == null) {
return "未缓存";
}
long elapsed = System.currentTimeMillis() - timestamp;
if (elapsed > CACHE_DURATION) {
return "缓存已过期";
}
RoomInfoResp data = roomDataCache.get(roomId);
if (data == null) {
return "缓存数据为空";
}
return String.format("已缓存 (%d秒前)", elapsed / 1000);
}
}

View File

@@ -0,0 +1,14 @@
package com.xscm.moduleutil.bean;
import lombok.Data;
/**
* @Description: 首页活动弹窗权限
* @Author: xscm
* @Date: 2021/9/27 14:05
*/
@Data
public class ActivitiesPermission {
private int first_charge_permission;//首充权限 1:有 0:无
private int day_drop_permission;//天降好礼权限 1:有 0:无
private int n_people_permission;//新人好礼权限 1:有 0:无
}

View File

@@ -0,0 +1,22 @@
package com.xscm.moduleutil.bean;
import lombok.Data;
/**
*@author qx
*@data 2025/9/25
*@description: 绑定详情
*/
@Data
public class BindDetail {
private String id;
private String alipay_name;//支付宝姓名
private String alipay_account;//支付宝账户
private String bank_card_number;//银行卡号
private String bank_user_name;//姓名
private String bank_card;//所属行
private String open_bank;//开户行
}

View File

@@ -0,0 +1,4 @@
package com.xscm.moduleutil.bean;
public class GiftAvatarBean {
}

View File

@@ -0,0 +1,8 @@
package com.xscm.moduleutil.bean;
import lombok.Data;
@Data
public class GiftPackEvent {
private String bdid;
}

View File

@@ -0,0 +1,13 @@
package com.xscm.moduleutil.bean;
import lombok.Data;
/**
*@author qx
*@data 2025/9/15
*@description: 背包礼物总价值
*/
@Data
public class GiftPackListCount {
private String count;
}

View File

@@ -0,0 +1,11 @@
package com.xscm.moduleutil.bean;
import lombok.Data;
import java.io.Serializable;
@Data
public class MqttXlhEnd implements Serializable {
private static final long serialVersionUID = 1L;
private String message;
}

View File

@@ -0,0 +1,17 @@
package com.xscm.moduleutil.bean;
import com.stx.xhb.xbanner.entity.SimpleBannerInfo;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class PermissionPicBean extends SimpleBannerInfo {
private int picId;
private int type;//类型 1首充、2天降 3新人
@Override
public Object getXBannerUrl() {
return picId;
}
}

View File

@@ -0,0 +1,8 @@
package com.xscm.moduleutil.bean;
import lombok.Data;
@Data
public class RedPackGrab {
private int code;//1:正常抢 2已经抢过了 3手慢了
}

View File

@@ -0,0 +1,52 @@
package com.xscm.moduleutil.bean;
import lombok.Data;
/**
* 红包推送的对象
*/
@Data
public class RedPacketInfo {
private int id;
private String remark;// 备注
private String password;// 口令
private int countdown;//0立即开抢其他倒计时抢
private String conditions;//条件
private String total_amount;//红包总金额
private int room_id;//房间ID
private int type;//红包类型
private int total_count;//红包数量
private int coin_type;//币种
private int user_id;//用户ID
private String nickname;// 昵称
private String redpacket_id;//红包ID
private String avatar;//头像
private String redpacket_time;//红包消失的时间
private long start_time;
private boolean isAvailable;//是否可以领取
private String left_amount;//33.00",
private int left_count;
private long end_time;
private long createtime;
private String updatetime;
private int is_qiang;
// 获取剩余时间
public long remainingTime() {
long needTime = 0;
// 获取当前时间戳(毫秒)
long currentTimeMillis = System.currentTimeMillis() / 1000;
// 计算剩余时间
needTime = start_time - currentTimeMillis;
return needTime;
}
// 判断红包是否可以领取
public boolean canOpenNow() {
return remainingTime() <= 0;
}
}

View File

@@ -0,0 +1,35 @@
package com.xscm.moduleutil.bean;
import lombok.Data;
import java.util.List;
@Data
public class RedpacketDetail {
private RedPacketInfo redpacket_info;
private List<Records> records;
private MyRecord my_record;
private boolean has_grabbed;
@Data
public static class Records {
private int id;
private int redpacket_id;
private int user_id;
private String nickname;
private String avatar;
private String amount;
private String createtime;
}
@Data
public static class MyRecord {
private int id;
private int redpacket_id;
private String nickname;
private String user_id;
private String avatar;
private String amount;
private String createtime;
}
}

View File

@@ -0,0 +1,21 @@
package com.xscm.moduleutil.bean;
import java.util.List;
import lombok.Data;
/**
*@author qx
*@data 2025/9/10
*@description: 魅力详情列表
*/
@Data
public class RoomUserCharmListBean {
private int user_id;
private String total_price;
private String nickname;
private String avatar;
private String user_code;
private int charm;
private List<String> icon ;
}

View File

@@ -0,0 +1,11 @@
package com.xscm.moduleutil.bean;
import java.util.List;
import lombok.Data;
@Data
public class SearchAll {
private List<RoomSearchResp> rooms;
private List<UserResultResp> users;
}

View File

@@ -0,0 +1,26 @@
package com.xscm.moduleutil.bean;
import com.xscm.moduleutil.bean.blindboxwheel.BlindBoxBean;
import lombok.Data;
import java.io.Serializable;
/**
*@author qx
*@data 2025/9/2
*@description: 巡乐会开始后推送的信息
*/
@Data
public class XLHBean implements Serializable {
private static final long serialVersionUID = 1L;
private String text;
private String room_id;
private int from_type ;//100巡乐会进度更新 101巡乐会即将开始 102巡乐会已经开始 103巡乐会有人锁定了礼物 104巡乐会结束落包
private BlindBoxBean.XlhData xlh_data;
private UserInfo FromUserInfo;
private String end_time;
private BlindBoxBean.xlhUser room_user;
private String gift_num;
}

View File

@@ -0,0 +1,164 @@
package com.xscm.moduleutil.bean.blindboxwheel;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.xscm.moduleutil.bean.GiftBean;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import lombok.Data;
/**
*@author qx
*@data 2025/8/27
*@description: 获取活动礼物列表
*/
@Data
public class BlindBoxBean {
private String title;
private String rule_url;
private String rule;
private int box_price ;///每一次抽奖的价格
private String xlh_end_time;///巡乐会结束时间
private int is_xlh; ///是否开启巡乐会 0 关闭 1 开启
private Object xlh_data;
private List<GiftBean> gift_list;
private String end_time;//巡乐会结束时间
private GiveGift give_homeowner_gift;//房主礼物
private GiveGift locking_gift;//锁定礼物
private xlhUser xlh_user;//巡乐会中奖用户
private xlhUser homeowner_user;//房主信息
public boolean isXlhDataArray() {
return xlh_data instanceof JsonArray || xlh_data instanceof List;
}
public boolean isXlhDataObject() {
return xlh_data instanceof JsonObject || xlh_data instanceof Map || xlh_data instanceof XlhData;
}
public List<XlhData> getXlhDataAsList() {
if (isXlhDataArray()) {
// 转换为List
return (List<XlhData>) xlh_data;
}
return new ArrayList<>();
}
public XlhData getXlhDataAsObject() {
if (isXlhDataObject()) {
// 如果已经是XlhData类型直接返回
if (xlh_data instanceof XlhData) {
return (XlhData) xlh_data;
}
// 如果是Map类型Gson解析后的LinkedTreeMap手动转换
else if (xlh_data instanceof Map) {
Map<String, Object> map = (Map<String, Object>) xlh_data;
XlhData xlhData = new XlhData();
// 安全地转换各个字段
Object waitingStartNum = map.get("waiting_start_num");
if (waitingStartNum != null) {
xlhData.setWaiting_start_num(waitingStartNum.toString());
}
Object startNum = map.get("start_num");
if (startNum != null) {
xlhData.setStart_num(startNum.toString());
}
Object currentNum = map.get("current_num");
if (currentNum != null) {
if (currentNum instanceof Number) {
xlhData.setCurrent_num(((Number) currentNum).intValue());
} else {
try {
xlhData.setCurrent_num(Integer.parseInt(currentNum.toString()));
} catch (NumberFormatException e) {
xlhData.setCurrent_num(0);
}
}
}
Object status = map.get("status");
if (status != null) {
if (status instanceof Number) {
xlhData.setStatus(((Number) status).intValue());
} else {
try {
xlhData.setStatus(Integer.parseInt(status.toString()));
} catch (NumberFormatException e) {
xlhData.setStatus(0);
}
}
}
Object endTime = map.get("end_time");
if (endTime != null) {
if (endTime instanceof String){
xlhData.setEnd_time(endTime.toString());
}else {
xlhData.setEnd_time(endTime.toString());
}
}
return xlhData;
}
// 如果是JsonObject也需要转换
else if (xlh_data instanceof JsonObject) {
JsonObject jsonObject = (JsonObject) xlh_data;
XlhData xlhData = new XlhData();
if (jsonObject.has("waiting_start_num")) {
xlhData.setWaiting_start_num(jsonObject.get("waiting_start_num").getAsString());
}
if (jsonObject.has("start_num")) {
xlhData.setStart_num(jsonObject.get("start_num").getAsString());
}
if (jsonObject.has("current_num")) {
xlhData.setCurrent_num(jsonObject.get("current_num").getAsInt());
}
if (jsonObject.has("status")) {
xlhData.setStatus(jsonObject.get("status").getAsInt());
}
if (jsonObject.has("end_time")){
xlhData.setEnd_time(jsonObject.get("end_time").getAsString());
}
return xlhData;
}
}
return null;
}
@Data
public static class XlhData {
private String waiting_start_num;//等待开始需要达到的次数
private String start_num;//巡乐会开启需要达到的次数
private int current_num;//当前已抽奖次数
private int status;
private String end_time;
}
@Data
public static class GiveGift {
private int gift_id;
private String gift_name;
private String base_image;
private String gift_num;
private String gift_price;
}
@Data
public static class xlhUser {
private String user_id;
private String nickname;
private String avatar;
}
}

View File

@@ -0,0 +1,23 @@
package com.xscm.moduleutil.bean.blindboxwheel;
import java.util.List;
import lombok.Data;
/**
* @author qx
* @data 2025/8/27
* @description: 礼物抽奖结果
*/
@Data
public class BlindReslutBean {
private String blind_box_turntable_id;//本次抽奖标识 Id 效果完成后用这个值推送发放
private List<ReslutList> reslut_list;
@Data
public class ReslutList {
private int gift_id;//中奖礼物Id
private int count;//中奖礼物数量
}
}

View File

@@ -0,0 +1,16 @@
package com.xscm.moduleutil.bean.blindboxwheel;
import lombok.Data;
/**
*@author qx
*@data 2025/9/4
*@description: 巡乐会抽奖
*/
@Data
public class XlhDrawBean {
private int gift_id;
private String gift_name;
private String base_image;
private String gift_price;
private int count ;
}

View File

@@ -0,0 +1,19 @@
package com.xscm.moduleutil.bean.room;
import lombok.Data;
// TODO: 2025/3/10 亲密关系
@Data
public class CloseBean {
private String id;//关系id
private String user_id;//用户id
private String head_picture; //用户头像
private String nickname;//用户昵称
private String sex;//性别
private String contact_end_time;//剩余天数
private String heart_value;//心动值
private String friend_config_id;//关系类型id
private String relationship_icon ;//关系类型图标
}

View File

@@ -0,0 +1,6 @@
package com.xscm.moduleutil.bean.room
class Emotion {
var type_name: String? = ""
var id: Int? = 0
}

View File

@@ -0,0 +1,20 @@
package com.xscm.moduleutil.bean.room
data class EmotionDeatils(
var id: Int? = 0,
var pid: Int? = 0,
var type_id: Int? = 0,
var name: String? = "",
var image: String? = "",
var animate_image : String? = "",
var children: List<Children>? =ArrayList (),
)
data class Children(
var id: Int? = 0,
var pid: Int? = 0,
var type_id: Int? = 0,
var name: String? = "",
var image: String? = "",
var animate_image : String? = "",
)

View File

@@ -0,0 +1,27 @@
package com.xscm.moduleutil.bean.room;
import java.io.Serializable;
import lombok.Data;
/**
*@author qx
*@data 2025/8/24
*@description: 结束后返回的关系数据,
*/
@Data
public class FriendUserBean implements Serializable {
private int is_cp;//1:卡关系 0不卡关系
private String user1_id;//王者位用户1id
private String user1_avatar;//王者位用户1头像
private String user1_nickname;//王者位用户1昵称
private String user2_id;//王者位用户2id
private String user2_avatar;//王者位用户2头像
private String user2_nickname;//王者位用户2昵称
private String heart_value;//连线值
private String heart_id;//连线值ID
private String relation_name;//什么关系
}

View File

@@ -0,0 +1,27 @@
package com.xscm.moduleutil.bean.room;
import lombok.Data;
import java.util.List;
/**
* 红包的结果集
*/
@Data
public class RedResultBean {
private String redUserName;//发布红包的用户名称
private String redUserAvatar;//发布红包的用户头像
private String redTitle;//发布红包的备注
private String redJb;//中奖的金币
private String redyl;//已经领取的个数
private List<RedBean> redList;
@Data
public static class RedBean {
private String redUserName;
private String redUserAvatar;
private String redNum;
private String redTime;
}
}

View File

@@ -0,0 +1,12 @@
package com.xscm.moduleutil.bean.room;
import lombok.Data;
// TODO: 2025/3/12 关系表
@Data
public class RoomConcernDean {
private String concernName;
private String concernType;
}

View File

@@ -0,0 +1,28 @@
package com.xscm.moduleutil.bean.room;
import lombok.Data;
import java.util.List;
/**
*@author qx
*@data 2025/9/29
*@description:小时榜实体类
*/
@Data
public class RoomHourBean {
private String time_range;
private List<RoomListBean> lists;
@Data
public class RoomListBean {
private String room_id;
private String room_name;
private int label_id;
private String room_cover;
private int total_price;
private String label_icon;
private int xlh_status;
private int redpacket_status;// >0 有红包,=0 没有红包
}
}

View File

@@ -0,0 +1,130 @@
package com.xscm.moduleutil.dialog;
import android.os.Bundle;
import android.view.Choreographer;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.listener.OnRefreshLoadMoreListener;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.base.BaseMvpDialogFragment;
import com.xscm.moduleutil.bean.GiftBean;
import com.xscm.moduleutil.bean.WalletBean;
import com.xscm.moduleutil.bean.blindboxwheel.BlindBoxBean;
import com.xscm.moduleutil.bean.blindboxwheel.BlindReslutBean;
import com.xscm.moduleutil.bean.blindboxwheel.XlhDrawBean;
import com.xscm.moduleutil.databinding.DialogNewRankingXlhFragmentBinding;
import com.xscm.moduleutil.databinding.FframentDataBinding;
import com.xscm.moduleutil.dialog.giftLottery.GiftLotteryContacts;
import com.xscm.moduleutil.dialog.giftLottery.GiftLotteryPresenter;
import com.xscm.moduleutil.dialog.giftLottery.GiftRecordAdapte;
import com.xscm.moduleutil.dialog.giftLottery.NewGiftRecordAdapte;
import java.util.List;
public class LotteryFragment extends BaseMvpDialogFragment<GiftLotteryPresenter, FframentDataBinding> implements GiftLotteryContacts.View {
private int page=1;
private String roomId;
private int type=-1;
private GiftRecordAdapte giftRecordAdapte;
@Override
protected GiftLotteryPresenter bindPresenter() {
return new GiftLotteryPresenter(this,getSelfActivity());
}
public static LotteryFragment newInstance(String giftBagId,int type) {
Bundle args = new Bundle();
LotteryFragment fragment = new LotteryFragment();
args.putString("roomId", giftBagId);
args.putInt("type",type);
fragment.setArguments(args);
return fragment;
}
@Override
protected void initData() {
roomId = getArguments().getString("roomId");
type = getArguments().getInt("type");
MvpPre.xlhAllRecord(roomId, "1", "20",type);
}
@Override
protected void initView() {
mBinding.smartRefreshLayout.setOnRefreshLoadMoreListener(new OnRefreshLoadMoreListener() {
@Override
public void onRefresh(@NonNull RefreshLayout refreshLayout) {
page = 1;
MvpPre.xlhAllRecord(roomId, page+"", "20",type);
}
@Override
public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
page++;
MvpPre.xlhAllRecord(roomId, page+"", "20",type);
}
});
giftRecordAdapte=new GiftRecordAdapte();
mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
mBinding.recyclerView.setAdapter(giftRecordAdapte);
}
@Override
protected int getLayoutId() {
return R.layout.fframent_data;
}
@Override
public void getGiftListSuccess(BlindBoxBean blindBoxBean) {
}
@Override
public void drawGiftListSuccess(BlindReslutBean blindReslutBean) {
}
@Override
public void getMyRecordSuccess(List<GiftBean> data) {
}
@Override
public void getAllRecordSuccess(List<GiftBean> data) {
if (data != null){
if (page==1){
giftRecordAdapte.setNewData(data);
}else {
giftRecordAdapte.addData(data);
}
}else {
if (page == 1) {
giftRecordAdapte.setNewData(null);
}
}
}
@Override
public void finishRefreshLoadMore() {
mBinding.smartRefreshLayout.finishRefresh();
mBinding.smartRefreshLayout.finishLoadMore();
}
@Override
public void wallet(WalletBean walletBean) {
}
@Override
public void xlhChouSuccess(List<XlhDrawBean> data) {
}
}

View File

@@ -0,0 +1,368 @@
package com.xscm.moduleutil.dialog;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Paint;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.blankj.utilcode.util.ScreenUtils;
import com.blankj.utilcode.util.ToastUtils;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.adapter.HeavenGiftAdapter;
import com.xscm.moduleutil.bean.BaseListData;
import com.xscm.moduleutil.bean.FirstChargeGiftBean;
import com.xscm.moduleutil.bean.RoonGiftModel;
import com.xscm.moduleutil.color.ThemeableDrawableUtils;
import com.xscm.moduleutil.databinding.DialogNewPeopleBinding;
import com.xscm.moduleutil.http.BaseObserver;
import com.xscm.moduleutil.http.RetrofitClient;
import com.xscm.moduleutil.utils.ColorManager;
import com.xscm.moduleutil.widget.dialog.BaseDialog;
import com.zhpan.bannerview.indicator.DrawableIndicator;
import com.zhpan.indicator.base.IIndicator;
import com.zhpan.indicator.enums.IndicatorSlideMode;
import io.reactivex.disposables.Disposable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author
* @data
* @description: 新人好礼
*/
public class NewPeopleDialog extends BaseDialog<DialogNewPeopleBinding> {
HeavenGiftAdapter heavenGiftAdapter;
FirstChargeGiftBean firstChargeGiftBean;
private int type;
public NewPeopleDialog(@NonNull Context context) {
super(context, R.style.BaseDialogStyleH);
}
@Override
public int getLayoutId() {
return R.layout.dialog_new_people;
}
@Override
public void initView() {
setCancelable(false);
setCanceledOnTouchOutside(false);
Window window = getWindow();
window.setLayout((int) (ScreenUtils.getScreenWidth() * 375.f / 375), WindowManager.LayoutParams.WRAP_CONTENT);
mBinding.ivClose.setOnClickListener(v -> dismiss());
heavenGiftAdapter = new HeavenGiftAdapter();
mBinding.bannerViewPager
.setPageMargin(15)
.setAutoPlay(false)
.setRevealWidth(0, 0)
.setIndicatorVisibility(View.VISIBLE)
.setIndicatorView(getVectorDrawableIndicator())
.setIndicatorSlideMode(IndicatorSlideMode.NORMAL)
.setAdapter(heavenGiftAdapter)
.create();
mBinding.rg.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup radioGroup, int i) {
if (firstChargeGiftBean == null || firstChargeGiftBean.getGift_bag().size() == 0) {
ToastUtils.showShort("暂无礼包");
return;
}
if (i == R.id.btn_0) {
List<RoonGiftModel> list = new ArrayList<>();
if (firstChargeGiftBean.getGift_bag().size() > 1) {
mBinding.tvTitle1.setText(firstChargeGiftBean.getGift_bag().get(0).getTitle1());
mBinding.tvTitle2.setText(firstChargeGiftBean.getGift_bag().get(0).getTitle2());
mBinding.tvTitle2.setPaintFlags(Paint.STRIKE_THRU_TEXT_FLAG);
mBinding.btn0.setText(firstChargeGiftBean.getGift_bag().get(0).getName());
list.addAll(firstChargeGiftBean.getGift_bag().get(0).getGift_list());
mBinding.bannerViewPager.create(baseListData(list, 4));
}
type = 1;
} else if (i == R.id.btn_1) {
List<RoonGiftModel> list = new ArrayList<>();
if (firstChargeGiftBean.getGift_bag().size() > 2) {
mBinding.tvTitle1.setText(firstChargeGiftBean.getGift_bag().get(1).getTitle1());
mBinding.tvTitle2.setText(firstChargeGiftBean.getGift_bag().get(1).getTitle2());
mBinding.tvTitle2.setPaintFlags(Paint.STRIKE_THRU_TEXT_FLAG);
mBinding.btn1.setText(firstChargeGiftBean.getGift_bag().get(1).getName());
list.addAll(firstChargeGiftBean.getGift_bag().get(1).getGift_list());
mBinding.bannerViewPager.create(baseListData(list, 4));
}
type = 2;
} else if (i == R.id.btn_2) {
List<RoonGiftModel> list = new ArrayList<>();
if (firstChargeGiftBean.getGift_bag().size() > 3) {
if (firstChargeGiftBean.getGift_bag().get(2) != null) {
mBinding.tvTitle1.setText(firstChargeGiftBean.getGift_bag().get(2).getTitle1());
mBinding.tvTitle2.setText(firstChargeGiftBean.getGift_bag().get(2).getTitle2());
mBinding.tvTitle2.setPaintFlags(Paint.STRIKE_THRU_TEXT_FLAG);
mBinding.btn2.setText(firstChargeGiftBean.getGift_bag().get(2).getName());
list.addAll(firstChargeGiftBean.getGift_bag().get(2).getGift_list());
mBinding.bannerViewPager.create(baseListData(list, 4));
type = 3;
}
}
} else if (i == R.id.btn_3) {
List<RoonGiftModel> list = new ArrayList<>();
if (firstChargeGiftBean.getGift_bag().size() >= 4) {
mBinding.tvTitle1.setText(firstChargeGiftBean.getGift_bag().get(3).getTitle1());
mBinding.tvTitle2.setText(firstChargeGiftBean.getGift_bag().get(3).getTitle2());
mBinding.btn3.setText(firstChargeGiftBean.getGift_bag().get(3).getName());
mBinding.tvTitle2.setPaintFlags(0); // 清除所有绘制标志
list.addAll(firstChargeGiftBean.getGift_bag().get(3).getGift_list());
mBinding.bannerViewPager.create(baseListData(list, 4));
type = 4;
}
}
}
});
mBinding.rg.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}
});
mBinding.tvInvite.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// RechargeDialogFragment.show(roomId, getSupportFragmentManager());
if (listener != null) {
listener.onFirstChargeConfirmed(firstChargeGiftBean, type);
}
}
});
}
public interface OnFirstChargeListener {
void onFirstChargeConfirmed(FirstChargeGiftBean giftBean, int type);
void onFirstChargeCancelled();
}
private OnFirstChargeListener listener;
// 设置监听器的方法
public void setOnFirstChargeListener(OnFirstChargeListener listener) {
this.listener = listener;
}
@Override
public void initData() {
RetrofitClient.getInstance().getNewChargeGift(new BaseObserver<FirstChargeGiftBean>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(FirstChargeGiftBean firstChargeGiftBean) {
if (firstChargeGiftBean != null) {
showGift(firstChargeGiftBean);
}
}
});
}
private List<BaseListData<RoonGiftModel>> baseListData(List<RoonGiftModel> list, int chunkSize) {
List<BaseListData<RoonGiftModel>> baseListData = new ArrayList<>();
for (int i = 0; i < list.size(); i += chunkSize) {
BaseListData<RoonGiftModel> baseListData1 = new BaseListData<>();
baseListData1.setData(list.subList(i, Math.min(i + chunkSize, list.size())));
baseListData.add(baseListData1);
}
return baseListData;
}
private IIndicator getVectorDrawableIndicator() {
int dp6 = getResources().getDimensionPixelOffset(com.xscm.moduleutil.R.dimen.dp_6);
return new DrawableIndicator(getContext())
.setIndicatorGap(getResources().getDimensionPixelOffset(com.xscm.moduleutil.R.dimen.dp_2_5))
.setIndicatorDrawable(com.xscm.moduleutil.R.drawable.banner_indicator_nornal, com.xscm.moduleutil.R.drawable.banner_indicator_focus)
.setIndicatorSize(getResources().getDimensionPixelOffset(com.xscm.moduleutil.R.dimen.dp_13), dp6, getResources().getDimensionPixelOffset(com.xscm.moduleutil.R.dimen.dp_13), dp6);
}
private Resources getResources() {
return getContext().getResources();
}
public void showGift(FirstChargeGiftBean firstChargeGiftBean) {
this.firstChargeGiftBean = firstChargeGiftBean;
mBinding.rg.check(R.id.btn_0);
if (firstChargeGiftBean.getGift_bag() != null && firstChargeGiftBean.getGift_bag().size() > 0) {
if (firstChargeGiftBean.getGift_bag().size() >= 0) {
type = 1;
List<RoonGiftModel> list = new ArrayList<>();
mBinding.tvTitle1.setText(firstChargeGiftBean.getGift_bag().get(0).getTitle1());
mBinding.tvTitle2.setText(firstChargeGiftBean.getGift_bag().get(0).getTitle2());
mBinding.btn0.setText(firstChargeGiftBean.getGift_bag().get(0).getName());
list.addAll(firstChargeGiftBean.getGift_bag().get(0).getGift_list());
mBinding.bannerViewPager.create(baseListData(list, 4));
mBinding.btn1.setText(firstChargeGiftBean.getGift_bag().get(1).getName());
mBinding.btn2.setText(firstChargeGiftBean.getGift_bag().get(2).getName());
mBinding.btn3.setText(firstChargeGiftBean.getGift_bag().get(3).getName());
initGiftBagButtonStatus(firstChargeGiftBean);
} else if (firstChargeGiftBean.getGift_bag().size() == 2) {
// mBinding.rg.check(R.id.btn_0);
// mBinding.btn1.setVisibility(View.VISIBLE);
// mBinding.btn2.setVisibility(View.INVISIBLE);
} else if (firstChargeGiftBean.getGift_bag().size() == 3) {
// mBinding.rg.check(R.id.btn_0);
// mBinding.btn1.setVisibility(View.VISIBLE);
// mBinding.btn2.setVisibility(View.VISIBLE);
}
// mBinding.rg.check(R.id.btn_0);
}
}
private boolean isstatus = true;
private void initGiftBagButtonStatus(FirstChargeGiftBean firstChargeGiftBean) {
// 1. 准备按钮列表顺序与gift_bag中的元素顺序一一对应
List<RadioButton> buttonList = Arrays.asList(
mBinding.btn0,
mBinding.btn1,
mBinding.btn2
// 未来加按钮mBinding.btn3, mBinding.btn4...
);
// 2. 空安全检查:先判断核心对象/列表非空
if (firstChargeGiftBean != null && firstChargeGiftBean.getGift_bag() != null) {
List<FirstChargeGiftBean.GiftBag> giftBagList = firstChargeGiftBean.getGift_bag();
// 3. 循环处理每个按钮
for (int i = 0; i < buttonList.size(); i++) {
RadioButton currentBtn = buttonList.get(i);
// 4. 索引防护若gift_bag列表长度不足默认按status=0处理
int status = (i < giftBagList.size()) ? giftBagList.get(i).getStatus() : 0;
// 检查是否有status=0的情况如果有则将isStatus设为false
if (status == 0) {
isstatus = false;
}
setButtonStatus(currentBtn, status, i); // 增加索引参数
}
updateRechargeTextViewStatus(isstatus, 0);
} else {
// 5. 兜底逻辑数据为空时所有按钮按status=0处理
for (int i = 0; i < buttonList.size(); i++) {
setButtonStatus(buttonList.get(i), 0, i);
}
}
}
/**
* 工具方法:统一设置单个按钮的状态
*
* @param button 要设置的RadioButton
* @param status 状态值
* @param index 按钮索引,用于标识不同按钮
*/
private void setButtonStatus(RadioButton button, int status, int index) {
if (button == null) return;
// 移除之前的点击监听器,避免重复设置
button.setOnClickListener(null);
if (status == 1) {
// 可用状态
button.setEnabled(true);
// button.setChecked(true);
// 恢复充值按钮状态
updateRechargeTextViewStatus(true, index);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
updateRechargeTextViewStatus(true, index);
}
});
} else {
// status=0和其他状态特殊处理
button.setEnabled(true); // 保持可点击以响应交互
// button.setChecked(false);
// 设置特殊背景(根据需求修改为你的资源)
button.setBackground(getResources().getDrawable(R.drawable.bf_e9));
button.setTextColor(getResources().getColor(R.color.colorBlack65));
updateRechargeTextViewStatus(false, index);
// 添加点击事件
button.setOnClickListener(v -> {
// 点击时再次确认是status=0状态才处理
button.setBackground(getResources().getDrawable(R.drawable.banner_indicator_focus));
// 更新充值按钮状态为不可点击
updateRechargeTextViewStatus(false, index);
});
}
}
/**
* 检查是否至少有一个元素达标status == 1
*/
public static boolean hasAnyQualified(List<FirstChargeGiftBean.GiftBag> giftBagList) {
// 空列表处理 + 任意匹配检查
return giftBagList != null && !giftBagList.isEmpty()
&& giftBagList.stream()
.anyMatch(gift -> gift.getStatus() == 1);
}
/**
* 更新充值TextView的状态
*
* @param enabled 是否可点击
* @param index 关联的按钮索引
*/
private void updateRechargeTextViewStatus(boolean enabled, int index) {
TextView rechargeTv = mBinding.tvInvite; // 假设充值按钮的id是tvRecharge
if (rechargeTv == null) return;
// 设置是否可点击
rechargeTv.setEnabled(enabled);
// 根据状态和索引设置不同背景
if (enabled) {
ThemeableDrawableUtils.setThemeableRoundedBackground(mBinding.tvInvite, ColorManager.getInstance().getPrimaryColorInt(), 53);
mBinding.tvInvite.setTextColor(ColorManager.getInstance().getButtonColorInt());
} else {
// 不可点击状态的背景
ThemeableDrawableUtils.setThemeableRoundedBackground(mBinding.tvInvite, ColorManager.getInstance().getButtonColorInt(), 53);
mBinding.tvInvite.setTextColor(getResources().getColor(R.color.colorBlack65));
}
}
}

View File

@@ -0,0 +1,228 @@
package com.xscm.moduleutil.dialog;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.webkit.JavascriptInterface;
import android.webkit.WebSettings;
import android.webkit.WebViewClient;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.alibaba.android.arouter.launcher.ARouter;
import com.blankj.utilcode.util.LogUtils;
import com.blankj.utilcode.util.ScreenUtils;
import com.tencent.imsdk.v2.V2TIMConversation;
import com.tencent.mm.opensdk.modelbiz.WXOpenCustomerServiceChat;
import com.tencent.mm.opensdk.openapi.IWXAPI;
import com.tencent.mm.opensdk.openapi.WXAPIFactory;
import com.tencent.qcloud.tuicore.TUIConstants;
import com.tencent.qcloud.tuikit.tuichat.classicui.page.TUIC2CChatActivity;
import com.tencent.qcloud.tuikit.tuichat.classicui.page.TUIGroupChatActivity;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.base.CommonAppContext;
import com.xscm.moduleutil.base.RoomManager;
import com.xscm.moduleutil.databinding.DialogRoomAuctionWebviewBinding;
import com.xscm.moduleutil.databinding.WebViewDialogBinding;
import com.xscm.moduleutil.utils.ARouteConstants;
import com.xscm.moduleutil.widget.dialog.BaseDialog;
/**
*@author qx
*@data 2025/9/24
*@description: 这是拍卖房的规则界面
*/
public class RoomAuctionWebViewDialog extends BaseDialog<DialogRoomAuctionWebviewBinding> {
String mUrl;
int type;//10天空之境 11岁月之城 12时空之巅
public RoomAuctionWebViewDialog(@NonNull Context context, Bundle args) {
super(context, R.style.BaseDialogStyleH);
this.mUrl = args.getString("url");
this.type = args.getInt("type");
initData1();
}
@Override
public void onStart() {
super.onStart();
if (getWindow() != null) {
// 获取屏幕尺寸
android.util.DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
// 设置高度为屏幕高度的80%
android.view.WindowManager.LayoutParams params = getWindow().getAttributes();
params.height = (int) (displayMetrics.heightPixels * 0.9);
params.width = android.view.ViewGroup.LayoutParams.MATCH_PARENT;
getWindow().setAttributes(params);
}
}
@Override
public int getLayoutId() {
return R.layout.dialog_room_auction_webview;
}
@Override
public void initView() {
setCancelable(true);
setCanceledOnTouchOutside(true);
Window window = getWindow();
assert window != null;
window.setGravity(Gravity.BOTTOM);
window.setLayout((int) (ScreenUtils.getScreenWidth() * 320.f / 375), WindowManager.LayoutParams.MATCH_PARENT);
mBinding.topBar.setTitle("规则");
mBinding.topBar.getIvBack().setOnClickListener(v -> dismiss());
}
@Override
public void initData() {
}
public void initData1() {
// WebSettings webSettings = mBinding.webView.getSettings();
// webSettings.setUseWideViewPort(true);
// webSettings.setLoadWithOverviewMode(true);
// webSettings.setJavaScriptEnabled(true);
// webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE); //关闭webview中缓存
// //增加JSBridge
// mBinding.webView.addJavascriptInterface(new WebAppInterface(getContext()), "Android");
//// mBinding.webView.addJavascriptInterface(new WebViewBridgeConfig(title), WebViewBridgeConfig.NAME);
// webSettings.setBuiltInZoomControls(false);
// webSettings.setSupportZoom(false);
// webSettings.setDomStorageEnabled(true);
// webSettings.setBlockNetworkImage(false);//解决图片不显示
// // 启用 WebView 内容的滚动
// mBinding.webView.setVerticalScrollBarEnabled(true);
// mBinding.webView.setScrollbarFadingEnabled(true);
// webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
// webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
// mBinding.webView.setHorizontalScrollBarEnabled(false);//水平不显示
// mBinding.webView.setVerticalScrollBarEnabled(false); //垂直不显示
// mBinding.webView.setWebViewClient(new WebViewClient());
// mBinding.webView.setBackgroundColor(Color.TRANSPARENT);
// mBinding.webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
//
// mBinding.webView.requestFocus();
// mBinding.webView.loadUrl(mUrl);
WebSettings webSettings = mBinding.webView.getSettings();
webSettings.setUseWideViewPort(true);
webSettings.setLoadWithOverviewMode(true);
webSettings.setJavaScriptEnabled(true);
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE); //关闭webview中缓存
//增加JSBridge
mBinding.webView.addJavascriptInterface(new WebAppInterface(getContext()), "Android");
// mBinding.webView.addJavascriptInterface(new WebViewBridgeConfig(title), WebViewBridgeConfig.NAME);
webSettings.setBuiltInZoomControls(false);
webSettings.setSupportZoom(false);
webSettings.setDomStorageEnabled(true);
webSettings.setBlockNetworkImage(false);//解决图片不显示
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
// 启用 WebView 内容的滚动,但隐藏滚动条
mBinding.webView.setHorizontalScrollBarEnabled(false);//水平不显示
mBinding.webView.setVerticalScrollBarEnabled(false); //垂直不显示滚动条
mBinding.webView.setWebViewClient(new WebViewClient());
mBinding.webView.setBackgroundColor(Color.TRANSPARENT);
mBinding.webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
// 确保内容可以滚动
webSettings.setDomStorageEnabled(true);
mBinding.webView.requestFocus();
mBinding.webView.loadUrl(mUrl);
}
private Resources getResources() {
return getContext().getResources();
}
public class WebAppInterface {
Context mContext;
WebAppInterface(Context c) {
mContext = c;
}
// 被 H5 调用的方法
@JavascriptInterface
public void showToast(String toast) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
}
@JavascriptInterface
public void closeWeb() {
LogUtils.e("value: ");
dismiss();
}
@JavascriptInterface
public void customerService() {
String appId = CommonAppContext.getInstance().getCurrentEnvironment().getWxAppId(); // 填移动应用(App)的 AppId
IWXAPI api = WXAPIFactory.createWXAPI(mContext, appId);
// 判断当前版本是否支持拉起客服会话
WXOpenCustomerServiceChat.Req req = new WXOpenCustomerServiceChat.Req();
req.corpId = "ww1de4300858c0b461"; // 企业ID
req.url = "https://work.weixin.qq.com/kfid/kfcb3d23a59c188a0e7"; // 客服URL
api.sendReq(req);
}
@JavascriptInterface
public void jumpRoomPage(String room_id) {
RoomManager.getInstance().fetchRoomDataAndEnter(getContext(), room_id,"");
// ARouter.getInstance().build(ARouteConstants.ROOM_DETAILS).withString("form", "首页").withString("roomId", room_id).navigation();
}
@JavascriptInterface
public void jumpWebPage(String objects) {
// ARouter.getInstance().build(ARouteConstants.USER_HOME_PAGE).navigation();
ARouter.getInstance().build(ARouteConstants.USER_HOME_PAGE).withString("userId", objects).navigation();
}
@JavascriptInterface
public void enterGroupChat(String group_id,String cover,String guild_name) {
Intent intent = new Intent(mContext, TUIGroupChatActivity.class);
intent.putExtra(TUIConstants.TUIChat.CHAT_ID, group_id);
intent.putExtra(TUIConstants.TUIChat.CHAT_TYPE, V2TIMConversation.V2TIM_GROUP);
mContext.startActivity(intent);
}
@JavascriptInterface
public void chatWithUser(String user_id,String nickname) {
Intent intent = new Intent(mContext, TUIC2CChatActivity.class);
intent.putExtra(TUIConstants.TUIChat.CHAT_ID, user_id);
intent.putExtra(TUIConstants.TUIChat.CHAT_TYPE, V2TIMConversation.V2TIM_C2C);
mContext.startActivity(intent);
}
@JavascriptInterface
public void exchange(){
ARouter.getInstance().build(ARouteConstants.CURRENCY).navigation();
}
@JavascriptInterface
public void Withdrawal() {
ARouter.getInstance().build(ARouteConstants.WITHDRAWAL_ACTIVITY).navigation();
}
@JavascriptInterface
public void enterAuthent() {//实名认证
ARouter.getInstance().build(ARouteConstants.REAL_NAME_ACTIVITY2).navigation();
}
@JavascriptInterface
public void Recharge(){
ARouter.getInstance().build(ARouteConstants.RECHARGE_ACTIVITY).navigation();
}
}
}

View File

@@ -0,0 +1,22 @@
package com.xscm.moduleutil.dialog.giftLottery;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.blindboxwheel.XlhDrawBean;
import com.xscm.moduleutil.utils.ImageUtils;
public class GiftItemAdapter extends BaseQuickAdapter<XlhDrawBean, BaseViewHolder> {
public GiftItemAdapter() {
super(R.layout.item_xlh_gift);
}
@Override
protected void convert(BaseViewHolder helper, XlhDrawBean item) {
helper.setText(R.id.tv_gift_name, item.getGift_name()+"x"+item.getCount());
ImageUtils.loadHeadCC(item.getBase_image(),helper.getView(R.id.gift_img));
helper.setText(R.id.tv_gift_num, item.getGift_price());
}
}

View File

@@ -0,0 +1,27 @@
package com.xscm.moduleutil.dialog.giftLottery;
import lombok.Data;
/**
*@author qx
*@data 2025/8/25
*@description: 盲盒抽奖的实体类
*/
@Data
public class GiftLottery {
private String id;
private String icon;
private String number;
private String name;
private String price;
private boolean isSelected;
public GiftLottery(String id, String icon, String number, String name, String price, boolean isSelected) {
this.id = id;
this.icon = icon;
this.number = number;
this.name = name;
this.price = price;
this.isSelected = isSelected;
}
}

View File

@@ -0,0 +1,88 @@
package com.xscm.moduleutil.dialog.giftLottery;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.os.Handler;
import android.os.Looper;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.GiftBean;
import com.xscm.moduleutil.bean.GiftBoxBean;
import com.xscm.moduleutil.utils.ImageUtils;
import java.util.List;
import java.util.Random;
/**
* @author qx
* @data 2025/8/25
* @description: 盲盒抽奖展示的视图
*/
// GiftLotteryAdapter.java
public class GiftLotteryAdapter extends BaseQuickAdapter<GiftBean, BaseViewHolder> {
public GiftLotteryAdapter() {
super(R.layout.item_gift_lottery);
}
@Override
protected void convert(BaseViewHolder helper, GiftBean item) {
helper.setText(R.id.tv_gift_time, item.getCreatetime());
ImageUtils.loadHeadCC(item.getBase_image(),helper.getView(R.id.iv_gift_image));
// 使用 SpannableString 给 "x4" 设置不同颜色
TextView giftNameTextView = helper.getView(R.id.gift_name);
TextView nickNameTextView = helper.getView(R.id.tv_user_name);
if (giftNameTextView != null) {
String baseName = item.getGift_name();
String countText = "x"+item.getCount();
String fullText = baseName + countText;
SpannableStringBuilder spannable = new SpannableStringBuilder(fullText);
// 给 "x4" 部分设置颜色
spannable.setSpan(
new ForegroundColorSpan(mContext.getResources().getColor(R.color.color_C7BF62)), // 替换为实际颜色
baseName.length(),
fullText.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
giftNameTextView.setText(spannable);
}
if (nickNameTextView!=null){
nickNameTextView.setText(item.getNickname());
String nickName = "赠予";
String fullText = nickName + " " + item.getNickname();
SpannableStringBuilder spannable = new SpannableStringBuilder(fullText);
// 给 "x4" 部分设置颜色
spannable.setSpan(
new ForegroundColorSpan(mContext.getResources().getColor(R.color.color_C7BF62)), // 替换为实际颜色
0,
nickName.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
nickNameTextView.setText(spannable);
}
}
}

View File

@@ -0,0 +1,253 @@
package com.xscm.moduleutil.dialog.giftLottery;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.listener.OnRefreshLoadMoreListener;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.base.BaseMvpDialogFragment;
import com.xscm.moduleutil.bean.GiftBean;
import com.xscm.moduleutil.bean.WalletBean;
import com.xscm.moduleutil.bean.blindboxwheel.BlindBoxBean;
import com.xscm.moduleutil.bean.blindboxwheel.BlindReslutBean;
import com.xscm.moduleutil.bean.blindboxwheel.XlhDrawBean;
import com.xscm.moduleutil.databinding.DialogGiftLotteryFragmentBinding;
import com.xscm.moduleutil.widget.pagerecyclerview.PagerGridSnapHelper;
import java.util.ArrayList;
import java.util.List;
/**
*@author qx
*@data 2025/8/28
*@description: 盲盒转盘中奖记录
*/
public class GiftLotteryDialogFragment extends BaseMvpDialogFragment<GiftLotteryPresenter, DialogGiftLotteryFragmentBinding> implements GiftLotteryContacts.View{
private int page=1;
private String giftBagId;
private int type=1;
private GiftLotteryAdapter adapter;
private GiftRecordAdapte giftRecordAdapte;
private List<GiftBean> data=new ArrayList<>();
@Override
protected GiftLotteryPresenter bindPresenter() {
return new GiftLotteryPresenter(this,getSelfActivity());
}
public static GiftLotteryDialogFragment newInstance(String giftBagId) {
Bundle args = new Bundle();
GiftLotteryDialogFragment fragment = new GiftLotteryDialogFragment();
args.putString("giftBagId", giftBagId);
fragment.setArguments(args);
return fragment;
}
@Nullable
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setCancelable(true);
return dialog;
}
@Override
public void onStart() {
super.onStart();
Window window = getDialog().getWindow();
if (window != null) {
// 获取屏幕高度
android.util.DisplayMetrics displayMetrics = new android.util.DisplayMetrics();
requireActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int screenHeight = displayMetrics.heightPixels;
// 设置高度为屏幕高度的100%(全屏)
int heightInPx = (int) (screenHeight * 0.8);;
window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, heightInPx);
window.setBackgroundDrawableResource(android.R.color.transparent);
// 可选:设置动画样式(从底部弹出)
window.setWindowAnimations(R.style.CommonShowDialogBottom);
}
}
@Override
protected void initDialogStyle(Window window) {
super.initDialogStyle(window);
window.setGravity(Gravity.BOTTOM);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
giftBagId = getArguments().getString("giftBagId");
}
@Override
protected void initData() {
MvpPre.getMyRecord(giftBagId, "1", "20",type);
}
@Override
protected void initView() {
if (giftBagId.equals("10")){
mBinding.clRoot.setBackgroundResource(R.mipmap.tkzj);
mBinding.imJc.setImageResource(R.mipmap.jilu);
}else if (giftBagId.equals("11")){
mBinding.clRoot.setBackgroundResource(R.mipmap.syzc);
mBinding.imJc.setImageResource(R.mipmap.syzc_jl);
}else if (giftBagId.equals("12")){
mBinding.clRoot.setBackgroundResource(R.mipmap.skzj);
mBinding.imJc.setImageResource(R.mipmap.skzl_jl);
}
mBinding.smartRefreshLayout.setOnRefreshLoadMoreListener(new OnRefreshLoadMoreListener() {
@Override
public void onRefresh(@NonNull RefreshLayout refreshLayout) {
page = 1;
MvpPre.getMyRecord(giftBagId, page+"", "20",type);
}
@Override
public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
page++;
MvpPre.getMyRecord(giftBagId, page+"", "20",type);
}
});
mBinding.textView1.setOnClickListener(this::onClick);
mBinding.textView2.setOnClickListener(this::onClick);
adapter=new GiftLotteryAdapter();
giftRecordAdapte=new GiftRecordAdapte();
// PagerGridLayoutManager layoutManager = new PagerGridLayoutManager(rows, columns, PagerGridLayoutManager.VERTICAL);
dianj(1);
}
private void onClick(View view) {
int id = view.getId();
if (id==R.id.textView1){
dianj(1);
}else if (id==R.id.textView2){
dianj(2);
}
}
public void dianj(int type1){
if (type1==1) {
GridLayoutManager layoutManager = new GridLayoutManager(getActivity(), 3);
mBinding.recyclerView.setLayoutManager(layoutManager);
mBinding.recyclerView.setOnFlingListener(null);
// 设置滚动辅助工具
PagerGridSnapHelper pageSnapHelper = new PagerGridSnapHelper();
pageSnapHelper.attachToRecyclerView(mBinding.recyclerView);
mBinding.recyclerView.setAdapter(adapter);
type=1;
setTextViewStyle(mBinding.textView2, false);
setTextViewStyle(mBinding.textView1, true);
}else if (type1==2){
mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
mBinding.recyclerView.setAdapter(giftRecordAdapte);
type=2;
setTextViewStyle(mBinding.textView2, true);
setTextViewStyle(mBinding.textView1, false);
}
page=1;
data.clear();
MvpPre.getMyRecord(giftBagId, page+"", "20",type);
}
private void setTextViewStyle(TextView textView, boolean isSelected) {
if (isSelected) {
textView.setTextColor(getResources().getColor(R.color.white));
textView.setTextSize(16);
textView.setBackground(getResources().getDrawable(R.mipmap.tab_dy));
} else {
textView.setTextColor(getResources().getColor(R.color.color_5B5B5B));
textView.setTextSize(14);
textView.setBackgroundColor(getResources().getColor(R.color.transparent));
}
}
@Override
protected int getLayoutId() {
return R.layout.dialog_gift_lottery_fragment;
}
@Override
public void getGiftListSuccess(BlindBoxBean blindBoxBean) {
}
@Override
public void drawGiftListSuccess(BlindReslutBean blindReslutBean) {
}
@Override
public void getMyRecordSuccess(List<GiftBean> data) {
if (data != null){
if (page==1){
adapter.setNewData(data);
}else {
adapter.addData(data);
}
}else {
if (page == 1) {
adapter.setNewData(null);
}
}
}
@Override
public void getAllRecordSuccess(List<GiftBean> data) {
if (data != null){
if (page==1){
giftRecordAdapte.setNewData(data);
}else {
giftRecordAdapte.addData(data);
}
}else {
if (page == 1) {
giftRecordAdapte.setNewData(null);
}
}
}
@Override
public void finishRefreshLoadMore() {
mBinding.smartRefreshLayout.finishRefresh();
mBinding.smartRefreshLayout.finishLoadMore();
}
@Override
public void wallet(WalletBean walletBean) {
}
@Override
public void xlhChouSuccess(List<XlhDrawBean> data) {
}
}

View File

@@ -0,0 +1,23 @@
package com.xscm.moduleutil.dialog.giftLottery;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.GiftBean;
import com.xscm.moduleutil.utils.ImageUtils;
public class GiftRecordAdapte extends BaseQuickAdapter<GiftBean, BaseViewHolder> {
public GiftRecordAdapte() {
super(R.layout.item_gift_record);
}
@Override
protected void convert(BaseViewHolder helper, GiftBean item) {
helper.setText(R.id.tv_user_name, item.getNickname());
helper.setText(R.id.tv_gift_count_name,"x"+item.getCount()+" "+ item.getGift_name());
helper.setText(R.id.tv_time, item.getCreatetime());
ImageUtils.loadHeadCC(item.getBase_image(),helper.getView(R.id.iv_gift_icon));
}
}

View File

@@ -0,0 +1,46 @@
package com.xscm.moduleutil.dialog.giftLottery;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.widget.TextView;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.GiftBean;
import com.xscm.moduleutil.utils.ImageUtils;
public class GiftRecordAdapter extends BaseQuickAdapter<GiftBean, BaseViewHolder> {
public GiftRecordAdapter() {
super(R.layout.item_my_record);
}
@Override
protected void convert(BaseViewHolder helper, GiftBean item) {
helper.setText(R.id.tv_gift_time, item.getCreatetime());
ImageUtils.loadHeadCC(item.getBase_image(),helper.getView(R.id.iv_gift_image));
// 使用 SpannableString 给 "x4" 设置不同颜色
TextView giftNameTextView = helper.getView(R.id.tv_gift_name);
if (giftNameTextView != null) {
String baseName = item.getGift_name();
String countText = "x"+item.getCount();
String fullText = baseName + countText;
SpannableStringBuilder spannable = new SpannableStringBuilder(fullText);
// 给 "x4" 部分设置颜色
spannable.setSpan(
new ForegroundColorSpan(mContext.getResources().getColor(R.color.color_C7BF62)), // 替换为实际颜色
baseName.length(),
fullText.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
giftNameTextView.setText(spannable);
}
}
}

View File

@@ -0,0 +1,150 @@
package com.xscm.moduleutil.dialog.giftLottery;
import android.view.View;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.GiftBean;
import com.xscm.moduleutil.utils.ImageUtils;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
@Getter
public class GiftXlhChouAdapter extends BaseQuickAdapter<GiftBean, BaseViewHolder> {
private List<GiftBean> giftLists = new ArrayList<>();
private int selectedPosition = -1;
private static final int LOOP_COUNT = 8; // 循环倍数
public GiftXlhChouAdapter() {
super(R.layout.item_xlh);
}
// 设置数据时创建循环数据
@Override
public void setNewData(List<GiftBean> data) {
this.giftLists = data != null ? data : new ArrayList<>();
super.setNewData(createLoopData());
}
/**
* 创建循环数据
* @return 循环数据列表
*/
private List<GiftBean> createLoopData() {
List<GiftBean> loopData = new ArrayList<>();
if (giftLists.isEmpty()) {
return loopData;
}
// 创建足够多的循环数据以实现循环效果
for (int i = 0; i < LOOP_COUNT; i++) {
loopData.addAll(giftLists);
}
return loopData;
}
@Override
public int getItemCount() {
// 如果原始数据为空返回0
if (giftLists.isEmpty()) {
return 0;
}
// 返回循环数据的数量
return super.getItemCount();
}
/**
* 获取实际位置(将循环位置映射到原始数据位置)
* @param position 循环列表中的位置
* @return 原始数据中的实际位置
*/
private int getActualPosition(int position) {
if (giftLists.isEmpty()) {
return 0;
}
return position % giftLists.size();
}
@Override
protected void convert(BaseViewHolder helper, GiftBean item) {
// 获取实际位置
int actualPosition = getActualPosition(helper.getAdapterPosition());
GiftBean actualItem = giftLists.get(actualPosition);
helper.setText(R.id.tv_gift_name, actualItem.getGift_name());
helper.setText(R.id.tv_gift_pic, actualItem.getGift_price());
ImageUtils.loadHeadCC(actualItem.getBase_image(), helper.getView(R.id.iv_gift_image));
// 处理选中状态
View selectedIcon = helper.getView(R.id.selected_icon);
// 处理选中状态
if (selectedIcon != null) {
// 检查当前item是否为选中位置
if (actualPosition == selectedPosition) {
selectedIcon.setVisibility(View.GONE);
helper.setBackgroundRes(R.id.ll_bg,R.mipmap.ke_bg);
} else {
helper.setBackgroundRes(R.id.ll_bg,R.mipmap.xlh_cj_item);
selectedIcon.setVisibility(View.GONE);
}
}
}
/**
* 设置选中位置并更新UI
* @param position 选中的位置
*/
public void setSelectedPosition(int position) {
int previousPosition = selectedPosition;
selectedPosition = position;
if (previousPosition >= 0) {
notifyItemsByActualPosition(previousPosition, false);
}
if (selectedPosition >= 0) {
notifyItemsByActualPosition(selectedPosition, true);
}
}
/**
* 根据实际位置通知所有匹配的item更新
* @param actualPosition 原始数据中的位置
* @param isSelected 是否选中
*/
private void notifyItemsByActualPosition(int actualPosition, boolean isSelected) {
if (giftLists.isEmpty() || actualPosition >= giftLists.size()) {
return;
}
// 通知所有匹配该实际位置的item更新
for (int i = 0; i < getItemCount(); i++) {
if (getActualPosition(i) == actualPosition) {
notifyItemChanged(i);
}
}
}
/**
* 清除选中状态
*/
public void clearSelection() {
int previousPosition = selectedPosition;
selectedPosition = -1;
if (previousPosition >= 0) {
notifyItemsByActualPosition(previousPosition, false);
}
}
/**
* 获取原始数据大小
* @return 原始数据大小
*/
public int getOriginalDataSize() {
List<GiftBean> loopData = new ArrayList<>();
for (int i = 0; i < LOOP_COUNT; i++) {
loopData.addAll(giftLists);
}
return loopData.size();
}
}

View File

@@ -0,0 +1,124 @@
package com.xscm.moduleutil.dialog.giftLottery;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.listener.OnRefreshLoadMoreListener;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.base.BaseMvpDialogFragment;
import com.xscm.moduleutil.bean.GiftBean;
import com.xscm.moduleutil.bean.WalletBean;
import com.xscm.moduleutil.bean.blindboxwheel.BlindBoxBean;
import com.xscm.moduleutil.bean.blindboxwheel.BlindReslutBean;
import com.xscm.moduleutil.bean.blindboxwheel.XlhDrawBean;
import com.xscm.moduleutil.databinding.FframentDataBinding;
import java.util.List;
public class LuckyFragment extends BaseMvpDialogFragment<GiftLotteryPresenter, FframentDataBinding> implements GiftLotteryContacts.View {
private int page=1;
private String roomId;
private int type=-1;
private NewGiftRecordAdapte giftRecordAdapte;
@Override
protected GiftLotteryPresenter bindPresenter() {
return new GiftLotteryPresenter(this,getSelfActivity());
}
public static LuckyFragment newInstance(String giftBagId, int type) {
Bundle args = new Bundle();
LuckyFragment fragment = new LuckyFragment();
args.putString("roomId", giftBagId);
args.putInt("type",type);
fragment.setArguments(args);
return fragment;
}
@Override
protected void initData() {
roomId = getArguments().getString("roomId");
type = getArguments().getInt("type");
MvpPre.xlhAllRecord(roomId, "1", "20",type);
}
@Override
protected void initView() {
mBinding.smartRefreshLayout.setOnRefreshLoadMoreListener(new OnRefreshLoadMoreListener() {
@Override
public void onRefresh(@NonNull RefreshLayout refreshLayout) {
page = 1;
MvpPre.xlhAllRecord(roomId, page+"", "20",type);
}
@Override
public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
page++;
MvpPre.xlhAllRecord(roomId, page+"", "20",type);
}
});
giftRecordAdapte=new NewGiftRecordAdapte();
mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
mBinding.recyclerView.setAdapter(giftRecordAdapte);
}
@Override
protected int getLayoutId() {
return R.layout.fframent_data;
}
@Override
public void getGiftListSuccess(BlindBoxBean blindBoxBean) {
}
@Override
public void drawGiftListSuccess(BlindReslutBean blindReslutBean) {
}
@Override
public void getMyRecordSuccess(List<GiftBean> data) {
}
@Override
public void getAllRecordSuccess(List<GiftBean> data) {
if (data != null){
if (page==1){
giftRecordAdapte.setNewData(data);
}else {
giftRecordAdapte.addData(data);
}
}else {
if (page == 1) {
giftRecordAdapte.setNewData(null);
}
}
}
@Override
public void finishRefreshLoadMore() {
mBinding.smartRefreshLayout.finishRefresh();
mBinding.smartRefreshLayout.finishLoadMore();
}
@Override
public void wallet(WalletBean walletBean) {
}
@Override
public void xlhChouSuccess(List<XlhDrawBean> data) {
}
}

View File

@@ -0,0 +1,23 @@
package com.xscm.moduleutil.dialog.giftLottery;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.GiftBean;
import com.xscm.moduleutil.utils.ImageUtils;
public class NewGiftRecordAdapte extends BaseQuickAdapter<GiftBean, BaseViewHolder> {
public NewGiftRecordAdapte() {
super(R.layout.item_gift_record_new);
}
@Override
protected void convert(BaseViewHolder helper, GiftBean item) {
helper.setText(R.id.tv_issue,item.getPeriods());
helper.setText(R.id.tv_user_name, item.getNickname());
helper.setText(R.id.tv_gift_count_name, item.getGift_name());
helper.setText(R.id.tv_time, item.getCreatetime());
ImageUtils.loadHeadCC(item.getBase_image(),helper.getView(R.id.iv_gift_icon));
}
}

View File

@@ -0,0 +1,174 @@
package com.xscm.moduleutil.dialog.giftLottery;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.Gravity;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.RadioGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.adapter.MyPagerAdapter;
import com.xscm.moduleutil.base.BaseMvpDialogFragment;
import com.xscm.moduleutil.bean.GiftBean;
import com.xscm.moduleutil.bean.WalletBean;
import com.xscm.moduleutil.bean.blindboxwheel.BlindBoxBean;
import com.xscm.moduleutil.bean.blindboxwheel.BlindReslutBean;
import com.xscm.moduleutil.bean.blindboxwheel.XlhDrawBean;
import com.xscm.moduleutil.databinding.DialogNewRankingXlhFragmentBinding;
import com.xscm.moduleutil.dialog.LotteryFragment;
import java.util.ArrayList;
import java.util.List;
/**
*@author qx
*@data 2025/9/4
*@description:巡乐会榜单
*/
public class NewXlhRankingDialog extends BaseMvpDialogFragment<GiftLotteryPresenter, DialogNewRankingXlhFragmentBinding> implements GiftLotteryContacts.View{
private String roomId;
private MyPagerAdapter pagerAdapter;
private List<Fragment> fragmentList;
private List<String> titleList = new ArrayList();
@Override
protected GiftLotteryPresenter bindPresenter() {
return new GiftLotteryPresenter(this,getSelfActivity());
}
public static NewXlhRankingDialog newInstance(String giftBagId) {
Bundle args = new Bundle();
NewXlhRankingDialog fragment = new NewXlhRankingDialog();
args.putString("roomId", giftBagId);
fragment.setArguments(args);
return fragment;
}
@Nullable
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setCancelable(true);
return dialog;
}
@Override
public void onStart() {
super.onStart();
Window window = getDialog().getWindow();
if (window != null) {
// 获取屏幕高度
android.util.DisplayMetrics displayMetrics = new android.util.DisplayMetrics();
requireActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int screenHeight = displayMetrics.heightPixels;
// 设置高度为屏幕高度的100%(全屏)
int heightInPx = (int) (screenHeight * 0.8);;
window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, heightInPx);
window.setBackgroundDrawableResource(android.R.color.transparent);
// 可选:设置动画样式(从底部弹出)
window.setWindowAnimations(R.style.CommonShowDialogBottom);
}
}
@Override
protected void initDialogStyle(Window window) {
super.initDialogStyle(window);
window.setGravity(Gravity.BOTTOM);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
}
@Override
protected void initData() {
roomId = getArguments().getString("roomId");
// MvpPre.xlhAllRecord(roomId, "1", "20");
// 初始化Fragment列表
initFragments();
initViewPager();
}
// 初始化Fragment列表
private void initFragments() {
fragmentList = new ArrayList<>();
fragmentList.add(new LotteryFragment().newInstance(roomId,1)); // 第1页抽奖榜单
fragmentList.add(new LuckyFragment().newInstance(roomId,2)); // 第1页抽奖榜单
}
// 初始化ViewPager
private void initViewPager() {
titleList.add("");
titleList.add("");
FragmentManager childFragmentManager = getChildFragmentManager();
pagerAdapter = new MyPagerAdapter(childFragmentManager, fragmentList,titleList );
mBinding.ivViewPager.setAdapter(pagerAdapter);
mBinding.ivViewPager.setCurrentItem(0); // 默认显示第1页
}
@Override
protected void initView() {
mBinding.rbBtn.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (checkedId==R.id.radio_all){
mBinding.ivViewPager.setCurrentItem(0);
}else {
mBinding.ivViewPager.setCurrentItem(1);
}
}
});
}
@Override
protected int getLayoutId() {
return R.layout.dialog_new_ranking_xlh_fragment;
}
@Override
public void getGiftListSuccess(BlindBoxBean blindBoxBean) {
}
@Override
public void drawGiftListSuccess(BlindReslutBean blindReslutBean) {
}
@Override
public void getMyRecordSuccess(List<GiftBean> data) {
}
@Override
public void getAllRecordSuccess(List<GiftBean> data) {
}
@Override
public void finishRefreshLoadMore() {
}
@Override
public void wallet(WalletBean walletBean) {
}
@Override
public void xlhChouSuccess(List<XlhDrawBean> data) {
}
}

View File

@@ -0,0 +1,61 @@
package com.xscm.moduleutil.dialog.giftLottery;
import android.content.Context;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.adapter.MyBaseAdapter;
import com.xscm.moduleutil.bean.GiftBean;
import com.xscm.moduleutil.utils.ImageUtils;
/**
*@author qx
*@data 2025/8/28
*@description: 盲盒转盘的奖池适配器
*/
public class PrizePoolAdapter extends BaseQuickAdapter<GiftBean, BaseViewHolder> {
private Context context;
private int type;
public PrizePoolAdapter(int type) {
super(R.layout.item_prize_pool);
this.type = type;
}
@Override
protected void convert(BaseViewHolder helper, GiftBean item) {
if (type == 10 || type == 12){
helper.setImageResource(R.id.iv_prize_pool,R.mipmap.tkzj_z);
}else {
helper.setImageResource(R.id.iv_prize_pool,R.mipmap.xlh_hd);
}
helper.setText(R.id.tv_gift_name, item.getGift_name());
helper.setText(R.id.tv_gift_pic, item.getGift_price());
ImageUtils.loadHeadCC(item.getBase_image(),helper.getView(R.id.iv_gift_image));
}
public static class ViewHolder {
private ImageView imGiftImage;
private TextView tv_gift_name;
private TextView tv_gift_price;
public ViewHolder(View convertView) {
imGiftImage = convertView.findViewById(R.id.iv_gift_image);
tv_gift_name = convertView.findViewById(R.id.tv_gift_name);
tv_gift_price = convertView.findViewById(R.id.tv_gift_pic);
}
}
}

View File

@@ -0,0 +1,117 @@
package com.xscm.moduleutil.dialog.giftLottery;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.GiftBean;
import com.xscm.moduleutil.databinding.DialogPrizePoolBinding;
import com.xscm.moduleutil.widget.dialog.BaseDialog;
import java.util.List;
/**
* @author qx
* @data 2025/8/28
* @description: 盲盒转盘奖池弹窗
*/
public class PrizePoolDialog extends BaseDialog<DialogPrizePoolBinding> {
private Context mContext;
private List<GiftBean> gift_list;
private int type;
public PrizePoolDialog(@NonNull Context context) {
super(context);
this.mContext = context;
// 设置对话框从底部弹出并紧贴底部
if (getWindow() != null) {
getWindow().setGravity(android.view.Gravity.BOTTOM);
}
}
@Override
public void onStart() {
super.onStart();
if (getWindow() != null) {
// 获取屏幕尺寸
android.util.DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
// 设置高度为屏幕高度的80%
android.view.WindowManager.LayoutParams params = getWindow().getAttributes();
params.height = (int) (displayMetrics.heightPixels * 0.7);
params.width = android.view.ViewGroup.LayoutParams.MATCH_PARENT;
getWindow().setAttributes(params);
}
}
@Override
public int getLayoutId() {
return R.layout.dialog_prize_pool;
}
@Override
public void initView() {
}
@Override
public void initData() {
}
private void showEmptyState() {
// 显示空状态或加载中提示
// mBinding.tvEmpty.setVisibility(View.VISIBLE);
// mBinding.tvEmpty.setText("暂无奖池数据");
}
// 提供更新数据的方法
public void updateData(List<GiftBean> newData, int type) {
if (type == 10) {
mBinding.clPrize.setBackgroundResource(R.mipmap.tkzj);
mBinding.imJc.setImageResource(R.mipmap.jiangc);
} else if (type == 11) {
mBinding.clPrize.setBackgroundResource(R.mipmap.syzc);
mBinding.imJc.setImageResource(R.mipmap.syzc_jc);
} else if (type == 12) {
mBinding.clPrize.setBackgroundResource(R.mipmap.skzj);
mBinding.imJc.setImageResource(R.mipmap.skzl_jc);
}else if (type == 13){
mBinding.clPrize.setBackgroundResource(R.mipmap.xlh);
mBinding.imJc.setImageResource(R.mipmap.xlh_jc);
}
// 根据屏幕密度调整行数和列数
int rows, columns;
float density = mContext.getResources().getDisplayMetrics().density;
if (density <= 2.0) { // 低密度屏幕如mdpi, hdpi
rows = 4;
columns = 3;
} else if (density <= 3.0) { // 中密度屏幕如xhdpi
rows = 4;
columns = 3;
} else { // 高密度屏幕如xxhdpi, xxxhdpi
rows = 4;
columns = 3;
}
if (newData != null && !newData.isEmpty()) {
this.gift_list = newData;
if (mBinding != null && mContext != null) {
PrizePoolAdapter prizePoolAdapter = new PrizePoolAdapter(type);
GridLayoutManager layoutManager = new GridLayoutManager(getContext(), 3);
// PagerGridLayoutManager layoutManager = new PagerGridLayoutManager(rows, columns, PagerGridLayoutManager.VERTICAL);
mBinding.gvGift.setLayoutManager(layoutManager);
// mBinding.gvGift.setOnFlingListener(null);
// 设置滚动辅助工具
// PagerGridSnapHelper pageSnapHelper = new PagerGridSnapHelper();
// pageSnapHelper.attachToRecyclerView(mBinding.gvGift);
mBinding.gvGift.setAdapter(prizePoolAdapter);
prizePoolAdapter.setNewData(gift_list);
}
}
}
}

View File

@@ -0,0 +1,109 @@
package com.xscm.moduleutil.dialog.giftLottery;
import android.content.Context;
import android.view.Gravity;
import android.view.Window;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import com.blankj.utilcode.util.ScreenUtils;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.blindboxwheel.XlhDrawBean;
import com.xscm.moduleutil.databinding.DialogHeavenGiftBinding;
import com.xscm.moduleutil.databinding.DialogXlhObtainBinding;
import com.xscm.moduleutil.widget.dialog.BaseDialog;
import java.util.List;
/**
* @author qx
* @data 2025/9/2
* @description: 巡乐会恭喜或得礼弹窗
*/
public class XlhObtainDialog extends BaseDialog<DialogXlhObtainBinding> {
public interface OnGiftItemClickListener {
void onPlayAgainClick();
void onCloseClick();
}
private GiftItemAdapter mAdapter;
private OnGiftItemClickListener mListener;
private List<XlhDrawBean> mGiftList;
public XlhObtainDialog(@NonNull Context context) {
super(context, R.style.BaseDialogStyleH);
}
public XlhObtainDialog(@NonNull Context context, List<XlhDrawBean> giftList) {
super(context, R.style.BaseDialogStyleH);
this.mGiftList = giftList;
}
@Override
public int getLayoutId() {
return R.layout.dialog_xlh_obtain;
}
@Override
public void initView() {
setCancelable(false);
setCanceledOnTouchOutside(false);
Window window = getWindow();
// 设置对话框在屏幕中央显示
window.setGravity(Gravity.CENTER);
// 去掉背景阴影
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
// 设置窗口背景为透明
window.setBackgroundDrawableResource(android.R.color.transparent);
window.setLayout((int) (ScreenUtils.getScreenWidth() * 375.f / 375), WindowManager.LayoutParams.WRAP_CONTENT);
// 设置点击事件
mBinding.xlhClose.setOnClickListener(v -> {
if (mListener != null) {
mListener.onCloseClick();
}
dismiss();
});
mBinding.ivAgain.setOnClickListener(v -> {
if (mListener != null) {
mListener.onPlayAgainClick();
}
dismiss();
});
initRecyclerView();
}
private void initRecyclerView() {
mAdapter = new GiftItemAdapter();
mBinding.rvHead.setLayoutManager(new GridLayoutManager(getContext(), 3));
mBinding.rvHead.setAdapter(mAdapter);
}
/**
* 设置礼物数据
*/
public void setGiftList(List<XlhDrawBean> giftList) {
this.mGiftList = giftList;
if (mAdapter != null) {
mAdapter.setNewData(giftList);
}
}
/**
* 设置点击回调监听器
*/
public void setOnGiftItemClickListener(OnGiftItemClickListener listener) {
this.mListener = listener;
}
@Override
public void initData() {
}
}

View File

@@ -0,0 +1,176 @@
package com.xscm.moduleutil.dialog.giftLottery;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.listener.OnRefreshLoadMoreListener;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.base.BaseMvpDialogFragment;
import com.xscm.moduleutil.bean.GiftBean;
import com.xscm.moduleutil.bean.WalletBean;
import com.xscm.moduleutil.bean.blindboxwheel.BlindBoxBean;
import com.xscm.moduleutil.bean.blindboxwheel.BlindReslutBean;
import com.xscm.moduleutil.bean.blindboxwheel.XlhDrawBean;
import com.xscm.moduleutil.databinding.DialogGiftLotteryFragmentBinding;
import com.xscm.moduleutil.databinding.DialogRankingXlhFragmentBinding;
import com.xscm.moduleutil.widget.pagerecyclerview.PagerGridSnapHelper;
import java.util.ArrayList;
import java.util.List;
/**
*@author qx
*@data 2025/9/4
*@description:巡乐会榜单
*/
public class XlhRankingDialog extends BaseMvpDialogFragment<GiftLotteryPresenter, DialogRankingXlhFragmentBinding> implements GiftLotteryContacts.View{
private int page=1;
private String roomId;
private GiftRecordAdapte giftRecordAdapte;
@Override
protected GiftLotteryPresenter bindPresenter() {
return new GiftLotteryPresenter(this,getSelfActivity());
}
public static XlhRankingDialog newInstance(String giftBagId,int type) {
Bundle args = new Bundle();
XlhRankingDialog fragment = new XlhRankingDialog();
args.putString("roomId", giftBagId);
fragment.setArguments(args);
return fragment;
}
@Nullable
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setCancelable(true);
return dialog;
}
@Override
public void onStart() {
super.onStart();
Window window = getDialog().getWindow();
if (window != null) {
// 获取屏幕高度
android.util.DisplayMetrics displayMetrics = new android.util.DisplayMetrics();
requireActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int screenHeight = displayMetrics.heightPixels;
// 设置高度为屏幕高度的100%(全屏)
int heightInPx = (int) (screenHeight * 0.8);;
window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, heightInPx);
window.setBackgroundDrawableResource(android.R.color.transparent);
// 可选:设置动画样式(从底部弹出)
window.setWindowAnimations(R.style.CommonShowDialogBottom);
}
}
@Override
protected void initDialogStyle(Window window) {
super.initDialogStyle(window);
window.setGravity(Gravity.BOTTOM);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
roomId = getArguments().getString("roomId");
}
@Override
protected void initData() {
MvpPre.xlhAllRecord(roomId, "1", "20",1);
}
@Override
protected void initView() {
mBinding.smartRefreshLayout.setOnRefreshLoadMoreListener(new OnRefreshLoadMoreListener() {
@Override
public void onRefresh(@NonNull RefreshLayout refreshLayout) {
page = 1;
MvpPre.xlhAllRecord(roomId, page+"", "20",1);
}
@Override
public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
page++;
MvpPre.xlhAllRecord(roomId, page+"", "20",1);
}
});
giftRecordAdapte=new GiftRecordAdapte();
mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
mBinding.recyclerView.setAdapter(giftRecordAdapte);
}
@Override
protected int getLayoutId() {
return R.layout.dialog_ranking_xlh_fragment;
}
@Override
public void getGiftListSuccess(BlindBoxBean blindBoxBean) {
}
@Override
public void drawGiftListSuccess(BlindReslutBean blindReslutBean) {
}
@Override
public void getMyRecordSuccess(List<GiftBean> data) {
}
@Override
public void getAllRecordSuccess(List<GiftBean> data) {
if (data != null){
if (page==1){
giftRecordAdapte.setNewData(data);
}else {
giftRecordAdapte.addData(data);
}
}else {
if (page == 1) {
giftRecordAdapte.setNewData(null);
}
}
}
@Override
public void finishRefreshLoadMore() {
mBinding.smartRefreshLayout.finishRefresh();
mBinding.smartRefreshLayout.finishLoadMore();
}
@Override
public void wallet(WalletBean walletBean) {
}
@Override
public void xlhChouSuccess(List<XlhDrawBean> data) {
}
}

View File

@@ -0,0 +1,182 @@
package com.xscm.moduleutil.dialog.giftLottery;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.listener.OnRefreshLoadMoreListener;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.base.BaseMvpDialogFragment;
import com.xscm.moduleutil.bean.GiftBean;
import com.xscm.moduleutil.bean.WalletBean;
import com.xscm.moduleutil.bean.blindboxwheel.BlindBoxBean;
import com.xscm.moduleutil.bean.blindboxwheel.BlindReslutBean;
import com.xscm.moduleutil.bean.blindboxwheel.XlhDrawBean;
import com.xscm.moduleutil.databinding.DialogGiftLotteryFragmentBinding;
import com.xscm.moduleutil.databinding.DialogXlhRecordFragmentBinding;
import com.xscm.moduleutil.widget.pagerecyclerview.PagerGridSnapHelper;
import java.util.ArrayList;
import java.util.List;
/**
*@author qx
*@data 2025/9/4
*@description:巡乐会记录
*/
public class XlhRecordDialog extends BaseMvpDialogFragment<GiftLotteryPresenter, DialogXlhRecordFragmentBinding> implements GiftLotteryContacts.View{
private int page=1;
private String roomId;
private GiftRecordAdapter adapter;
@Override
protected GiftLotteryPresenter bindPresenter() {
return new GiftLotteryPresenter(this,getSelfActivity());
}
public static XlhRecordDialog newInstance(String roomId) {
Bundle args = new Bundle();
XlhRecordDialog fragment = new XlhRecordDialog();
args.putString("roomId", roomId);
fragment.setArguments(args);
return fragment;
}
@Nullable
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setCancelable(true);
return dialog;
}
@Override
public void onStart() {
super.onStart();
Window window = getDialog().getWindow();
if (window != null) {
// 获取屏幕高度
android.util.DisplayMetrics displayMetrics = new android.util.DisplayMetrics();
requireActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int screenHeight = displayMetrics.heightPixels;
// 设置高度为屏幕高度的100%(全屏)
int heightInPx = (int) (screenHeight * 0.8);;
window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, heightInPx);
window.setBackgroundDrawableResource(android.R.color.transparent);
// 可选:设置动画样式(从底部弹出)
window.setWindowAnimations(R.style.CommonShowDialogBottom);
}
}
@Override
protected void initDialogStyle(Window window) {
super.initDialogStyle(window);
window.setGravity(Gravity.BOTTOM);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
roomId = getArguments().getString("roomId");
}
@Override
protected void initData() {
MvpPre.xlhMyRecord(roomId, "1", "20");
}
@Override
protected void initView() {
mBinding.smartRefreshLayout.setOnRefreshLoadMoreListener(new OnRefreshLoadMoreListener() {
@Override
public void onRefresh(@NonNull RefreshLayout refreshLayout) {
page = 1;
MvpPre.xlhMyRecord(roomId, page+"", "20");
}
@Override
public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
page++;
MvpPre.xlhMyRecord(roomId, page+"", "20");
}
});
adapter=new GiftRecordAdapter();
GridLayoutManager layoutManager = new GridLayoutManager(getActivity(), 3);
mBinding.recyclerView.setLayoutManager(layoutManager);
mBinding.recyclerView.setOnFlingListener(null);
// 设置滚动辅助工具
PagerGridSnapHelper pageSnapHelper = new PagerGridSnapHelper();
pageSnapHelper.attachToRecyclerView(mBinding.recyclerView);
mBinding.recyclerView.setAdapter(adapter);
}
@Override
protected int getLayoutId() {
return R.layout.dialog_xlh_record_fragment;
}
@Override
public void getGiftListSuccess(BlindBoxBean blindBoxBean) {
}
@Override
public void drawGiftListSuccess(BlindReslutBean blindReslutBean) {
}
@Override
public void getMyRecordSuccess(List<GiftBean> data) {
if (data != null){
if (page==1){
adapter.setNewData(data);
}else {
adapter.addData(data);
}
}else {
if (page == 1) {
adapter.setNewData(null);
}
}
}
@Override
public void getAllRecordSuccess(List<GiftBean> data) {
}
@Override
public void finishRefreshLoadMore() {
mBinding.smartRefreshLayout.finishRefresh();
mBinding.smartRefreshLayout.finishLoadMore();
}
@Override
public void wallet(WalletBean walletBean) {
}
@Override
public void xlhChouSuccess(List<XlhDrawBean> data) {
}
}

View File

@@ -0,0 +1,17 @@
package com.xscm.moduleutil.event;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
*@author qx
*@data 2025/9/22
*@description: 关闭飘屏
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class FloatingScreenEvent {
private boolean floatingScreen;
}

View File

@@ -0,0 +1,17 @@
package com.xscm.moduleutil.event;
import lombok.Data;
import java.io.Serializable;
/**
* 小时榜飘屏
*/
@Data
public class HourlyBean implements Serializable {
private static final long serialVersionUID = 1L;
private String room_id;
private String room_name;
private String text;
private String rank_number;
}

View File

@@ -0,0 +1,37 @@
package com.xscm.moduleutil.event;
/**
*@author qx
*@data 2025/8/28
*@description: 这是创建一枚举,根据类型的不同,创建不同的弹窗
*/
public enum LotteryEvent {
MIRROR_SKY("10"), // 天空之境
CITY_TIME("11"), //岁月之城
PINNACLE_TIME("12"); // 时光之巅
private final String type;
LotteryEvent(String type) {
this.type = type;
}
public String getType() {
return type;
}
// 根据giftBagId字符串判断类型
public static LotteryEvent fromLotteryEvent(String giftBagId) {
if (giftBagId.equals("10")) {
return MIRROR_SKY;
}
if (giftBagId.equals("11")) {
return CITY_TIME;
}
if (giftBagId.equals("12")) {
return PINNACLE_TIME;
}
return MIRROR_SKY;
}
}

View File

@@ -0,0 +1,76 @@
package com.xscm.moduleutil.event;
public enum QXRoomSeatViewType {
/**
* 无类型
*/
NONE(0, "无类型"),
/**
* 普通麦位(二卡八麦)
*/
NORMAL(1, "点唱"),
KTV(3,"K歌"),
/**
* 拍卖麦位
*/
AUCTION(2, "拍卖麦位"),
/**
* 小黑屋麦位
*/
CABIN(6, "小黑屋麦位"),
/**
* 交友房麦位
*/
FRIEND(7, "交友房麦位");
private final int value;
private final String description;
QXRoomSeatViewType(int value, String description) {
this.value = value;
this.description = description;
}
public int getValue() {
return value;
}
public String getDescription() {
return description;
}
public static QXRoomSeatViewType fromLotteryEvent(int value) {
if (value==1) {
return NORMAL;
}
if (value==2) {
return AUCTION;
}
if (value==3) {
return KTV;
}
if (value==6){
return CABIN;
}
if (value==7){
return FRIEND;
}
return NONE;
}
@Override
public String toString() {
return "QXRoomSeatViewType{" +
"value=" + value +
", description='" + description + '\'' +
'}';
}
}

View File

@@ -0,0 +1,14 @@
package com.xscm.moduleutil.event;
import lombok.Data;
import java.io.Serializable;
@Data
public class RedBean implements Serializable {
private static final long serialVersionUID = 1L;
private String room_id;
private String room_name;
private String text;
private String nickname;
}

View File

@@ -0,0 +1,17 @@
package com.xscm.moduleutil.event;
/**
* 红包打开状态
*/
public enum RedEnvelopeStatus {
/// 打开红包
QXRedBagDrawTypeOpen,
/// 仅倒计时
QXRedBagDrawTypeTimeDown,
/// 仅收藏房间
QXRedBagDrawTypeCollect,
/// 手慢了被领完了
QXRedBagDrawTypeFinished,
/// 发送评论领红包
QXRedBagDrawTypePwdSend,
}

View File

@@ -0,0 +1,134 @@
package com.xscm.moduleutil.http;
import android.content.Context;
import android.widget.Toast;
import com.blankj.utilcode.util.LogUtils;
import com.blankj.utilcode.util.ToastUtils;
import com.xscm.moduleutil.base.CommonAppContext;
import org.greenrobot.eventbus.EventBus;
import java.io.IOException;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* 通用的API响应处理回调类
* 统一处理所有接口的响应和错误情况
*/
public abstract class ApiResponseCallback<T> implements Callback<BaseModel<T>> {
private Context mContext;
// 构造方法,传入上下文用于显示提示
public ApiResponseCallback(Context context) {
this.mContext = context;
}
@Override
public void onResponse(Call<BaseModel<T>> call, Response<BaseModel<T>> response) {
// 统一处理HTTP响应
if (response.isSuccessful()) {
// 处理200-299范围内的HTTP状态码
BaseModel<T> body = response.body();
if (body != null) {
// 根据code值进行不同处理
switch (body.getCode()) {
case 1: // 接口返回成功
// 业务成功,回调给具体实现
// 即使data为null也调用onSuccess由具体实现决定如何处理null值
onSuccess(body.getData());
break;
case 0: // 接口请求成功但数据错误
// 显示错误信息
// String errorMsg = body.getMsg() != null ? body.getMsg() : "操作失败,请重试";
// showToast(errorMsg);
onFailure(new Exception(body.getMsg()));
break;
case 301: // 登录失效
// 显示错误信息并退出应用
// String loginErrorMsg = body.getMsg() != null ? body.getMsg() : "登录已失效,请重新登录";
showToast(body.getMsg());
try {
// 发送退出登录事件
// EventBus.getDefault().post(new com.xscm.moduleutil.event.LogOutEvent());
// 清除登录信息
CommonAppContext.getInstance().clearLoginInfo();
// 跳转到登录页面
// android.content.Intent intent = new android.content.Intent(CommonAppContext.getInstance(), Class.forName("com.xscm.midi.LaunchPageActivity"));
// intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK | android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK);
// CommonAppContext.getInstance().startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
onFailure(new Exception(body.getMsg()));
break;
default:
// 其他错误情况
String defaultErrorMsg = body.getMsg() != null ? body.getMsg() : "未知错误";
showToast(defaultErrorMsg);
onFailure(new Exception(defaultErrorMsg));
break;
}
} else {
// 响应体为空的情况
String errorMsg = "获取数据失败,请重试";
showToast(errorMsg);
onFailure(new Exception(errorMsg));
}
} else {
// 处理HTTP错误状态码
String errorInfo;
try {
if (response.errorBody() != null) {
errorInfo = response.errorBody().string();
// 可以在这里统一解析错误响应体
} else {
errorInfo = "请求失败,状态码:" + response.code();
}
} catch (IOException e) {
errorInfo = "请求失败,状态码:" + response.code();
e.printStackTrace();
}
showToast("");
onFailure(new Exception(errorInfo));
}
}
@Override
public void onFailure(Call<BaseModel<T>> call, Throwable t) {
// 统一处理网络异常
String errorMsg;
if (t instanceof IOException) {
// errorMsg = "网络异常,请检查网络连接";
} else {
// errorMsg = "请求处理失败,请重试";
}
showToast("");
// 回调给具体实现处理
onFailure(t);
}
// 显示提示信息
private void showToast(String message) {
if (mContext != null) {
Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
}
}
// 业务成功时的回调,由具体接口实现
public abstract void onSuccess(T data);
// 错误时的回调,可选实现
public void onFailure(Throwable t) {
// 可以留空,由子类选择性实现
LogUtils.e("接口错误:",t);
}
}

View File

@@ -0,0 +1,74 @@
package com.xscm.moduleutil.http;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import com.blankj.utilcode.util.ToastUtils;
import com.xscm.moduleutil.base.CommonAppContext;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class BusinessAwareConverterFactory extends Converter.Factory {
private final GsonConverterFactory originalFactory;
private final Context context;
public BusinessAwareConverterFactory(Context context) {
this.context = context;
this.originalFactory = GsonConverterFactory.create();
}
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type,
Annotation[] annotations,
Retrofit retrofit) {
final Converter<ResponseBody, ?> delegate =
originalFactory.responseBodyConverter(type, annotations, retrofit);
return new Converter<ResponseBody, Object>() {
@Override
public Object convert(ResponseBody value) throws IOException {
// 先读取响应字符串检查业务状态码
String responseString = value.string();
try {
JSONObject jsonObject = new JSONObject(responseString);
int code = jsonObject.getInt("code");
String msg = jsonObject.getString("msg");
if (code == 301) {
handleForceLogout();
ToastUtils.showShort(msg);
}
// 重新构建 ResponseBody 供原始转换器使用
ResponseBody newValue = ResponseBody.create(value.contentType(), responseString);
return delegate.convert(newValue);
} catch (JSONException e) {
throw new IOException("");
}
}
private void handleForceLogout() {
new Handler(Looper.getMainLooper()).post(() -> {
try {
CommonAppContext.getInstance().clearLoginInfo();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
});
}
};
}
}

View File

@@ -0,0 +1,13 @@
package com.xscm.moduleutil.rtc;
import lombok.Data;
/**
*@author qx
*@data 2025/9/18
*@description: 加入声网返回
*/
@Data
public class AgoraIsOPen {
private boolean isOpen;
}

View File

@@ -0,0 +1,124 @@
package com.xscm.moduleutil.service;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
import com.blankj.utilcode.util.LogUtils;
import com.blankj.utilcode.util.ToastUtils;
import com.tencent.imsdk.v2.V2TIMManager;
import com.tencent.imsdk.v2.V2TIMSDKListener;
import com.tencent.imsdk.v2.V2TIMUserFullInfo;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.base.CommonAppContext;
import com.xscm.moduleutil.http.RetrofitClient;
public class IMConnectionService extends Service {
private static final String TAG = "IMConnectionService";
private static final int NOTIFICATION_ID = 2;
private static final String CHANNEL_ID = "im_connection_channel";
private final V2TIMSDKListener imSdkListener = new V2TIMSDKListener() {
@Override
public void onConnecting() {
Log.d(TAG, "IM connecting...");
}
@Override
public void onConnectSuccess() {//重连成功
Log.d(TAG, "IM connect success");
if (CommonAppContext.getInstance().playId != null) {
LogUtils.e("@@@", ""+CommonAppContext.getInstance().playId);
RetrofitClient.getInstance().roomUserReconnect(CommonAppContext.getInstance().playId);
}
}
@Override
public void onConnectFailed(int code, String error) {
Log.e(TAG, "IM connect failed, code: " + code + ", error: " + error);
}
@Override
public void onKickedOffline() {
Log.w(TAG, "IM kicked offline");
if (CommonAppContext.getInstance().playId != null) {
ToastUtils.showShort("您的账号已被挤下线");
try {
CommonAppContext.getInstance().clearLoginInfo();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
@Override
public void onUserSigExpired() {
Log.w(TAG, "IM user sig expired");
}
@Override
public void onSelfInfoUpdated(V2TIMUserFullInfo info) {
Log.d(TAG, "IM self info updated");
}
};
@Override
public void onCreate() {
super.onCreate();
startForegroundService();
V2TIMManager.getInstance().addIMSDKListener(imSdkListener);
Log.d(TAG, "IMConnectionService created and listener registered");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY; // 服务被杀死后会自动重启
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void startForegroundService() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 创建通知渠道
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
"IM Connection Service",
NotificationManager.IMPORTANCE_LOW
);
NotificationManager manager = getSystemService(NotificationManager.class);
if (manager != null) {
manager.createNotificationChannel(channel);
}
// 创建通知
Notification notification = new Notification.Builder(this, CHANNEL_ID)
.setContentTitle("IM连接服务")
.setContentText("保持IM连接活跃")
.setSmallIcon(R.mipmap.default_avatar)
.setOngoing(true)
.build();
startForeground(NOTIFICATION_ID, notification);
}
}
@Override
public void onDestroy() {
super.onDestroy();
V2TIMManager.getInstance().removeIMSDKListener(imSdkListener);
Log.d(TAG, "IMConnectionService destroyed and listener unregistered");
}
}

View File

@@ -0,0 +1,221 @@
package com.xscm.moduleutil.service;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import com.blankj.utilcode.util.LogUtils;
import com.google.android.gms.common.api.Api;
import com.hjq.toast.ToastUtils;
import com.xscm.moduleutil.utils.logger.DataLogger;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttTopic;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import java.util.ArrayList;
public class MqttConnect {
private String HOST;
private String Tag = "MQTT";
private String clientId = "";
private static MqttClient mqttClient = null;
private Context context;
// 订阅主题
public static String shutdown = "";
public static String update_app = "";
public static String qx_hour_ranking = "";
public static String qx_redpacket_arrive="";//红包飘屏的主题
Handler handler = new Handler(Looper.getMainLooper());
String[] topic;
int[] qos = {1,2,3,0,0,0,0,0,0,0,0,0,0}; // 消息质量
private static MqttConnect instance;
public MqttConnect(Context context, String host, String clientId) {
this.HOST = host;
this.context = context;
this.clientId = clientId;
// 这里是你自己需要订阅的主题
shutdown = "qx_room_topic"; // 关机
update_app = "qx_xunlehui"; // 发送更新APP
// qx_hour_ranking = "qx_hour_ranking";
qx_hour_ranking = "qx_hour_ranking";
qx_redpacket_arrive="qx_redpacket_arrive";
ArrayList<String> topicList = new ArrayList<>();
topicList.add(shutdown);
topicList.add(update_app);
topicList.add(qx_hour_ranking);
topicList.add(qx_redpacket_arrive);
topic = topicList.toArray(new String[0]);
}
/**
* 单列模式,只能实例化一次
* @param context
* @param host
* @param clientId
* @return
*/
public static synchronized MqttConnect getInstance(Context context, String host, String clientId) {
if (instance == null) {
instance = new MqttConnect(context, host, clientId);
}
return instance;
}
/**
* 客户端connect连接mqtt服务器
**/
public void mqttClient()
{
// close();
// handler.postDelayed(new Runnable() {
// @Override
// public void run() {
try {
// uiTip("MQTT开始连接");
MqttConnectOptions options = mqttConnectOptions();
mqttClient.setCallback(new MqttInitCallback(context, HOST, clientId));
mqttClient.connect(options);
// sub(topic,qos);
sub(shutdown);
sub(update_app);
sub(qx_hour_ranking);
sub(qx_redpacket_arrive);
// uiTip("MQTT连接成功");
}catch (MqttException e){
// uiTip("MQTT连接失败,准备重连。。。:"+e.getMessage());
handler.postDelayed(new Runnable() {
@Override
public void run() {
Log.e(Tag,"开始重连。。。");
mqttClient();
}
},3000);
}
// }
// },200);
}
/**
* 在主线程弹出消息
* @param msg
*/
private void uiTip(String msg){
Log.d(Tag,msg);
handler.post(new Runnable() {
@Override
public void run() {
// Toast.makeText(context.getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
LogUtils.e("mqtt","连接成功");
ToastUtils.show(msg);
}
});
}
/**
* MQTT连接参数设置
*/
private MqttConnectOptions mqttConnectOptions()
throws MqttException {
mqttClient = new MqttClient(HOST, clientId, new MemoryPersistence());
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName("public");
options.setConnectionTimeout(10);
options.setCleanSession(true);
options.setConnectionTimeout(10);
options.setKeepAliveInterval(10);
return options;
}
/**
* 关闭MQTT连接
*/
public void close(){
if(mqttClient != null && mqttClient.isConnected()){
try {
mqttClient.close();
mqttClient.disconnect();
mqttClient = null;
} catch (MqttException e) {
Log.e(Tag,"关闭MQTT连接报错"+e.getMessage());
// ToastUtils.show("关闭MQTT连接报错");
}
}else {
Log.d(Tag,"Mqtt已关闭");
}
}
/**
* 向某个主题发布消息 默认qos1
*/
public static void pub(String topic, String msg) throws MqttException {
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(msg.getBytes());
MqttTopic mqttTopic = mqttClient.getTopic(topic);
MqttDeliveryToken token = mqttTopic.publish(mqttMessage);
token.waitForCompletion();
}
/**
* 向某个主题发布消息
*
* @param topic: 发布的主题
* @param msg: 发布的消息
* @param qos: 消息质量 Qos0、1、2
*/
public void pub(String topic, String msg, int qos) throws MqttException {
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setQos(qos);
mqttMessage.setPayload(msg.getBytes());
MqttTopic mqttTopic = mqttClient.getTopic(topic);
MqttDeliveryToken token = mqttTopic.publish(mqttMessage);
token.waitForCompletion();
}
/**
* 订阅某一个主题 此方法默认的的Qos等级为1
*
* @param topic 主题
*/
public void sub(String topic){
try {
mqttClient.subscribe(topic);
} catch (MqttException e) {
Log.e(Tag,"MQTT主题订阅失败" + e.getMessage());
// uiTip("MQTT主题订阅失败");
}
}
/**
* 订阅某一个主题可携带Qos
*
* @param topic 所要订阅的主题
* @param qos
* 消息质量0最多发送一次不保证消息能够到达接收端也不负责重发
* 1至少发送一次确保消息能够到达接收端但可能会导致消息重复
* 2确保消息恰好被接收一次
*/
public void sub(String[] topic, int[] qos){
try {
mqttClient.subscribe(topic, qos);
}catch (MqttException e){
Log.e(Tag,"订阅主题失败:"+e.getMessage());
}
}
// 原文链接https://blog.csdn.net/Fyx1987496919/article/details/140516525
}

View File

@@ -0,0 +1,234 @@
package com.xscm.moduleutil.service;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.blankj.utilcode.util.GsonUtils;
import com.blankj.utilcode.util.LogUtils;
import com.hjq.toast.ToastUtils;
import com.orhanobut.logger.Logger;
import com.xscm.moduleutil.bean.MqttXlhEnd;
import com.xscm.moduleutil.bean.XLHBean;
import com.xscm.moduleutil.event.HourlyBean;
import com.xscm.moduleutil.event.RedBean;
import com.xscm.moduleutil.event.RoomGiftRunable;
import com.xscm.moduleutil.utils.SpUtil;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.greenrobot.eventbus.EventBus;
import java.util.List;
public class MqttInitCallback implements MqttCallback {
private String Tag = "MqttInitCallback";
private String HOST;
private String clientId = "";
private MqttConnect mqttConnect = null;
private Context context = null;
MqttInitCallback(Context context, String host, String clientId) {
this.context = context;
this.HOST = host;
this.clientId = clientId;
}
/**
* 连接丢失
*/
@Override
public void connectionLost(Throwable cause) {
Log.d(Tag, "mqtt连接断开,执行重连");
// ToastUtils.show("mqtt连接断开,执行重连");
mqttConnect = MqttConnect.getInstance(context, HOST, clientId);
mqttConnect.mqttClient();
}
/**
* subscribe订阅后得到的消息会执行到这里
*/
@Override
public void messageArrived(String topic, MqttMessage message) {
// Log.d(Tag,"topic");
// Log.d(Tag,topic);
String messageStr = message.toString();
Logger.e("MQTT", "收到的消息", "主题:" + topic + " 收到的消息:" + messageStr);
if (topic.equals("qx_room_topic")) {
// ToastUtils.show("收到礼物飘屏");
receiveMessage(topic, messageStr);
} else if (topic.equals("qx_xunlehui")) {
// ToastUtils.show("收到轮盘飘屏");
receiveXlhMessage(messageStr);
} else if (topic.equals("qx_hour_ranking")) {
receiveQXHourRanking(topic, messageStr);
} else if (topic.equals("qx_redpacket_arrive")) {
receiveRed(topic, messageStr);
}
}
private void receiveRed(String topic, String messageStr) {
try {
JSONObject jsonObject = JSON.parseObject(messageStr);
String message = jsonObject.getString("msg");
// 将事件处理放到主线程执行
new Handler(Looper.getMainLooper()).post(() -> {
processRedMessage(topic, message);
});
} catch (Exception e) {
Log.e("MQTT", "解析MQTT消息异常", e);
}
}
private void processRedMessage(String topic, String message) {
try {
// 如果 data 是集合字符串
// 解析为集合
RedBean dataList = JSON.parseObject(message, RedBean.class);
// 在主线程处理集合数据
new Handler(Looper.getMainLooper()).post(() -> {
processDataRed(dataList);
});
} catch (Exception e) {
Log.e("MQTT", "解析MQTT消息异常", e);
}
}
private void processDataRed(RedBean dataList) {
// 遍历集合并发送每个元素
// for (HourlyBean dataItem : dataList) {
// EventBus.getDefault().post(dataItem);
// }
// 或者发送整个集合
EventBus.getDefault().post(dataList);
}
private void receiveQXHourRanking(String topic, String data) {
try {
JSONObject jsonObject = JSON.parseObject(data);
int type = jsonObject.getIntValue("type");
String message = jsonObject.getString("msg");
// 将事件处理放到主线程执行
new Handler(Looper.getMainLooper()).post(() -> {
processMessage(topic, message);
});
} catch (Exception e) {
Log.e("MQTT", "解析MQTT消息异常", e);
// ToastUtils.show("收到礼物飘屏,解析异常");
}
}
private void processMessage(String topic, String data) {
try {
// 如果 data 是集合字符串
if (isJsonArray(data)) {
// 解析为集合
List<HourlyBean> dataList = JSON.parseArray(data, HourlyBean.class);
// 在主线程处理集合数据
new Handler(Looper.getMainLooper()).post(() -> {
processDataList(dataList);
});
}
} catch (Exception e) {
Log.e("MQTT", "解析MQTT消息异常", e);
}
}
// 处理集合数据
private void processDataList(List<HourlyBean> dataList) {
// 遍历集合并发送每个元素
// for (HourlyBean dataItem : dataList) {
// EventBus.getDefault().post(dataItem);
// }
// 或者发送整个集合
EventBus.getDefault().post(dataList);
}
// 判断是否为 JSON 数组
private boolean isJsonArray(String jsonString) {
try {
return JSON.parseArray(jsonString) != null;
} catch (Exception e) {
return false;
}
}
private void receiveMessage(String topic, String data) {
try {
JSONObject jsonObject = JSON.parseObject(data);
int type = jsonObject.getIntValue("type");
String message = jsonObject.getString("msg");
// 将事件处理放到主线程执行
new Handler(Looper.getMainLooper()).post(() -> {
processMessageType(type, message);
});
} catch (Exception e) {
Log.e("MQTT", "解析MQTT消息异常", e);
// ToastUtils.show("收到礼物飘屏,解析异常");
}
}
private void processMessageType(int type, String message) {
switch (type) {
case 5019://推送所有人-横幅礼物通知
new RoomGiftRunable(message).run();
break;
case 8000:
// XLHBean xlhBean= GsonUtils.fromJson(message, XLHBean.class);
// if (xlhBean!=null && xlhBean.getRoom_id()!=null && SpUtil.getMyRoomId()!=null) {
// if (xlhBean.getRoom_id().equals(SpUtil.getMyRoomId())) {
// if (xlhBean.getFrom_type()==3) {
LogUtils.e("MQTT", "收到消息" + message);
MqttXlhEnd mqttXlhEnd = new MqttXlhEnd();
mqttXlhEnd.setMessage(message);
EventBus.getDefault().post(mqttXlhEnd);
// }
// }
// }
break;
default:
break;
}
}
private void receiveXlhMessage(String messageStr) {
try {
JSONObject jsonObject = JSON.parseObject(messageStr);
int type = jsonObject.getIntValue("type");
String message = jsonObject.getString("msg");
XLHBean xlhBean = JSON.parseObject(message, XLHBean.class);
// 将事件处理放到主线程执行
new Handler(Looper.getMainLooper()).post(() -> {
processMessageType(type, message);
EventBus.getDefault().post(xlhBean);
});
} catch (Exception e) {
Log.e("MQTT", "解析MQTT消息异常", e);
// ToastUtils.show("收到轮盘飘屏,解析异常");
}
}
/**
* publish发布成功后会执行到这里
*/
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
}
// 原文链接https://blog.csdn.net/Fyx1987496919/article/details/140516525
}

View File

@@ -0,0 +1,19 @@
package com.xscm.moduleutil.utils;
/**
*@author qx
*@data 2025/9/10
*@description: 防止重复点击的工具类
*/
public class ClickUtils {
private static final long CLICK_INTERVAL = 1000; // 1000ms内不允许重复点击
private static long lastClickTime = 0;
public static boolean isFastDoubleClick() {
long currentTime = System.currentTimeMillis();
if (currentTime - lastClickTime < CLICK_INTERVAL) {
return true;
}
lastClickTime = currentTime;
return false;
}
}

View File

@@ -0,0 +1,41 @@
package com.xscm.moduleutil.utils;
import android.content.Context;
import android.util.Log;
import com.alibaba.android.arouter.launcher.ARouter;
public class CrashHandler implements Thread.UncaughtExceptionHandler {
private static CrashHandler instance;
private Thread.UncaughtExceptionHandler defaultHandler;
private CrashHandler(Context context) {
defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
}
public static void init(Context context) {
if (instance == null) {
instance = new CrashHandler(context);
Thread.setDefaultUncaughtExceptionHandler(instance);
}
}
@Override
public void uncaughtException(Thread t, Throwable e) {
// 记录崩溃日志
Log.e("CrashHandler", "未捕获异常: " + e.getMessage());
// 简单处理空指针
if (e instanceof NullPointerException) {
// 重启应用或跳转错误页
restartApp();
} else {
// 交给系统默认处理
defaultHandler.uncaughtException(t, e);
}
}
private void restartApp() {
// 实现应用重启逻辑
ARouter.getInstance().build(ARouteConstants.ME).navigation();
}
}

View File

@@ -0,0 +1,58 @@
package com.xscm.moduleutil.utils;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import com.xscm.moduleutil.service.IMConnectionService;
public class IMServiceManager {
private static IMServiceManager instance;
private boolean isServiceStarted = false;
private IMServiceManager() {
}
public static synchronized IMServiceManager getInstance() {
if (instance == null) {
instance = new IMServiceManager();
}
return instance;
}
public void startIMService(Context context) {
if (isServiceStarted) {
return;
}
Intent serviceIntent = new Intent(context, IMConnectionService.class);
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent);
} else {
context.startService(serviceIntent);
}
isServiceStarted = true;
} catch (Exception e) {
e.printStackTrace();
}
}
public void stopIMService(Context context) {
if (!isServiceStarted) {
return;
}
Intent serviceIntent = new Intent(context, IMConnectionService.class);
try {
context.stopService(serviceIntent);
isServiceStarted = false;
} catch (Exception e) {
e.printStackTrace();
}
}
public boolean isServiceStarted() {
return isServiceStarted;
}
}

View File

@@ -0,0 +1,105 @@
package com.xscm.moduleutil.utils;
import android.content.Context;
import com.blankj.utilcode.util.LogUtils;
import com.bumptech.glide.Glide;
import com.orhanobut.logger.Logger;
public class MemoryOptimizationUtils {
private static final String TAG = "MemoryOptimization";
/**
* 检查内存状态
*/
public static boolean isMemoryLow() {
Runtime runtime = Runtime.getRuntime();
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
long maxMemory = runtime.maxMemory();
double memoryUsage = (double) usedMemory / maxMemory;
LogUtils.d(TAG, "Memory usage: " + (memoryUsage * 100) + "%");
// 内存使用超过85%认为是低内存
return memoryUsage > 0.85;
}
private static long lastGCTime = 0;
private static final long MIN_GC_INTERVAL = 5000; // 5秒最小间隔
/**
* 强制进行垃圾回收
*/
public static void forceGC() {
long currentTime = System.currentTimeMillis();
// 避免频繁调用GC
if (currentTime - lastGCTime < MIN_GC_INTERVAL) {
Logger.d(TAG, "Skipping GC, too frequent");
return;
}
lastGCTime = currentTime;
// 使用异步方式调用GC
new Thread(() -> {
try {
// 在后台线程执行GC
System.gc();
Thread.sleep(100); // 给GC一些时间
Runtime.getRuntime().runFinalization();
Logger.d(TAG, "Garbage collection completed");
} catch (Exception e) {
Logger.e(TAG, "Error during GC: " + e.getMessage());
}
}).start();
}
/**
* 清理图片缓存
*/
public static void clearImageCache(Context context) {
try {
// 清理Glide缓存
Glide.get(context).clearMemory();
// 在后台线程清理磁盘缓存
new Thread(() -> {
try {
Glide.get(context).clearDiskCache();
} catch (Exception e) {
LogUtils.e(TAG, "Error clearing Glide disk cache: " + e.getMessage());
}
}).start();
} catch (Exception e) {
LogUtils.e(TAG, "Error clearing image cache: " + e.getMessage());
}
}
/**
* 获取当前内存使用情况
*/
public static String getMemoryInfo() {
Runtime runtime = Runtime.getRuntime();
long maxMemory = runtime.maxMemory();
long totalMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
long usedMemory = totalMemory - freeMemory;
return String.format("Max: %d MB, Total: %d MB, Used: %d MB, Free: %d MB",
maxMemory / (1024 * 1024),
totalMemory / (1024 * 1024),
usedMemory / (1024 * 1024),
freeMemory / (1024 * 1024));
}
/**
* 清理SVGA缓存
*/
public static void clearSVGACache() {
try {
// 如果SVGA库提供了清理缓存的方法调用它
// SVGAParser.clearCache(); // 假设有这样的方法
} catch (Exception e) {
LogUtils.e(TAG, "Error clearing SVGA cache: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,281 @@
package com.xscm.moduleutil.utils;
import android.os.Handler;
import android.os.Looper;
import com.blankj.utilcode.util.LogUtils;
import com.xscm.moduleutil.bean.RedPacketInfo;
import lombok.Getter;
import lombok.Setter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* 红包管理器单例类
*/
public class QXRedPacketManager {
private static QXRedPacketManager instance;
private final Map<String, RedPacketInfo> redPackets;
private Handler checkTimerHandler;
private Runnable checkTimerRunnable;
// 私有构造函数,防止外部实例化
private QXRedPacketManager() {
this.redPackets = new ConcurrentHashMap<>();
}
/**
* 获取单例实例
*
* @return QXRedPacketManager 单例
*/
public static QXRedPacketManager getInstance() {
if (instance == null) {
synchronized (QXRedPacketManager.class) {
if (instance == null) {
instance = new QXRedPacketManager();
}
}
}
return instance;
}
public List<RedPacketInfo> getSortedUserListLambda(Map<String, RedPacketInfo> userMap) {
List<RedPacketInfo> redPacketInfoList = new ArrayList<>(userMap.values());
redPacketInfoList.sort((user1, user2) -> Long.compare(user1.getStart_time(), user2.getStart_time()));
return redPacketInfoList;
}
/**
* 添加红包列表
*
* @param redPackets 红包模型列表
*/
public void addRedPackets(List<RedPacketInfo> redPackets) {
if (redPackets == null || redPackets.isEmpty()) {
return;
}
for (RedPacketInfo model : redPackets) {
this.redPackets.put(model.getRedpacket_id(), model);
}
// 在添加数据后启动定时器(如果尚未启动)
startCheckTimer();
if (this.delegate != null && this.delegate instanceof QXRedPacketManagerDelegate) {
((QXRedPacketManagerDelegate) this.delegate).onRedPacketsAdded(redPackets, this.redPackets.size());
}
}
/**
* 添加单个红包
*
* @param redPacket 红包模型
*/
public void addRedPacket(RedPacketInfo redPacket) {
if (redPacket == null || redPacket.getRedpacket_id() == null) {
return;
}
this.redPackets.put(redPacket.getRedpacket_id(), redPacket);
// 在添加数据后启动定时器(如果尚未启动)
startCheckTimer();
if (this.delegate != null && this.delegate instanceof QXRedPacketManagerDelegate) {
((QXRedPacketManagerDelegate) this.delegate).onRedPacketAdded(redPacket, this.redPackets.size());
}
}
/**
* 移除红包
*
* @param packetId 红包ID
*/
public void removeRedPacket(String packetId) {
this.redPackets.remove(packetId);
if (this.delegate != null && this.delegate instanceof QXRedPacketManagerDelegate) {
((QXRedPacketManagerDelegate) this.delegate).onRedPacketRemoved(packetId, this.redPackets.size());
}
}
/**
* 获取所有红包
*
* @return 红包列表
*/
public List<RedPacketInfo> getAllRedPackets() {
return getSortedUserListLambda(redPackets);
}
/**
* 根据ID获取红包
*
* @param packetId 红包ID
* @return 红包模型
*/
public RedPacketInfo getRedPacket(String packetId) {
return this.redPackets.get(packetId);
}
/**
* 开始检查定时器
*/
public void startCheckTimer() {
// 如果定时器已经在运行,直接返回
if (checkTimerRunnable != null && checkTimerHandler != null) {
return;
}
if (checkTimerRunnable == null) {
checkTimerRunnable = new Runnable() {
@Override
public void run() {
checkAndUpdateRedPackets();
}
};
checkTimerHandler = new Handler(Looper.getMainLooper());
checkTimerHandler.post(checkTimerRunnable);
}
}
/**
* 检查并更新红包状态
*/
private void checkAndUpdateRedPackets() {
// 添加空值检查
if (this.redPackets == null || this.redPackets.isEmpty()) {
return;
}
List<RedPacketInfo> packets = getAllRedPackets();
for (RedPacketInfo packet : packets) {
long packetTime = packet.remainingTime();
LogUtils.e("红包剩余时间:" + packet.getRedpacket_time());
long redpacketTime = 0;
try {
if (packet.getRedpacket_time() != null) {
redpacketTime = Long.parseLong(packet.getRedpacket_time());
}
} catch (NumberFormatException e) {
LogUtils.e("红包时间格式错误: " + packet.getRedpacket_time());
}
if (packetTime <= -redpacketTime) {
removeRedPacket(packet.getRedpacket_id());
}
if (packet.getCountdown()==0){
continue;
}
if (this.delegate != null && this.delegate instanceof QXRedPacketManagerDelegate) {
((QXRedPacketManagerDelegate) this.delegate).didUpdateRedPacketTime(packet, packetTime);
}
boolean wasAvailable = packet.isAvailable();
packet.setAvailable(packet.canOpenNow());
// 状态发生变化时通知
if (wasAvailable != packet.isAvailable()) {
if (this.delegate != null && this.delegate instanceof QXRedPacketManagerDelegate) {
((QXRedPacketManagerDelegate) this.delegate).onRedPacketUpdated(packet, this.redPackets.size());
}
}
}
// 继续执行定时任务
// 修复:增加空值检查避免 NullPointerException
if (checkTimerHandler != null && checkTimerRunnable != null) {
// 继续执行定时任务
checkTimerHandler.postDelayed(checkTimerRunnable, 1000);
}
}
/**
* 移除所有红包
*/
public void removeAllRedPackets() {
this.redPackets.clear();
endCheckTimer();
}
/**
* 结束检查定时器
*/
public void endCheckTimer() {
if (checkTimerHandler != null) {
checkTimerHandler.removeCallbacks(checkTimerRunnable);
checkTimerHandler = null;
checkTimerRunnable = null;
}
}
/**
* 销毁红包信息
*/
public void destroyRedpacketInfo() {
removeAllRedPackets();
endCheckTimer();
this.delegate = null;
}
/**
* 委托接口
*/
public interface QXRedPacketManagerDelegate {
/**
* 添加红包列表回调
*
* @param redPackets 红包列表
* @param remainingCount 剩余数量
*/
void onRedPacketsAdded(List<RedPacketInfo> redPackets, int remainingCount);
/**
* 添加单个红包回调
*
@param redPacket 红包模型
* @param remainingCount 剩余数量
*/
void onRedPacketAdded(RedPacketInfo redPacket, int remainingCount);
/**
* 移除红包回调
*
* @param packetId 红包ID
* @param remainingCount 剩余数量
*/
void onRedPacketRemoved(String packetId, int remainingCount);
/**
* 更新红包状态回调
*
* @param packet 红包模型
* @param remainingCount 剩余数量
*/
void onRedPacketUpdated(RedPacketInfo packet, int remainingCount);
/**
* 更新红包时间回调
*
* @param packet 红包模型
* @param packetTime 红包剩余时间
*/
void didUpdateRedPacketTime(RedPacketInfo packet, long packetTime);
}
/**
* -- SETTER --
* 设置委托对象
*
*
* -- GETTER --
* 获取委托对象
*
@param delegate 委托对象
* @return 委托对象
*/
@Getter
@Setter
private QXRedPacketManagerDelegate delegate;
}

View File

@@ -0,0 +1,171 @@
package com.xscm.moduleutil.utils;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Build;
import android.text.Html;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
import android.text.style.BackgroundColorSpan;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.UnderlineSpan;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
/**
* TextView 富文本工具类Java实现
* 支持HTML解析、部分文本样式、点击事件等功能
*/
public class TextViewUtils {
/**
* 显示HTML格式文本
* @param textView 目标TextView
* @param htmlContent HTML内容字符串
*/
public static void setHtmlText(TextView textView, String htmlContent) {
setHtmlText(textView, htmlContent, true);
}
/**
* 显示HTML格式文本可控制链接点击
* @param textView 目标TextView
* @param htmlContent HTML内容字符串
* @param enableLinks 是否启用链接点击
*/
public static void setHtmlText(TextView textView, String htmlContent, boolean enableLinks) {
if (textView == null || htmlContent == null) return;
// 处理不同Android版本的HTML解析
CharSequence spannedText;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
spannedText = Html.fromHtml(htmlContent, Html.FROM_HTML_MODE_COMPACT);
} else {
// 兼容Android N以下版本
spannedText = Html.fromHtml(htmlContent);
}
textView.setText(spannedText);
// 启用链接点击功能
if (enableLinks) {
textView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setHighlightColor(Color.TRANSPARENT); // 去除点击高亮
}
}
/**
* 给部分文本设置样式
* @param textView 目标TextView
* @param fullText 完整文本
* @param targetText 需要设置样式的子文本
* @param spans 样式集合(可传入多个)
*/
public static void setPartialStyle(TextView textView, String fullText,
String targetText, Object... spans) {
if (textView == null || fullText == null || targetText == null) return;
int startIndex = fullText.indexOf(targetText);
if (startIndex == -1) {
textView.setText(fullText);
return;
}
int endIndex = startIndex + targetText.length();
SpannableString spannable = new SpannableString(fullText);
// 应用所有样式
for (Object span : spans) {
spannable.setSpan(span, startIndex, endIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
textView.setText(spannable);
}
/**
* 设置可点击文本
* @param textView 目标TextView
* @param fullText 完整文本
* @param clickText 可点击的子文本
* @param linkColor 链接颜色
* @param isUnderline 是否显示下划线
* @param listener 点击事件监听器
*/
public static void setClickableText(TextView textView, String fullText, String clickText,
@ColorInt int linkColor, boolean isUnderline,
OnClickableTextListener listener) {
if (textView == null || fullText == null || clickText == null || listener == null) return;
int startIndex = fullText.indexOf(clickText);
if (startIndex == -1) {
textView.setText(fullText);
return;
}
int endIndex = startIndex + clickText.length();
SpannableString spannable = new SpannableString(fullText);
// 创建可点击样式
ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(@NonNull View widget) {
listener.onClick();
}
@Override
public void updateDrawState(@NonNull android.text.TextPaint ds) {
super.updateDrawState(ds);
ds.setColor(linkColor);
ds.setUnderlineText(isUnderline);
}
};
spannable.setSpan(clickableSpan, startIndex, endIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(spannable);
textView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setHighlightColor(Color.TRANSPARENT);
}
// 快捷创建样式的工具方法
public static StyleSpan createBoldSpan() {
return new StyleSpan(Typeface.BOLD);
}
public static StyleSpan createItalicSpan() {
return new StyleSpan(Typeface.ITALIC);
}
public static ForegroundColorSpan createTextColorSpan(@ColorInt int color) {
return new ForegroundColorSpan(color);
}
public static BackgroundColorSpan createBgColorSpan(@ColorInt int color) {
return new BackgroundColorSpan(color);
}
public static UnderlineSpan createUnderlineSpan() {
return new UnderlineSpan();
}
public static StrikethroughSpan createStrikethroughSpan() {
return new StrikethroughSpan();
}
public static RelativeSizeSpan createTextSizeSpan(float proportion) {
return new RelativeSizeSpan(proportion);
}
/**
* 可点击文本的监听器接口
*/
public interface OnClickableTextListener {
void onClick();
}
}

View File

@@ -0,0 +1,146 @@
package com.xscm.moduleutil.utils.cos;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.Nullable;
import com.blankj.utilcode.util.LogUtils;
import com.tencent.cos.xml.CosXmlService;
import com.tencent.cos.xml.CosXmlServiceConfig;
import com.tencent.cos.xml.exception.CosXmlClientException;
import com.tencent.cos.xml.exception.CosXmlServiceException;
import com.tencent.cos.xml.listener.CosXmlResultListener;
import com.tencent.cos.xml.model.CosXmlRequest;
import com.tencent.cos.xml.model.CosXmlResult;
import com.tencent.cos.xml.model.object.PutObjectRequest;
import com.tencent.cos.xml.transfer.COSXMLUploadTask;
import com.tencent.cos.xml.transfer.TransferConfig;
import com.tencent.cos.xml.transfer.TransferManager;
import com.tencent.qcloud.core.auth.SessionQCloudCredentials;
import com.xscm.moduleutil.http.BaseObserver;
import com.xscm.moduleutil.http.RetrofitClient;
import com.xscm.moduleutil.utils.oss.OSSOperUtils;
import io.reactivex.disposables.Disposable;
import org.jetbrains.annotations.NotNull;
/**
* com.xscm.moduleutil.utils.cos
* qx
* 2025/10/23
*/
public class CosUploadManager {
private static volatile CosUploadManager instance;
private Context context;
private Handler mainHandler;
// 私有构造函数,防止外部实例化
private CosUploadManager() {
mainHandler = new Handler(Looper.getMainLooper());
}
// 双重检查锁定获取单例实例
public static CosUploadManager getInstance() {
if (instance == null) {
synchronized (CosUploadManager.class) {
if (instance == null) {
instance = new CosUploadManager();
}
}
}
return instance;
}
public void init(Context context) {
this.context = context.getApplicationContext();
}
public void upParameters( String objectKey, String localPath, UploadCallback callback) {
// 确保已初始化
if (context == null) {
callback.onFailure(new IllegalStateException("CosUploadManager not initialized with context"));
return;
}
RetrofitClient.getInstance().getTempKey(new BaseObserver<TempKeyBean>() {
@Override
public void onSubscribe(@NotNull Disposable disposable) {
}
@Override
public void onNext(@NotNull TempKeyBean tempKeyBean) {
if (tempKeyBean != null){
upCosData(tempKeyBean, tempKeyBean.getBucket(), objectKey, localPath, callback);
}
}
});
}
public void upCosData(TempKeyBean tempKeyBean, String bucketName, String objectKey, String localFilePath, UploadCallback callback){
// 获取临时密钥(业务层控制获取的方式)
String tmpSecretId = tempKeyBean.getCredentials().getTmpSecretId(); // 临时密钥 SecretId
String tmpSecretKey = tempKeyBean.getCredentials().getTmpSecretKey(); // 临时密钥 SecretKey
String sessionToken = tempKeyBean.getCredentials().getSessionToken(); // 临时密钥 Token
long expiredTime = tempKeyBean.getExpiredTime();//临时密钥有效截止时间戳,单位是秒
// 建议返回服务器时间作为签名的开始时间,避免由于用户手机本地时间偏差过大导致请求过期
long startTime = tempKeyBean.getStartTime(); //临时密钥有效起始时间,单位是秒
// 存储桶所在地域简称,例如广州地区是 ap-guangzhou
String region = tempKeyBean.getRegion();
SessionQCloudCredentials sessionQCloudCredentials = new SessionQCloudCredentials(tmpSecretId, tmpSecretKey,
sessionToken, startTime, expiredTime);
// 创建 CosXmlServiceConfig 对象,根据需要修改默认的配置参数
CosXmlServiceConfig serviceConfig = new CosXmlServiceConfig.Builder()
.setRegion(region)
.isHttps(true) // 使用 HTTPS 请求, 默认为 HTTP 请求
.builder();
CosXmlService cosXmlService = new CosXmlService(context, serviceConfig);
// 任何 CosXmlRequest 都支持这种方式,例如上传 PutObjectRequest、下载 GetObjectRequest、删除 DeleteObjectRequest 等
// 以下用上传进行示例
PutObjectRequest putRequest = new PutObjectRequest(bucketName, objectKey, localFilePath);
// sessionQCloudCredentials 为第一步“初始化密钥”中获取到的单次临时密钥
putRequest.setCredential(sessionQCloudCredentials);
// 初始化 TransferConfig这里使用默认配置如果需要定制请参考 SDK 接口文档
TransferConfig transferConfig = new TransferConfig.Builder().build();
// 初始化 TransferManager
TransferManager transferManager = new TransferManager(cosXmlService, transferConfig);
COSXMLUploadTask uploadTask = transferManager.upload(putRequest, null);
uploadTask.setCosXmlResultListener(new CosXmlResultListener() {
@Override
public void onSuccess(CosXmlRequest cosXmlRequest, CosXmlResult cosXmlResult) {
COSXMLUploadTask.COSXMLUploadTaskResult uploadResult =
(COSXMLUploadTask.COSXMLUploadTaskResult) cosXmlResult;
LogUtils.e("@@@1", "上传成功", "描述:", "文件ID" + uploadResult);
// 如果有回调,则调用成功回调
if (callback != null) {
// 构造文件访问URL
String url =uploadResult.accessUrl;
mainHandler.post(() -> callback.onSuccess(url));
}
}
@Override
public void onFail(CosXmlRequest cosXmlRequest, @Nullable @org.jetbrains.annotations.Nullable CosXmlClientException e, @Nullable @org.jetbrains.annotations.Nullable CosXmlServiceException e1) {
// 切换到主线程执行回调
mainHandler.post(() -> {
if (e != null) {
LogUtils.e("CosUpload", "上传失败", e);
if (callback != null) {
callback.onFailure(e);
}
} else {
LogUtils.e("CosUpload", "上传失败", e1);
if (callback != null) {
callback.onFailure(e1);
}
}
});
}
});
}
// 上传回调接口
public interface UploadCallback {
void onSuccess(String url); // 上传成功返回访问URL
void onFailure(Exception e); // 上传失败
}
}

View File

@@ -0,0 +1,216 @@
package com.xscm.moduleutil.utils.cos;
import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.util.Log;
import androidx.annotation.RequiresApi;
/**
* com.xscm.moduleutil.utils.cos
* qx
* 2025/10/23
*/
public class FilePathHelpe {
public static String getPathFromUri(Context context, Uri uri) {
String scheme = uri.getScheme();
Log.d("TAG", scheme);
if (scheme.equalsIgnoreCase("content")) {
return getPathFromMediaUri(context, uri);
} else if (scheme.equalsIgnoreCase("file")){
return getPathFromFileUri(context, uri);
}
return "";
}
private static String getPathFromMediaUri(Context context, Uri uri) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return getKitKatPathFromMediaUri(context, uri);
} else {
return getImagePathFromMediaUri(context, uri, null);
}
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private static String getKitKatPathFromMediaUri(Context context, Uri uri) {
String imagePath = "";
if (DocumentsContract.isDocumentUri(context, uri)) {
String docId = DocumentsContract.getDocumentId(uri);
if ("com.android.providers.media.documents".equals(uri.getAuthority())) {
//Log.d(TAG, uri.toString());
String id = docId.split(":")[1];
String selection = MediaStore.Images.Media._ID + "=" + id;
imagePath = getImagePathFromMediaUri(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
} else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {
//Log.d(TAG, uri.toString());
Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"),
Long.valueOf(docId));
imagePath = getImagePathFromMediaUri(context, contentUri, null);
}
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
//Log.d(TAG, "content: " + uri.toString());
imagePath = getImagePathFromMediaUri(context, uri, null);
}
return imagePath;
}
@SuppressLint("Range")
private static String getImagePathFromMediaUri(Context context, Uri uri, String selection) {
String path = null;
Cursor cursor = context.getContentResolver().query(uri, null, selection, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
//path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
path = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DATA));
}
cursor.close();
}
return path;
}
private static String getPathFromFileUri(Context context, Uri uri) {
return uri.getPath();
}
public static String getPathBeforeKitKat(Context context, Uri uri) {
if ("content".equalsIgnoreCase(uri.getScheme())) {
String[] projection = { MediaStore.MediaColumns.DATA };
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, projection,null, null, null);
int column_index = cursor.getColumnIndexOrThrow("_data");
if (cursor.moveToFirst()) {
return cursor.getString(column_index);
}
} catch (Exception e) {
} finally {
if(cursor != null)
cursor.close();
}
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
@SuppressLint("NewApi")
public static String getPathAfterKitKat(Context context, Uri uri) {
if (DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
// TODO handle non-primary volumes
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[] { split[1] };
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
public static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = { column };
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
final int column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
public static String getPath(Context context, Uri uri) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return getPathAfterKitKat(context, uri);
}
return getPathBeforeKitKat(context, uri);
}
}

View File

@@ -0,0 +1,158 @@
package com.xscm.moduleutil.utils.cos;
import android.text.TextUtils;
import com.tencent.qcloud.core.auth.QCloudCredentials;
import com.tencent.qcloud.core.auth.QCloudSigner;
import com.tencent.qcloud.core.common.QCloudClientException;
import com.tencent.qcloud.core.common.QCloudServiceException;
import com.tencent.qcloud.core.http.QCloudHttpClient;
import com.tencent.qcloud.core.http.QCloudHttpRequest;
import com.tencent.qcloud.core.http.RequestBodySerializer;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* com.xscm.moduleutil.utils.cos
* qx
* 2025/10/23
*/
public class RemoteCOSSigner implements QCloudSigner {
private URL requestSignUrl;
public RemoteCOSSigner(URL url) {
requestSignUrl = url;
}
/**
* @param request 即为发送到 CSP 服务端的请求,您需要根据这个 HTTP 请求的参数来计算签名,并给其添加 Authorization header
* @param credentials 空字段,请不要使用
* @throws QCloudClientException 您可以在处理过程中抛出异常
*/
@Override
public void sign(QCloudHttpRequest request, QCloudCredentials credentials) throws QCloudClientException {
/**
* 获取计算签名所需字段
*/
URL url = request.url();
String method = request.method();
String host = url.getHost();
String schema = url.getProtocol();
String path = url.getPath();
Map<String, String> headers = getHeaderMap(request.headers());
Map<String, String> params = getQueryMap(url.getQuery());
String signFieldJson = null;
try {
signFieldJson = signField2Json(method, schema, host, path, headers, params);
} catch (JSONException e) {
e.printStackTrace();
throw new QCloudClientException("sign field transfer to json failed");
}
/**
* 向您自己的服务端请求签名
*/
QCloudHttpRequest<String> httpRequest = new QCloudHttpRequest.Builder<String>()
.method("PUT")
.url(requestSignUrl)
.body(RequestBodySerializer.string(null, signFieldJson))
.build();
String response = null;
try {
response = QCloudHttpClient.getDefault().resolveRequest(httpRequest).executeNow().content();
} catch (QCloudServiceException e) {
e.printStackTrace();
throw new QCloudClientException(e);
}
String sign = null;
try {
sign = getSignFromResponse(response);
} catch (JSONException e) {
e.printStackTrace();
throw new QCloudClientException("parse response failed");
}
/**
* 给请求设置 Authorization Header
*/
if (TextUtils.isEmpty(sign)) {
throw new QCloudClientException("get sign from server failed!!!");
}
request.addHeader("Authorization", sign);
}
private Map<String, String> getHeaderMap(Map<String, List<String>> multiValuesHeaders) {
Map<String, String> header = new HashMap<>();
for (Map.Entry<String, List<String>> entry : multiValuesHeaders.entrySet()) {
if (entry.getValue().size() > 0) {
header.put(entry.getKey(), entry.getValue().get(0));
}
}
return header;
}
private Map<String, String> getQueryMap(String query)
{
Map<String, String> map = new HashMap<>();
if (TextUtils.isEmpty(query)) {
return map;
}
String[] params = query.split("&");
for (String param : params)
{
String[] paramKeyValue = param.split("=");
if (paramKeyValue.length >= 2) {
String name = paramKeyValue[0];
String value = paramKeyValue[1];
map.put(name, value);
}
}
return map;
}
/**
* 将签名需要的字段转化为 json 字符串
*
* @return
*/
private String signField2Json(String method, String schema, String host, String path,
Map<String, String> headers, Map<String, String> params) throws JSONException {
JSONObject signJson = new JSONObject();
signJson.put("method", method);
signJson.put("schema", schema);
signJson.put("host", host);
signJson.put("path", path);
JSONObject headersJSON = new JSONObject(headers);
signJson.put("headers", headersJSON);
JSONObject paramsJSON = new JSONObject(params);
signJson.put("params", paramsJSON);
return signJson.toString();
}
private String getSignFromResponse(String response) throws JSONException {
JSONObject jsonObject = new JSONObject(response);
return jsonObject.optString("sign");
}
}

View File

@@ -0,0 +1,132 @@
package com.xscm.moduleutil.utils.cos;
import android.content.Context;
import com.tencent.cos.xml.CosXmlService;
import com.tencent.cos.xml.CosXmlServiceConfig;
import com.tencent.cos.xml.exception.CosXmlClientException;
import com.tencent.cos.xml.exception.CosXmlServiceException;
import com.tencent.cos.xml.listener.CosXmlProgressListener;
import com.tencent.cos.xml.model.bucket.PutBucketRequest;
import com.tencent.cos.xml.model.bucket.PutBucketResult;
import com.tencent.cos.xml.model.object.PutObjectRequest;
import com.tencent.cos.xml.model.object.PutObjectResult;
import com.tencent.cos.xml.model.service.GetServiceRequest;
import com.tencent.cos.xml.model.service.GetServiceResult;
import com.tencent.cos.xml.transfer.UploadService;
import com.tencent.qcloud.core.auth.QCloudSigner;
import com.xscm.moduleutil.base.CommonAppContext;
import com.xscm.moduleutil.utils.SpUtil;
import com.xscm.moduleutil.widget.Constants;
import java.net.MalformedURLException;
import java.net.URL;
/**
* com.xscm.moduleutil.utils.cos
* qx
* 2025/10/23
*/
public class RemoteStorage {
private int MULTIPART_UPLOAD_SIZE = 1024 * 2;
private CosXmlService cosXmlService;
private boolean isHttps;
private String appid;
private String region;
public RemoteStorage(Context context, String appid, String region, String hostFormat) {
isHttps = false;
this.appid = appid;
this.region = region;
/**
* 初始化配置
*/
CosXmlServiceConfig cosXmlServiceConfig = new CosXmlServiceConfig.Builder()
.isHttps(isHttps)
.setAppidAndRegion(appid, region) // appid 和 region 均可以为空
.setDebuggable(true)
.setBucketInPath(false) // 将 Bucket 放在 URL 的 Path 中
.setHostFormat(hostFormat) // 私有云需要设置主域名
.builder();
/**
* 私有云暂时不支持临时密钥进行签名,如果直接在客户端直接使用永久密钥会有安全性问题,因此这里采用
* 服务端直接下发签名的方式来进行鉴权。
*/
URL url = null; // 您的服务端签名的 URL 地址
try {
url = new URL(CommonAppContext.getInstance().getCurrentEnvironment().getServerUrl()+ Constants.GET_TEMP_KEY+"?"+ SpUtil.getToken());
} catch (MalformedURLException e) {
e.printStackTrace();
}
QCloudSigner cosSigner = new RemoteCOSSigner(url);
cosXmlService = new CosXmlService(context, cosXmlServiceConfig, cosSigner);
}
/**
* 上传文件
*
* @param bucketName bucket 名称
* @param cosPath 上传到 COS 的路径
* @param localPath 需要上传文件的本地路径
* @param progressListener 进度监听器
*
* @return 本次上传的 id可以通过这个 id 来取消上传
*/
public UploadService.UploadServiceResult uploadFile(String bucketName, String cosPath, String localPath, CosXmlProgressListener progressListener)
throws CosXmlServiceException, CosXmlClientException {
UploadService.ResumeData resumeData = new UploadService.ResumeData();
resumeData.sliceSize = MULTIPART_UPLOAD_SIZE; // 分片上传的大小
resumeData.cosPath = cosPath;
resumeData.bucket = bucketName;
resumeData.srcPath = localPath;
/**
* 上传服务类,这个类封装了 {@link CosXmlService} 几个上传相关的接口,通过使用该接口,您可以更加方便的上传文件。
* 注意,每次上传都要初始化一个新的 {@link CosXmlService} 对象。
*/
final UploadService uploadService = new UploadService(cosXmlService, resumeData);
uploadService.setProgressListener(progressListener);
return uploadService.upload();
}
public PutObjectResult simpleUploadFile(String bucketName, String cosPath, String localPath, CosXmlProgressListener progressListener)
throws CosXmlServiceException, CosXmlClientException {
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, cosPath, localPath);
putObjectRequest.setProgressListener(progressListener);
return cosXmlService.putObject(putObjectRequest);
}
/**
* 列出所有的 bucket
*/
public GetServiceResult getService() throws CosXmlServiceException, CosXmlClientException {
GetServiceRequest getServiceRequest = new GetServiceRequest();
getServiceRequest.setRequestHeaders("x-cos-meta-bucket", "BucketName", false);
return cosXmlService.getService(getServiceRequest);
}
/**
* 创建 bucket
*
* @param bucketName bucket 名称
*/
public PutBucketResult putBucket(String bucketName) throws CosXmlServiceException, CosXmlClientException {
PutBucketRequest putBucketRequest = new PutBucketRequest(bucketName);
return cosXmlService.putBucket(putBucketRequest);
}
}

View File

@@ -0,0 +1,239 @@
package com.xscm.moduleutil.utils.cos;
import android.content.Context;
import android.os.AsyncTask;
import android.widget.Toast;
import com.tencent.cos.xml.exception.CosXmlClientException;
import com.tencent.cos.xml.exception.CosXmlServiceException;
import com.tencent.cos.xml.listener.CosXmlProgressListener;
import com.tencent.cos.xml.model.bucket.PutBucketResult;
import com.tencent.cos.xml.model.object.PutObjectResult;
import com.tencent.cos.xml.model.service.GetServiceResult;
import com.tencent.cos.xml.model.tag.ListAllMyBuckets;
import com.tencent.cos.xml.transfer.UploadService;
import com.tencent.qcloud.core.logger.QCloudLogger;
import java.util.List;
/**
* com.xscm.moduleutil.utils.cos
* qx
* 2025/10/23
*/
public class TaskFactory {
private static TaskFactory instance;
private TaskFactory() {}
public static TaskFactory getInstance() {
if (instance == null) {
synchronized (TaskFactory.class) {
if (instance == null) {
instance = new TaskFactory();
}
}
}
return instance;
}
public GetServiceTask createGetServiceTask(Context context, RemoteStorage remoteStorage) {
return new GetServiceTask(context, remoteStorage);
}
public PutBucketTask createPutBucketTask(Context context, RemoteStorage remoteStorage, String bucketName) {
return new PutBucketTask(context, remoteStorage, bucketName);
}
public PutObjectTask createPutObjectTask(Context context, RemoteStorage remoteStorage, String bucket,
String srcPath, String dstPath) {
return new PutObjectTask(context, remoteStorage, bucket, srcPath, dstPath);
}
public SimplePutObjectTask createSimplePutObjectTask(Context context, RemoteStorage remoteStorage, String bucket,
String srcPath, String dstPath) {
return new SimplePutObjectTask(context, remoteStorage, bucket, srcPath, dstPath);
}
public class GetServiceTask extends AsyncTask<Void, Void, GetServiceResult> {
Context context;
RemoteStorage remoteStorage ;
public GetServiceTask(Context context, RemoteStorage remoteStorage) {
this.remoteStorage = remoteStorage;
this.context = context;
}
@Override
protected GetServiceResult doInBackground(Void ... voids) {
try {
return remoteStorage.getService();
} catch (CosXmlServiceException e) {
e.printStackTrace();
} catch (CosXmlClientException e) {
e.printStackTrace();
}
return null;
}
protected void onPostExecute(GetServiceResult getServiceResult) {
if (getServiceResult != null && getServiceResult.listAllMyBuckets != null) {
List<ListAllMyBuckets.Bucket> buckets = getServiceResult.listAllMyBuckets.buckets;
Toast.makeText(context, buckets.toString(), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "GetService failed", Toast.LENGTH_SHORT).show();
}
}
}
public class PutBucketTask extends AsyncTask<Void, Void, PutBucketResult> {
RemoteStorage remoteStorage ;
String bucketName;
Context context;
public PutBucketTask(Context context, RemoteStorage remoteStorage, String bucketName) {
this.context = context;
this.remoteStorage = remoteStorage;
this.bucketName = bucketName;
}
@Override
protected PutBucketResult doInBackground(Void ... voids) {
try {
return remoteStorage.putBucket(bucketName);
} catch (CosXmlServiceException e) {
e.printStackTrace();
} catch (CosXmlClientException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(PutBucketResult putBucketResult) {
if (putBucketResult != null) {
Toast.makeText(context, putBucketResult.printResult(), Toast.LENGTH_SHORT).show();
}
}
}
static class PutObjectTask extends AsyncTask<Void, Integer, UploadService.UploadServiceResult> {
Context context;
RemoteStorage remoteStorage;
String bucket;
String srcPath;
String dstPath;
public PutObjectTask(Context context, RemoteStorage remoteStorage, String bucket, String srcPath, String dstPath) {
this.context = context;
this.remoteStorage = remoteStorage;
this.bucket = bucket;
this.srcPath = srcPath;
this.dstPath = dstPath;
}
@Override
protected UploadService.UploadServiceResult doInBackground(Void... voids) {
try {
return remoteStorage.uploadFile(bucket, dstPath, srcPath, new CosXmlProgressListener() {
@Override
public void onProgress(long progress, long total) {
publishProgress((int) ((progress/ (float) total) * 100));
}
});
} catch (CosXmlServiceException e) {
e.printStackTrace();
} catch (CosXmlClientException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
QCloudLogger.i("upload", "progress " + values[0]);
}
@Override
protected void onPostExecute(UploadService.UploadServiceResult uploadServiceResult) {
if (uploadServiceResult != null) {
Toast.makeText(context, uploadServiceResult.printResult(), Toast.LENGTH_SHORT).show();
}
}
}
static class SimplePutObjectTask extends AsyncTask<Void, Integer, PutObjectResult> {
Context context;
RemoteStorage remoteStorage;
String bucket;
String srcPath;
String dstPath;
public SimplePutObjectTask(Context context, RemoteStorage remoteStorage, String bucket, String srcPath, String dstPath) {
this.context = context;
this.remoteStorage = remoteStorage;
this.bucket = bucket;
this.srcPath = srcPath;
this.dstPath = dstPath;
}
@Override
protected PutObjectResult doInBackground(Void... voids) {
try {
return remoteStorage.simpleUploadFile(bucket, dstPath, srcPath, new CosXmlProgressListener() {
@Override
public void onProgress(long progress, long total) {
publishProgress((int) ((progress/ (float) total) * 100));
}
});
} catch (CosXmlServiceException e) {
e.printStackTrace();
} catch (CosXmlClientException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
QCloudLogger.i("upload", "progress " + values[0]);
}
@Override
protected void onPostExecute(PutObjectResult putObjectResult) {
if (putObjectResult != null) {
Toast.makeText(context, putObjectResult.printResult(), Toast.LENGTH_SHORT).show();
}
}
}
}

View File

@@ -0,0 +1,21 @@
package com.xscm.moduleutil.utils.cos
/**
*com.xscm.moduleutil.utils.cos
*qx
*2025/10/23
*
*/
data class TempKeyBean (
var startTime:Long = 0,
var expiredTime:Long = 0,
var region:String = "",
var bucket:String = "",
var credentials : Credentials = Credentials(),
)
data class Credentials(
var sessionToken : String="",
var tmpSecretId : String="",
var tmpSecretKey : String=""
)

View File

@@ -0,0 +1,276 @@
package com.xscm.moduleutil.utils.logger;
import android.util.Log;
import androidx.annotation.NonNull;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.internal.http.HttpHeaders;
import okio.Buffer;
import okio.BufferedSource;
/**
* OkHttp 日志拦截器,用于打印请求和响应详情
*/
public class LogInterceptor implements Interceptor {
private static final String TAG = "NetworkLog";
private static final Charset UTF8 = StandardCharsets.UTF_8;
// 日志开关可根据debug/release环境动态设置
private boolean isLogEnabled = true;
// 是否打印请求体
private boolean logRequestBody = true;
// 是否打印响应体
private boolean logResponseBody = true;
// 最大日志长度(避免过大的响应体导致日志刷屏)
private int maxLogLength = 2048;
public LogInterceptor() {
}
// 配置方法
public LogInterceptor setLogEnabled(boolean enabled) {
isLogEnabled = enabled;
return this;
}
public LogInterceptor setLogRequestBody(boolean logRequestBody) {
this.logRequestBody = logRequestBody;
return this;
}
public LogInterceptor setLogResponseBody(boolean logResponseBody) {
this.logResponseBody = logResponseBody;
return this;
}
public LogInterceptor setMaxLogLength(int maxLogLength) {
this.maxLogLength = maxLogLength;
return this;
}
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
if (!isLogEnabled) {
return chain.proceed(chain.request());
}
Request request = chain.request();
// 打印请求日志
logRequest(request);
// 记录请求开始时间,用于计算耗时
long startNs = System.nanoTime();
Response response;
try {
response = chain.proceed(request);
} catch (Exception e) {
// 打印请求异常
Log.e(TAG, "请求失败: " + e.getMessage());
throw e;
}
// 计算请求耗时
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
// 打印响应日志
logResponse(response, tookMs);
return response;
}
/**
* 打印请求日志
*/
private void logRequest(Request request) {
try {
StringBuilder log = new StringBuilder();
log.append("\n==================== 请求开始 ====================\n");
// 请求行: 方法 + URL
log.append(String.format("方法: %s URL: %s\n", request.method(), request.url()));
// 请求头
log.append("请求头:\n");
for (String name : request.headers().names()) {
// 脱敏敏感头信息如Authorization、Cookie等
String value = isSensitiveHeader(name) ? "***" : request.headers().get(name);
log.append(String.format(" %s: %s\n", name, value));
}
// 请求体
if (logRequestBody && request.body() != null) {
RequestBody requestBody = request.body();
if (requestBody.contentLength() > 0) {
log.append("请求体:\n");
// 复制请求体(避免原请求体被消耗)
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
Charset charset = UTF8;
MediaType contentType = requestBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
// 读取请求体内容
String body = buffer.readString(charset);
// 格式化JSON如果是JSON类型
if (isJson(contentType)) {
body = formatJson(body);
}
// 截断过长的日志
log.append(truncateLog(body)).append("\n");
}
}
log.append("==================== 请求结束 ====================\n");
Log.d(TAG, log.toString());
} catch (Exception e) {
Log.e(TAG, "打印请求日志失败: " + e.getMessage());
}
}
/**
* 打印响应日志
*/
private void logResponse(Response response, long tookMs) {
try {
StringBuilder log = new StringBuilder();
log.append("\n==================== 响应开始 ====================\n");
// 响应行: 状态码 + 消息 + 耗时
log.append(String.format("状态码: %d 消息: %s 耗时: %dms\n",
response.code(), response.message(), tookMs));
// 响应头
log.append("响应头:\n");
for (String name : response.headers().names()) {
log.append(String.format(" %s: %s\n", name, response.headers().get(name)));
}
// 响应体
if (logResponseBody && HttpHeaders.hasBody(response)) {
ResponseBody responseBody = response.body();
if (responseBody != null) {
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // 读取整个响应体
Buffer buffer = source.buffer();
Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
// 读取响应体内容
String body = buffer.clone().readString(charset);
// 格式化JSON
if (isJson(contentType)) {
body = formatJson(body);
}
// 截断过长的日志
log.append("响应体:\n").append(truncateLog(body)).append("\n");
}
}
log.append("==================== 响应结束 ====================\n");
Log.d(TAG, log.toString());
} catch (Exception e) {
Log.e(TAG, "打印响应日志失败: " + e.getMessage());
}
}
/**
* 判断是否为JSON类型
*/
private boolean isJson(MediaType mediaType) {
if (mediaType == null) return false;
return mediaType.type().equals("application") &&
mediaType.subtype().equals("json");
}
/**
* 格式化JSON字符串增强可读性
*/
private String formatJson(String json) {
try {
// 简单格式化可根据需要使用更复杂的JSON格式化库
StringBuilder formatted = new StringBuilder();
int indent = 0;
boolean inQuotes = false;
char lastChar = ' ';
for (char c : json.toCharArray()) {
if (c == '"' && lastChar != '\\') {
inQuotes = !inQuotes;
}
if (!inQuotes) {
switch (c) {
case '{':
case '[':
formatted.append(c).append("\n");
indent += 4;
formatted.append(" ".repeat(indent));
break;
case '}':
case ']':
formatted.append("\n");
indent -= 4;
formatted.append(" ".repeat(indent)).append(c);
break;
case ',':
formatted.append(c).append("\n").append(" ".repeat(indent));
break;
case ':':
formatted.append(" : ");
break;
default:
formatted.append(c);
break;
}
} else {
formatted.append(c);
}
lastChar = c;
}
return formatted.toString();
} catch (Exception e) {
// 格式化失败时返回原始字符串
return json;
}
}
/**
* 截断过长的日志
*/
private String truncateLog(String log) {
if (log.length() <= maxLogLength) {
return log;
}
return log.substring(0, maxLogLength) + "\n...[日志过长,已截断]...";
}
/**
* 判断是否为敏感头信息(需要脱敏)
*/
private boolean isSensitiveHeader(String headerName) {
String lowerHeader = headerName.toLowerCase();
return lowerHeader.contains("authorization") ||
lowerHeader.contains("cookie") ||
lowerHeader.contains("token") ||
lowerHeader.contains("secret");
}
}

View File

@@ -0,0 +1,233 @@
package com.xscm.moduleutil.utils.roomview;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.xscm.moduleutil.bean.GiftBean;
import com.xscm.moduleutil.bean.RoonGiftModel;
import java.lang.ref.WeakReference;
import java.util.*;
public class GiftDisplayManager {
private static GiftDisplayManager instance;
private WeakReference<ViewGroup> containerRef;
private List<GiftDisplayView> displayViews;
private Queue<GiftBean> giftQueue;
private Map<String, GiftBean> accumulatedGifts;
private boolean isProcessingQueue = false;
private Handler mainHandler = new Handler(Looper.getMainLooper());
public static GiftDisplayManager getInstance() {
if (instance == null) {
synchronized (GiftDisplayManager.class) {
if (instance == null) {
instance = new GiftDisplayManager();
}
}
}
return instance;
}
private GiftDisplayManager() {
displayViews = new ArrayList<>();
giftQueue = new LinkedList<>();
accumulatedGifts = new HashMap<>();
}
public void setupDisplayView(ViewGroup container) {
this.containerRef = new WeakReference<>(container);
createDisplayViews();
}
private void createDisplayViews() {
if (displayViews.size() > 0 || containerRef == null || containerRef.get() == null) {
return;
}
ViewGroup container = containerRef.get();
int viewHeight = dpToPx(40);
int spacing = dpToPx(10);
int topMargin = dpToPx(100);
int width = dpToPx(270);
for (int i = 0; i < 3; i++) {
int y = topMargin + (viewHeight + spacing) * i;
GiftDisplayView displayView = new GiftDisplayView(container.getContext());
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(width, viewHeight);
params.setMargins(0, y, 0, 0);
displayView.setLayoutParams(params);
displayView.setTag(1000 + i);
final int finalI = i;
displayView.setGiftAnimationListener(view -> {
Log.d("GiftDisplayManager", "Gift animation ended on view: " + finalI);
onGiftAnimationEnd(view);
});
container.addView(displayView);
displayViews.add(displayView);
Log.d("GiftDisplayManager", "Created display view " + i);
}
}
public void receiveGift(GiftBean gift) {
if (gift == null) return;
Log.d("GiftDisplayManager", "Received gift: " + gift.getSenderName() +
" - " + gift.getGift_name() + " x" + gift.getNumber());
mainHandler.post(() -> internalReceiveGift(gift));
}
private void internalReceiveGift(GiftBean gift) {
// 查找正在显示的同类型礼物
GiftDisplayView displayingView = findDisplayingViewForGift(gift);
if (displayingView != null) {
// 找到正在显示的视图,直接累加
String key = gift.getGiftKey();
GiftBean accumulatedGift = accumulatedGifts.get(key);
if (accumulatedGift != null) {
accumulatedGift.setNumber(accumulatedGift.getNumber() + gift.getNumber());
displayingView.updateGiftCount(accumulatedGift.getNumber());
Log.d("GiftDisplayManager", "Gift accumulated: " + gift.getGift_name() +
" x" + accumulatedGift.getNumber());
}
} else {
// 新礼物,检查是否可以立即显示
GiftDisplayView availableView = findAvailableDisplayView();
if (availableView != null) {
// 有可用视图,立即显示
String key = gift.getGiftKey();
accumulatedGifts.put(key, gift.clone());
availableView.showGift(gift);
Log.d("GiftDisplayManager", "Immediately display gift on view: " + availableView.getTag());
} else {
// 没有可用视图,加入队列
giftQueue.offer(gift);
Log.d("GiftDisplayManager", "Added to queue, current queue size: " + giftQueue.size());
}
}
// 处理队列
processGiftQueue();
}
private GiftDisplayView findDisplayingViewForGift(GiftBean gift) {
for (GiftDisplayView view : displayViews) {
if (view.isAnimating() && view.getCurrentGift() != null &&
view.getCurrentGift().isSameGiftFromSameSender(gift)) {
return view;
}
}
return null;
}
private GiftDisplayView findAvailableDisplayView() {
for (GiftDisplayView view : displayViews) {
if (!view.isAnimating()) {
return view;
}
}
return null;
}
private void processGiftQueue() {
if (isProcessingQueue) {
return;
}
isProcessingQueue = true;
// 循环处理队列直到队列为空或没有可用视图
while (!giftQueue.isEmpty()) {
GiftDisplayView availableView = findAvailableDisplayView();
if (availableView == null) {
break;
}
GiftBean gift = giftQueue.poll();
if (gift == null) continue;
// 检查是否已经有同类型礼物在显示
GiftDisplayView displayingView = findDisplayingViewForGift(gift);
if (displayingView == null) {
String key = gift.getGiftKey();
accumulatedGifts.put(key, gift.clone());
availableView.showGift(gift);
Log.d("GiftDisplayManager", "Display gift from queue: " + gift.getGift_name());
} else {
// 如果已经在显示,累加到现有视图
String key = gift.getGiftKey();
GiftBean accumulatedGift = accumulatedGifts.get(key);
if (accumulatedGift != null) {
accumulatedGift.setNumber(accumulatedGift.getNumber() + gift.getNumber());
displayingView.updateGiftCount(accumulatedGift.getNumber());
Log.d("GiftDisplayManager", "Queue gift accumulated to existing: " +
gift.getNickname() + " x" + accumulatedGift.getNumber());
}
}
}
isProcessingQueue = false;
// 打印队列状态
if (!giftQueue.isEmpty()) {
Log.d("GiftDisplayManager", "Still " + giftQueue.size() + " gifts waiting in queue");
}
}
private void onGiftAnimationEnd(GiftDisplayView view) {
Log.d("GiftDisplayManager", "Gift animation end on view: " + view.getTag());
// 从累加记录中移除
if (view.getCurrentGift() != null) {
String key = view.getCurrentGift().getGiftKey();
accumulatedGifts.remove(key);
Log.d("GiftDisplayManager", "Removed accumulated record: " + key);
}
// 延迟一下再处理队列,确保视图状态完全重置
mainHandler.postDelayed(this::processGiftQueue, 100);
}
public void clearAll() {
Log.d("GiftDisplayManager", "Clear all gifts and queue");
for (GiftDisplayView view : displayViews) {
view.finishAnimationImmediately();
}
containerRef.clear();
displayViews.clear();
giftQueue.clear();
accumulatedGifts.clear();
isProcessingQueue = false;
}
// 调试方法
public void printDebugInfo() {
Log.d("GiftDisplayManager", "=== Gift Display Manager Status ===");
Log.d("GiftDisplayManager", "Queue size: " + giftQueue.size());
Log.d("GiftDisplayManager", "Accumulated records: " + accumulatedGifts.size());
for (int i = 0; i < displayViews.size(); i++) {
GiftDisplayView view = displayViews.get(i);
Log.d("GiftDisplayManager", "View " + i + ": Animating=" + view.isAnimating() +
", Gift=" + (view.getCurrentGift() != null ? view.getCurrentGift().getGift_name() : "None"));
}
Log.d("GiftDisplayManager", "===================================");
}
private int dpToPx(int dp) {
if (containerRef == null || containerRef.get() == null) return dp;
float density = containerRef.get().getResources().getDisplayMetrics().density;
return Math.round(dp * density);
}
}

View File

@@ -0,0 +1,263 @@
package com.xscm.moduleutil.utils.roomview;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.graphics.Color;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.GiftBean;
import com.xscm.moduleutil.utils.ImageUtils;
import java.util.Random;
public class GiftDisplayView extends FrameLayout {
private ImageView avatarImageView;
private TextView senderTextView;
private TextView giftTextView;
private TextView countTextView;
private ImageView giftImageView;
private LinearLayout ll;
private GiftBean currentGift;
private boolean isAnimating = false;
private GiftAnimationListener listener;
private Handler handler = new Handler();
private Runnable hideRunnable;
public interface GiftAnimationListener {
void onGiftAnimationEnd(GiftDisplayView view);
}
public GiftDisplayView(Context context) {
super(context);
initView();
}
public GiftDisplayView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
private void initView() {
LayoutInflater.from(getContext()).inflate(R.layout.gift_display_layout, this, true);
avatarImageView = findViewById(R.id.iv_avatar);
senderTextView = findViewById(R.id.tv_sender);
giftTextView = findViewById(R.id.tv_gift);
countTextView = findViewById(R.id.tv_count);
giftImageView = findViewById(R.id.iv_gift);
// ll = findViewById(R.id.ll);
// setBackgroundResource(R.drawable.gift_background);
setAlpha(0f);
}
public void showGift(GiftBean gift) {
if (isAnimating) {
Log.w("GiftDisplayView", "View is animating, cannot show new gift");
return;
}
this.currentGift = gift;
this.isAnimating = true;
Log.d("GiftDisplayView", "Start showing gift: " + gift.getGift_name());
// 更新UI
updateUIWithGift(gift);
// 重置位置
setTranslationX(-getWidth());
setAlpha(1f);
// 从左往右进入动画
animate()
.translationX(0)
.setDuration(500)
.setInterpolator(new AccelerateDecelerateInterpolator())
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
Log.d("GiftDisplayView", "Enter animation completed: " + gift.getGift_name());
startHideTimer();
}
})
.start();
}
private void updateUIWithGift(GiftBean gift) {
if (gift == null) return;
senderTextView.setText(gift.getNickname()!=null ? gift.getNickname() : "未知用户");
// 更新发送者名称
// senderTextView.setText(gift.getSenderName() != null ? gift.getSenderName() : "未知用户");
// 更新礼物信息
giftTextView.setText("送给 "+(gift.getSenderName() != null ? gift.getSenderName() : "未知用户") + (gift.getGift_name() != null ? gift.getGift_name() : "礼物"));
// 更新礼物数量
countTextView.setText("x" + gift.getNumber());
// 加载头像图片这里可以使用Glide、Picasso等图片加载库
loadAvatarImage(gift.getUserAvatar());
// 加载礼物图片
loadGiftImage(gift.getBase_image());
Log.d("GiftDisplayView", "Update UI: " + gift.getSenderName() + " - " +
gift.getGift_name() + " x" + gift.getNumber());
}
private void loadAvatarImage(String avatarUrl) {
if (avatarUrl != null && !avatarUrl.isEmpty()) {
// 使用图片加载库,例如:
// Glide.with(getContext()).load(avatarUrl).into(avatarImageView);
// 临时用颜色代替
// avatarImageView.setBackgroundColor(getRandomColor());
ImageUtils.loadHeadCC(avatarUrl, avatarImageView);
} else {
avatarImageView.setBackgroundColor(Color.LTGRAY);
}
}
private void loadGiftImage(String giftImageUrl) {
if (giftImageUrl != null && !giftImageUrl.isEmpty()) {
// 使用图片加载库
// Glide.with(getContext()).load(giftImageUrl).into(giftImageView);
// 临时用颜色代替
// giftImageView.setBackgroundColor(getRandomColor());
ImageUtils.loadHeadCC(giftImageUrl, giftImageView);
} else {
giftImageView.setBackgroundColor(Color.parseColor("#FFA500"));
}
}
private int getRandomColor() {
Random random = new Random();
return Color.argb(255, random.nextInt(256), random.nextInt(256), random.nextInt(256));
}
public void updateGiftCount(int count) {
if (!isAnimating) {
Log.w("GiftDisplayView", "View is not animating, cannot update count");
return;
}
Log.d("GiftDisplayView", "Update gift count: " + count);
// 更新数量显示
countTextView.setText("x" + count);
// 数量更新动画
countTextView.animate()
.scaleX(1.5f)
.scaleY(1.5f)
.setDuration(200)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
countTextView.animate()
.scaleX(1f)
.scaleY(1f)
.setDuration(200)
.start();
}
})
.start();
// 重置计时器
resetHideTimer();
}
private void startHideTimer() {
// 移除之前的任务
if (hideRunnable != null) {
handler.removeCallbacks(hideRunnable);
}
hideRunnable = this::hideAnimation;
handler.postDelayed(hideRunnable, 3000);
}
private void resetHideTimer() {
startHideTimer();
}
private void hideAnimation() {
if (!isAnimating) {
return;
}
Log.d("GiftDisplayView", "Start hide animation: " + currentGift.getGift_name());
// 从右往左消失动画
animate()
.translationX(-getWidth())
.alpha(0f)
.setDuration(500)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
Log.d("GiftDisplayView", "Hide animation completed: " + currentGift.getGift_name());
isAnimating = false;
if (listener != null) {
listener.onGiftAnimationEnd(GiftDisplayView.this);
}
currentGift = null;
}
})
.start();
}
public void finishAnimationImmediately() {
Log.d("GiftDisplayView", "Finish animation immediately");
// 移除计时任务
if (hideRunnable != null) {
handler.removeCallbacks(hideRunnable);
hideRunnable = null;
}
// 清除动画
clearAnimation();
animate().cancel();
isAnimating = false;
currentGift = null;
setAlpha(0f);
setTranslationX(-getWidth());
}
public boolean isAnimating() {
return isAnimating;
}
public GiftBean getCurrentGift() {
return currentGift;
}
public void setGiftAnimationListener(GiftAnimationListener listener) {
this.listener = listener;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (hideRunnable != null) {
handler.removeCallbacks(hideRunnable);
}
}
}

View File

@@ -0,0 +1,44 @@
package com.xscm.moduleutil.view;
import android.content.Context;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat;
import com.xscm.moduleutil.R;
import org.jetbrains.annotations.NotNull;
/**
* 这是抢红包的自定义view
*/
public class CustomDialogView extends ConstraintLayout {
public CustomDialogView(@NonNull @NotNull Context context) {
super(context);
init();
}
public CustomDialogView(@NonNull @NotNull Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomDialogView(@NonNull @NotNull Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public CustomDialogView(@NonNull @NotNull Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
// 初始化视图
// 设置背景色
setBackground(ContextCompat.getDrawable(getContext(), R.drawable.bg_red_16_envel));
}
}

View File

@@ -0,0 +1,262 @@
package com.xscm.moduleutil.view;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.bumptech.glide.Glide;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.GiftBean;
import com.xscm.moduleutil.bean.blindboxwheel.BlindBoxBean;
public class QXMeetGiftView extends RelativeLayout {
private TextView giftNameLabel;
private ImageView giftPriceBgView;
private Button giftCoin;
private ImageView bgImageView;
private ImageView giftBgImageView;
private ImageView giftImageView;
private boolean isLockGift;
private Object model; // 这里用 Object 代替 QXDrawGiftModel
public QXMeetGiftView(Context context) {
super(context);
initSubviews(context);
}
public QXMeetGiftView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initSubviews(context);
}
public QXMeetGiftView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initSubviews(context);
}
private void initSubviews(Context context) {
// 创建背景图片视图
bgImageView = new ImageView(context);
bgImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
bgImageView.setImageResource(R.drawable.ac_left_gift_bg);
LayoutParams bgParams = new LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT
);
addView(bgImageView, bgParams);
// 创建礼物图片视图(圆形)
giftImageView = new ImageView(context);
giftImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
LayoutParams giftImageParams = new LayoutParams(
dpToPx(48), // 固定宽度
dpToPx(62) // 固定高度
);
giftImageParams.addRule(CENTER_IN_PARENT);
giftImageParams.topMargin = dpToPx(10);
addView(giftImageView, giftImageParams);
// 创建礼物背景光效视图
giftBgImageView = new ImageView(context);
giftBgImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
giftBgImageView.setImageResource(R.drawable.ac_lock_gift_light_bg);
giftBgImageView.setVisibility(View.GONE); // 初始隐藏
LayoutParams giftBgParams = new LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT
);
addView(giftBgImageView, giftBgParams);
// 创建价格背景视图
giftPriceBgView = new ImageView(context);
giftPriceBgView.setImageResource(R.drawable.ac_meet_gift_name_bg);
LayoutParams priceBgParams = new LayoutParams(
LayoutParams.MATCH_PARENT,
dpToPx(15)
);
priceBgParams.addRule(CENTER_HORIZONTAL);
priceBgParams.addRule(ALIGN_PARENT_BOTTOM);
priceBgParams.bottomMargin = dpToPx(10); // 在名称标签上方
addView(giftPriceBgView, priceBgParams);
// 创建金币按钮
giftCoin = new Button(context);
giftCoin.setTextColor(0xFFC7BF62); // 使用直接的颜色值
// 设置按钮图标
Drawable coinDrawable = getResources().getDrawable(R.mipmap.jinb);
coinDrawable.setBounds(0, 0, dpToPx(1), dpToPx(1));
giftCoin.setCompoundDrawables(coinDrawable, null, null, null);
giftCoin.setTextSize(10);
giftCoin.setBackgroundColor(0x00000000); // 透明背景
LayoutParams coinParams = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT
);
coinParams.addRule(CENTER_HORIZONTAL);
coinParams.addRule(ALIGN_PARENT_BOTTOM);
coinParams.bottomMargin = -dpToPx(2);
addView(giftCoin, coinParams);
// 创建礼物名称标签
giftNameLabel = new TextView(context);
giftNameLabel.setTextColor(0xFFFFFFFF);
giftNameLabel.setTextSize(12);
giftNameLabel.setGravity(android.view.Gravity.CENTER);
giftNameLabel.setSingleLine(true); // 设置为单行显示
giftNameLabel.setEllipsize(android.text.TextUtils.TruncateAt.END); // 超出部分用省略号表示
LayoutParams nameParams = new LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT
);
nameParams.addRule(CENTER_HORIZONTAL);
nameParams.addRule(ALIGN_PARENT_BOTTOM);
nameParams.bottomMargin = dpToPx(1);
addView(giftNameLabel, nameParams);
// 调整视图层级 - 确保正确的层级关系
// 按添加顺序已经确定层级,最晚添加的在最上层
}
public void setIsLockGift(boolean isLockGift) {
this.isLockGift = isLockGift;
// 设置背景图片
int bgResource = isLockGift ?
R.drawable.ac_lock_gift_bg : R.drawable.ac_left_gift_bg;
bgImageView.setImageResource(bgResource);
// 显示/隐藏光效背景
giftBgImageView.setVisibility(isLockGift ? View.VISIBLE : View.GONE);
if (isLockGift) {
// 重新设置礼物图片的约束
LayoutParams params = (LayoutParams) giftImageView.getLayoutParams();
params.width = dpToPx(36);
params.height = dpToPx(36);
params.addRule(CENTER_IN_PARENT);
params.setMargins(0, 0, 0, 0);
giftImageView.setLayoutParams(params);
} else {
// 恢复原始约束
LayoutParams params = (LayoutParams) giftImageView.getLayoutParams();
params.width = LayoutParams.MATCH_PARENT;
params.height = LayoutParams.WRAP_CONTENT;
params.addRule(CENTER_IN_PARENT);
params.setMargins(dpToPx(6), dpToPx(6), dpToPx(6), 0);
giftImageView.setLayoutParams(params);
}
}
public void setModel(BlindBoxBean.GiveGift model) {
this.model = model;
// 这里需要根据您的 QXDrawGiftModel 类来实现具体逻辑
if (model instanceof BlindBoxBean.GiveGift) {
BlindBoxBean.GiveGift giftModel = (BlindBoxBean.GiveGift) model;
// 使用图片加载库加载图片
Glide.with(getContext()).load(giftModel.getBase_image()).into(giftImageView);
giftNameLabel.setText(giftModel.getGift_name());
giftCoin.setText(giftModel.getGift_price());
}
}
public void startAnimation() {
// 礼物图片顺时针旋转动画
RotateAnimation rotateAnimation = new RotateAnimation(
0, 360, // 从 0 度旋转到 360 度
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f
);
rotateAnimation.setDuration(3000); // 3 秒
rotateAnimation.setRepeatCount(Animation.INFINITE);
rotateAnimation.setRepeatMode(Animation.RESTART);
rotateAnimation.setInterpolator(getContext(), android.R.anim.linear_interpolator);
giftImageView.startAnimation(rotateAnimation);
// 光效背景逆时针旋转动画
RotateAnimation rotateAnimation1 = new RotateAnimation(
0, -360, // 从 0 度旋转到 -360 度
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f
);
rotateAnimation1.setDuration(3000); // 3 秒
rotateAnimation1.setRepeatCount(Animation.INFINITE);
rotateAnimation1.setRepeatMode(Animation.RESTART);
rotateAnimation1.setInterpolator(getContext(), android.R.anim.linear_interpolator);
giftBgImageView.startAnimation(rotateAnimation1);
}
public void resetAnimation() {
giftImageView.clearAnimation();
giftBgImageView.clearAnimation();
}
public void stopAnimation() {
giftImageView.clearAnimation();
giftBgImageView.clearAnimation();
}
// 辅助方法dp 转 px
private int dpToPx(int dp) {
float density = getResources().getDisplayMetrics().density;
return Math.round(dp * density);
}
// 缩放宽度方法(对应 ScaleWidth
private int scaleWidth(int value) {
// 这里需要根据您的缩放逻辑实现
// 通常可以根据屏幕密度进行缩放
float scale = getResources().getDisplayMetrics().density;
return (int) (value * scale);
}
// Getter 方法
public TextView getGiftNameLabel() {
return giftNameLabel;
}
public ImageView getGiftPriceBgView() {
return giftPriceBgView;
}
public Button getGiftCoin() {
return giftCoin;
}
public ImageView getBgImageView() {
return bgImageView;
}
public ImageView getGiftBgImageView() {
return giftBgImageView;
}
public ImageView getGiftImageView() {
return giftImageView;
}
public boolean isLockGift() {
return isLockGift;
}
public Object getModel() {
return model;
}
}

View File

@@ -0,0 +1,332 @@
package com.xscm.moduleutil.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.GradientDrawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.blindboxwheel.BlindBoxBean;
import com.xscm.moduleutil.utils.ImageUtils;
import com.xscm.moduleutil.widget.GifAvatarOvalView;
public class QXMeetUserView extends RelativeLayout {
private static final String TAG = "QXMeetUserView";
private GifAvatarOvalView headerImageView; // 头像
private ImageView dressImageView; // 装饰图
private TextView tagLabel; // 标签
private TextView nameLabel; // 名称
// 布局属性变量
private int avatarSize; // 头像大小
private int dressSize; // 装饰图大小
private float nameTextSize; // 名字字体大小
private int nameTextColor; // 名字颜色
private float tagTextSize; // 标签字体大小
private int tagTextColor; // 标签颜色
private int tagMargin; // 标签与头像的水平间距
// 布局常量dp
private static final int DEFAULT_AVATAR_SIZE = 60; // 默认头像大小
private static final int DEFAULT_DRESS_SIZE = 70; // 默认装饰图大小
private static final float DEFAULT_NAME_TEXT_SIZE = 12; // 默认名字字体大小
private static final int DEFAULT_NAME_TEXT_COLOR = 0xFFFFFFFF; // 默认名字颜色
private static final float DEFAULT_TAG_TEXT_SIZE = 12; // 默认标签字体大小
private static final int DEFAULT_TAG_TEXT_COLOR = 0xFFFFE554; // 默认标签颜色
private static final int DEFAULT_TAG_MARGIN = 0; // 默认标签间距
private static final int NAME_MARGIN_TOP = 3; // 名字与标签间距
public QXMeetUserView(Context context) {
super(context);
initAttrs(null);
initSubviews(context);
}
public QXMeetUserView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initAttrs(attrs);
initSubviews(context);
}
public QXMeetUserView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(attrs);
initSubviews(context);
}
// 初始化自定义属性
private void initAttrs(AttributeSet attrs) {
if (attrs == null) {
// 设置默认值
avatarSize = dpToPx(DEFAULT_AVATAR_SIZE);
dressSize = dpToPx(DEFAULT_DRESS_SIZE);
nameTextSize = DEFAULT_NAME_TEXT_SIZE;
nameTextColor = DEFAULT_NAME_TEXT_COLOR;
tagTextSize = DEFAULT_TAG_TEXT_SIZE;
tagTextColor = DEFAULT_TAG_TEXT_COLOR;
tagMargin = dpToPx(DEFAULT_TAG_MARGIN);
return;
}
// 从XML获取属性
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.QXMeetUserView);
avatarSize = ta.getDimensionPixelSize(R.styleable.QXMeetUserView_avatarSize, dpToPx(DEFAULT_AVATAR_SIZE));
dressSize = ta.getDimensionPixelSize(R.styleable.QXMeetUserView_dressSize, dpToPx(DEFAULT_DRESS_SIZE));
nameTextSize = ta.getDimension(R.styleable.QXMeetUserView_nameTextSize, DEFAULT_NAME_TEXT_SIZE);
nameTextColor = ta.getColor(R.styleable.QXMeetUserView_nameTextColor, DEFAULT_NAME_TEXT_COLOR);
tagTextSize = ta.getDimension(R.styleable.QXMeetUserView_tagTextSize, DEFAULT_TAG_TEXT_SIZE);
tagTextColor = ta.getColor(R.styleable.QXMeetUserView_tagTextColor, DEFAULT_TAG_TEXT_COLOR);
tagMargin = ta.getDimensionPixelSize(R.styleable.QXMeetUserView_tagMargin, dpToPx(DEFAULT_TAG_MARGIN));
ta.recycle();
// 日志输出属性值,便于调试
Log.d(TAG, "属性初始化 - 头像大小: " + avatarSize + ", 装饰大小: " + dressSize);
}
private void initSubviews(Context context) {
setClipChildren(false);
setClipToPadding(false);
setWillNotDraw(false);
// 为整个视图添加背景色,便于调试视图范围
// setBackgroundColor(0x0A000000); // 极浅灰色背景
// 2. 头像(中间层)
headerImageView = new GifAvatarOvalView(context);
headerImageView.setId(View.generateViewId());
headerImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
// 设置强制背景色,确保即使资源加载失败也能看到
// headerImageView.setBackgroundColor(0x33FF0000); // 半透明红色
// 尝试加载默认头像资源
try {
int resId = R.mipmap.default_avatar;
// headerImageView.setImageResource(resId);
Log.d(TAG, "尝试加载默认头像资源: " + resId);
} catch (Exception e) {
Log.e(TAG, "默认头像资源加载失败: " + e.getMessage());
}
LayoutParams headerParams = new LayoutParams(avatarSize, avatarSize);
headerParams.addRule(CENTER_IN_PARENT); // 头像在父容器居中
addView(headerImageView, headerParams);
Log.d(TAG, "头像已添加到视图,大小: " + avatarSize + "x" + avatarSize);
// 1. 装饰图(底层)- 优先初始化确保在最底层
dressImageView = new ImageView(context);
dressImageView.setId(View.generateViewId());
dressImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
// 设置强制背景色,确保即使资源加载失败也能看到
/// dressImageView.setBackgroundColor(0x330000FF); // 半透明蓝色
// 尝试加载装饰图资源
try {
int resId = R.mipmap.xlh_image;
dressImageView.setImageResource(resId);
Log.d(TAG, "尝试加载装饰图资源: " + resId);
} catch (Exception e) {
Log.e(TAG, "装饰图资源加载失败: " + e.getMessage());
}
LayoutParams dressParams = new LayoutParams(dressSize, dressSize);
dressParams.addRule(CENTER_HORIZONTAL);
dressParams.topMargin = dpToPx(5); // 稍微向下移动一点,确保可见
addView(dressImageView, dressParams);
Log.d(TAG, "装饰图已添加到视图,大小: " + dressSize + "x" + dressSize);
// 3. 标签(顶层,与头像底部平齐)
tagLabel = new TextView(context);
tagLabel.setId(View.generateViewId());
tagLabel.setTextColor(tagTextColor);
tagLabel.setTextSize(tagTextSize);
tagLabel.setGravity(Gravity.CENTER);
tagLabel.setText("房主");
tagLabel.setBackground(getRoundedRectBackground(0xFF8D6F28, dpToPx(8)));
tagLabel.setPadding(dpToPx(6), 0, dpToPx(6), 0);
LayoutParams tagParams = new LayoutParams(LayoutParams.WRAP_CONTENT, dpToPx(16));
tagParams.addRule(CENTER_HORIZONTAL);
addView(tagLabel, tagParams);
// 4. 名字(顶层,在标签下方)
nameLabel = new TextView(context);
nameLabel.setId(View.generateViewId());
nameLabel.setTextColor(nameTextColor);
nameLabel.setTextSize(nameTextSize);
nameLabel.setText("虚位以待");
nameLabel.setGravity(Gravity.CENTER);
nameLabel.setSingleLine(true);
LayoutParams nameParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
nameParams.addRule(CENTER_HORIZONTAL);
nameParams.addRule(BELOW, tagLabel.getId());
nameParams.topMargin = dpToPx(NAME_MARGIN_TOP);
addView(nameLabel, nameParams);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 强制设置最小宽高,确保视图可见
int minWidth = Math.max(avatarSize, dressSize);
int minHeight = dressSize + dpToPx(40); // 装饰图高度 + 标签和名字的高度
int width = resolveSizeAndState(minWidth, widthMeasureSpec, 0);
int height = resolveSizeAndState(minHeight, heightMeasureSpec, 0);
setMeasuredDimension(width, height);
// 测量子视图
measureChildren(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
);
Log.d(TAG, "onMeasure - 视图大小: " + width + "x" + height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
// 确保装饰图正确布局
if (dressImageView != null) {
int dressLeft = (getWidth() - dressSize) / 2;
int dressTop = dpToPx(5); // 顶部留出一点空间
dressImageView.layout(
dressLeft,
dressTop,
dressLeft + dressSize,
dressTop + dressSize
);
Log.d(TAG, "装饰图布局位置: " + dressLeft + "," + dressTop + "," +
(dressLeft + dressSize) + "," + (dressTop + dressSize));
}
// 确保头像正确布局
if (headerImageView != null) {
int avatarLeft = (getWidth() - avatarSize) / 2;
int avatarTop = (dressSize - avatarSize) / 2 + dpToPx(5); // 居中显示在装饰图上
headerImageView.layout(
avatarLeft,
avatarTop,
avatarLeft + avatarSize,
avatarTop + avatarSize
);
Log.d(TAG, "头像布局位置: " + avatarLeft + "," + avatarTop + "," +
(avatarLeft + avatarSize) + "," + (avatarTop + avatarSize));
}
// 标签布局(与头像底部平齐)
if (headerImageView != null && tagLabel != null) {
int avatarBottom = headerImageView.getBottom();
int tagTop = avatarBottom + tagMargin;
int tagLeft = (getWidth() - tagLabel.getMeasuredWidth()) / 2;
tagLabel.layout(
tagLeft,
tagTop,
tagLeft + tagLabel.getMeasuredWidth(),
tagTop + tagLabel.getMeasuredHeight()
);
}
// 名字布局(在标签下方)
if (tagLabel != null && nameLabel != null) {
int tagBottom = tagLabel.getBottom();
int nameTop = tagBottom + dpToPx(NAME_MARGIN_TOP);
int nameLeft = (getWidth() - nameLabel.getMeasuredWidth()) / 2;
nameLabel.layout(
nameLeft,
nameTop,
nameLeft + nameLabel.getMeasuredWidth(),
nameTop + nameLabel.getMeasuredHeight()
);
}
}
// 以下方法保持不变
public void setIsLuckUser(boolean isLuckUser) {
if (tagLabel == null) return;
if (isLuckUser) {
tagLabel.setTextColor(0xFFFFFFFF);
tagLabel.setBackground(getRoundedRectBackground(0xFF6C49E4, dpToPx(8)));
tagLabel.setText("幸运者");
} else {
tagLabel.setTextColor(tagTextColor);
tagLabel.setBackground(getRoundedRectBackground(0xFF8D6F28, dpToPx(8)));
tagLabel.setText("房主");
}
}
public void setModel(BlindBoxBean.xlhUser model) {
if (headerImageView == null || nameLabel == null) return;
if (model != null) {
try {
if (model.getAvatar().toString()!=""){
ImageUtils.loadHeadCC(model.getAvatar(), headerImageView);
}else {
int resId = R.mipmap.default_avatar;
headerImageView.setImageResource(resId);
}
Log.d(TAG, "加载用户头像: " + model.getAvatar());
} catch (Exception e) {
int resId = R.mipmap.default_avatar;
headerImageView.setImageResource(resId);
Log.e(TAG, "加载用户头像失败: " + e.getMessage());
//headerImageView.setBackgroundColor(0x33FF0000); // 保持红色背景以便识别
}
nameLabel.setText(model.getNickname() != null ? model.getNickname() : "虚位以待");
} else {
resetView();
}
}
public void resetView() {
if (headerImageView != null) {
try {
headerImageView.setImageResource(R.mipmap.default_avatar);
} catch (Exception e) {
Log.e(TAG, "重置头像失败: " + e.getMessage());
// headerImageView.setBackgroundColor(0x33FF0000);
}
}
if (nameLabel != null) {
nameLabel.setText("虚位以待");
}
setIsLuckUser(false);
}
private GradientDrawable getRoundedRectBackground(int color, float radius) {
GradientDrawable drawable = new GradientDrawable();
drawable.setColor(color);
drawable.setCornerRadius(radius);
return drawable;
}
private int dpToPx(int dp) {
float density = getResources().getDisplayMetrics().density;
return Math.round(dp * density);
}
// Getter方法
public ImageView getHeaderImageView() { return headerImageView; }
public ImageView getDressImageView() { return dressImageView; }
public TextView getTagLabel() { return tagLabel; }
public TextView getNameLabel() { return nameLabel; }
}

View File

@@ -0,0 +1,860 @@
package com.xscm.moduleutil.view;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
import android.text.InputType;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.xscm.moduleutil.R;
public class QXRedBagSendView extends FrameLayout {
// Properties
private String redBagType = "1";
private String redBagContentType = "1";
private String redBagTime = "0";
private int currentPage = 0;
private boolean isFromRule = false;
// UI Components
private LinearLayout mainContainer;
private ImageView bgImageView;
private TextView titleLabel;
private Button helpBtn;
private Button backBtn;
private Button closeBtn;
private Button nextBtn;
private Button commitBtn;
private LinearLayout firstContentView;
private Button normalRedBagBtn;
private Button pwdRedBagBtn;
private LinearLayout firstPwdView;
private EditText pwdTextField;
private LinearLayout firstTimeView;
private View scrollBgView;
private Button coinRedBagBtn;
private Button diamondRedBagBtn;
private Button selectedRedBagTimeBtn;
private LinearLayout nextContentView;
private TextView moneyLabel;
private TextView moneyUnitLabel;
private EditText moneyTextField;
private EditText countTextField;
private EditText remarkTextField;
private Button noDrawAuthBtn;
private Button collectDrawAuthBtn;
private Button upSeatDrawAuthBtn;
private LinearLayout ruleContentView;
private WebView webView;
private final int[] timeArray = {0, 1, 2, 5, 10};
private final int[] drawAuthArray = {0, 1, 2};
public QXRedBagSendView(Context context) {
super(context);
init(context);
}
public QXRedBagSendView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public QXRedBagSendView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
));
setBackgroundColor(Color.parseColor("#80000000"));
initMainContainer(context);
initTitleBar(context);
initContentArea(context);
initButtons(context);
// 默认选择普通红包
selectedRedBagTypeAction(normalRedBagBtn);
}
private void initMainContainer(Context context) {
mainContainer = new LinearLayout(context);
mainContainer.setOrientation(LinearLayout.VERTICAL);
LayoutParams containerParams = new LayoutParams(
dpToPx(345),
dpToPx(454)
);
containerParams.gravity = Gravity.CENTER;
mainContainer.setLayoutParams(containerParams);
// 背景图片
bgImageView = new ImageView(context);
bgImageView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
));
// 设置背景资源需要你在res/drawable中添加对应的图片
bgImageView.setBackgroundResource(R.mipmap.red_en);
// bgImageView.setBackgroundColor(Color.parseColor("#FFD700")); // 临时颜色
// 将背景图片添加到主容器
mainContainer.addView(bgImageView);
addView(mainContainer);
}
private void initTitleBar(Context context) {
// 标题栏容器
LinearLayout titleContainer = new LinearLayout(context);
titleContainer.setOrientation(LinearLayout.HORIZONTAL);
titleContainer.setGravity(Gravity.CENTER_VERTICAL);
LinearLayout.LayoutParams titleContainerParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
titleContainerParams.setMargins(0, dpToPx(15), 0, 0);
titleContainer.setLayoutParams(titleContainerParams);
// 帮助按钮
helpBtn = createIconButton(context);
helpBtn.setBackgroundColor(Color.TRANSPARENT);
helpBtn.setOnClickListener(v -> helpAction());
titleContainer.addView(helpBtn);
// 返回按钮
backBtn = createIconButton(context);
backBtn.setBackgroundColor(Color.TRANSPARENT);
backBtn.setOnClickListener(v -> backAction());
backBtn.setVisibility(View.GONE);
titleContainer.addView(backBtn);
// 标题
titleLabel = new TextView(context);
titleLabel.setText("直播间红包");
titleLabel.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);
titleLabel.setTextColor(Color.WHITE);
titleLabel.setTypeface(titleLabel.getTypeface(), android.graphics.Typeface.BOLD);
LinearLayout.LayoutParams titleParams = new LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1
);
titleLabel.setLayoutParams(titleParams);
titleLabel.setGravity(Gravity.CENTER);
titleContainer.addView(titleLabel);
// 关闭按钮
closeBtn = createIconButton(context);
closeBtn.setBackgroundColor(Color.TRANSPARENT);
closeBtn.setOnClickListener(v -> closeAction());
titleContainer.addView(closeBtn);
// 将标题栏添加到主容器(在背景图片之上)
mainContainer.addView(titleContainer);
}
private void initContentArea(Context context) {
// 内容区域容器
LinearLayout contentArea = new LinearLayout(context);
contentArea.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
0, 1
);
contentParams.setMargins(dpToPx(15), dpToPx(15), dpToPx(15), dpToPx(15));
contentArea.setLayoutParams(contentParams);
initFirstContentView(context, contentArea);
initNextContentView(context, contentArea);
initRuleView(context, contentArea);
mainContainer.addView(contentArea);
}
private void initButtons(Context context) {
// 按钮容器
LinearLayout buttonContainer = new LinearLayout(context);
buttonContainer.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
buttonParams.setMargins(dpToPx(15), 0, dpToPx(15), dpToPx(15));
buttonContainer.setLayoutParams(buttonParams);
// 下一步按钮
nextBtn = createActionButton(context, "下一步");
nextBtn.setOnClickListener(v -> nextAction());
buttonContainer.addView(nextBtn);
// 提交按钮
commitBtn = createActionButton(context, "发红包");
commitBtn.setOnClickListener(v -> commitAction());
commitBtn.setVisibility(View.GONE);
buttonContainer.addView(commitBtn);
mainContainer.addView(buttonContainer);
}
private void initFirstContentView(Context context, ViewGroup parent) {
firstContentView = new LinearLayout(context);
firstContentView.setOrientation(LinearLayout.VERTICAL);
firstContentView.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
));
parent.addView(firstContentView);
// 顶部视图 - 参与领取限制
LinearLayout firstTopView = createCardView(context);
firstTopView.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams topParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
dpToPx(88)
);
topParams.bottomMargin = dpToPx(12);
firstTopView.setLayoutParams(topParams);
firstContentView.addView(firstTopView);
TextView topTitleLabel = createTitleLabel(context, "参与领取限制");
firstTopView.addView(topTitleLabel);
LinearLayout typeButtonContainer = new LinearLayout(context);
typeButtonContainer.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
typeButtonContainer.setWeightSum(2);
firstTopView.addView(typeButtonContainer);
normalRedBagBtn = createTypeButton(context, "普通红包");
normalRedBagBtn.setOnClickListener(v -> selectedRedBagTypeAction(normalRedBagBtn));
typeButtonContainer.addView(normalRedBagBtn);
pwdRedBagBtn = createTypeButton(context, "口令红包");
pwdRedBagBtn.setOnClickListener(v -> selectedRedBagTypeAction(pwdRedBagBtn));
typeButtonContainer.addView(pwdRedBagBtn);
// 口令输入视图
firstPwdView = createInputCard(context, "口令", "请输入口令");
pwdTextField = (EditText) ((LinearLayout) firstPwdView).getChildAt(1);
firstContentView.addView(firstPwdView);
// 时间选择视图
firstTimeView = createCardView(context);
firstTimeView.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams timeParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
dpToPx(88)
);
timeParams.bottomMargin = dpToPx(12);
firstTimeView.setLayoutParams(timeParams);
firstContentView.addView(firstTimeView);
TextView timeTitleLabel = createTitleLabel(context, "开奖倒计时");
firstTimeView.addView(timeTitleLabel);
LinearLayout timeButtonContainer = new LinearLayout(context);
timeButtonContainer.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
firstTimeView.addView(timeButtonContainer);
// 创建时间选择按钮
for (int time : timeArray) {
Button timeBtn = createTimeButton(context, time);
timeBtn.setOnClickListener(v -> redBagTimeAction(timeBtn));
if (time == 0) {
timeBtn.setSelected(true);
selectedRedBagTimeBtn = timeBtn;
}
timeButtonContainer.addView(timeBtn);
}
// 红包类型选择
LinearLayout bottomView = createCardView(context);
bottomView.setOrientation(LinearLayout.HORIZONTAL);
bottomView.setGravity(Gravity.CENTER_VERTICAL);
LinearLayout.LayoutParams bottomParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
dpToPx(48)
);
bottomView.setLayoutParams(bottomParams);
firstContentView.addView(bottomView);
TextView redBagTypeLabel = createTitleLabel(context, "红包类型");
bottomView.addView(redBagTypeLabel);
// 红包类型切换容器
RelativeLayout typeSwitchContainer = new RelativeLayout(context);
LinearLayout.LayoutParams switchParams = new LinearLayout.LayoutParams(
dpToPx(130),
dpToPx(26)
);
switchParams.gravity = Gravity.CENTER_VERTICAL;
typeSwitchContainer.setLayoutParams(switchParams);
typeSwitchContainer.setBackgroundColor(Color.parseColor("#BA230A"));
typeSwitchContainer.setPadding(dpToPx(2), dpToPx(2), dpToPx(2), dpToPx(2));
bottomView.addView(typeSwitchContainer);
scrollBgView = new View(context);
RelativeLayout.LayoutParams scrollParams = new RelativeLayout.LayoutParams(
dpToPx(63),
ViewGroup.LayoutParams.MATCH_PARENT
);
scrollBgView.setLayoutParams(scrollParams);
scrollBgView.setBackgroundColor(Color.parseColor("#FDE8A3"));
typeSwitchContainer.addView(scrollBgView);
coinRedBagBtn = createContentTypeButton(context, "金币红包");
coinRedBagBtn.setSelected(true);
coinRedBagBtn.setOnClickListener(v -> redBagContentTypeAction(coinRedBagBtn));
typeSwitchContainer.addView(coinRedBagBtn);
diamondRedBagBtn = createContentTypeButton(context, "钻石红包");
diamondRedBagBtn.setOnClickListener(v -> redBagContentTypeAction(diamondRedBagBtn));
typeSwitchContainer.addView(diamondRedBagBtn);
}
private void initNextContentView(Context context, ViewGroup parent) {
nextContentView = new LinearLayout(context);
nextContentView.setOrientation(LinearLayout.VERTICAL);
nextContentView.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
));
nextContentView.setVisibility(View.GONE);
parent.addView(nextContentView);
// 可用余额标签
moneyLabel = new TextView(context);
moneyLabel.setText("-金币可用");
moneyLabel.setTextSize(TypedValue.COMPLEX_UNIT_SP, 13);
moneyLabel.setTextColor(Color.WHITE);
LinearLayout.LayoutParams moneyLabelParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
moneyLabelParams.gravity = Gravity.END;
moneyLabel.setLayoutParams(moneyLabelParams);
nextContentView.addView(moneyLabel);
// 金额输入
LinearLayout moneyBgView = createInputCard(context, "金额", "请输入红包金额");
moneyTextField = (EditText) moneyBgView.getChildAt(1);
moneyUnitLabel = new TextView(context);
moneyUnitLabel.setText("金币");
moneyUnitLabel.setTextSize(TypedValue.COMPLEX_UNIT_SP, 13);
moneyUnitLabel.setTextColor(Color.parseColor("#666666"));
LinearLayout.LayoutParams unitParams = new LinearLayout.LayoutParams(
dpToPx(30),
ViewGroup.LayoutParams.WRAP_CONTENT
);
moneyUnitLabel.setLayoutParams(unitParams);
moneyBgView.addView(moneyUnitLabel);
nextContentView.addView(moneyBgView);
// 个数输入
LinearLayout countBgView = createInputCard(context, "个数", "请输入红包数量");
countTextField = (EditText) countBgView.getChildAt(1);
TextView countUnitLabel = new TextView(context);
countUnitLabel.setText("");
countUnitLabel.setTextSize(TypedValue.COMPLEX_UNIT_SP, 13);
countUnitLabel.setTextColor(Color.parseColor("#666666"));
countUnitLabel.setLayoutParams(unitParams);
countBgView.addView(countUnitLabel);
nextContentView.addView(countBgView);
// 领取条件
LinearLayout drawAuthBgView = createCardView(context);
drawAuthBgView.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams authParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
dpToPx(88)
);
authParams.bottomMargin = dpToPx(12);
drawAuthBgView.setLayoutParams(authParams);
nextContentView.addView(drawAuthBgView);
TextView authTitleLabel = createTitleLabel(context, "条件");
drawAuthBgView.addView(authTitleLabel);
LinearLayout authButtonContainer = new LinearLayout(context);
authButtonContainer.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
drawAuthBgView.addView(authButtonContainer);
// 创建领取条件按钮
noDrawAuthBtn = createAuthButton(context, "");
noDrawAuthBtn.setSelected(true);
noDrawAuthBtn.setOnClickListener(v -> drawAuthAction(noDrawAuthBtn));
authButtonContainer.addView(noDrawAuthBtn);
collectDrawAuthBtn = createAuthButton(context, "收藏房间");
collectDrawAuthBtn.setOnClickListener(v -> drawAuthAction(collectDrawAuthBtn));
authButtonContainer.addView(collectDrawAuthBtn);
upSeatDrawAuthBtn = createAuthButton(context, "仅麦上用户");
upSeatDrawAuthBtn.setOnClickListener(v -> drawAuthAction(upSeatDrawAuthBtn));
authButtonContainer.addView(upSeatDrawAuthBtn);
// 备注输入
LinearLayout remarkBgView = createInputCard(context, "备注", "请输入备注");
remarkTextField = (EditText) remarkBgView.getChildAt(1);
nextContentView.addView(remarkBgView);
}
private void initRuleView(Context context, ViewGroup parent) {
ruleContentView = new LinearLayout(context);
ruleContentView.setOrientation(LinearLayout.VERTICAL);
ruleContentView.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
));
ruleContentView.setBackgroundColor(Color.WHITE);
ruleContentView.setPadding(dpToPx(15), dpToPx(15), dpToPx(15), dpToPx(15));
ruleContentView.setVisibility(View.GONE);
parent.addView(ruleContentView);
webView = new WebView(context);
webView.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
));
ruleContentView.addView(webView);
}
// 工具方法
private Button createIconButton(Context context) {
Button button = new Button(context);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
dpToPx(40),
dpToPx(40)
);
button.setLayoutParams(params);
return button;
}
private Button createActionButton(Context context, String text) {
Button button = new Button(context);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
dpToPx(44)
);
button.setLayoutParams(params);
button.setText(text);
button.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);
button.setTextColor(Color.parseColor("#F35248"));
button.setTypeface(button.getTypeface(), android.graphics.Typeface.BOLD);
button.setBackgroundColor(Color.parseColor("#FDE8A3")); // 临时背景色
return button;
}
private LinearLayout createCardView(Context context) {
LinearLayout card = new LinearLayout(context);
card.setBackgroundColor(Color.WHITE);
card.setPadding(dpToPx(15), dpToPx(11), dpToPx(15), dpToPx(5));
return card;
}
private TextView createTitleLabel(Context context, String text) {
TextView label = new TextView(context);
label.setText(text);
label.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);
label.setTextColor(Color.parseColor("#666666"));
label.setTypeface(label.getTypeface(), android.graphics.Typeface.BOLD);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
dpToPx(26)
);
label.setLayoutParams(params);
return label;
}
private Button createTypeButton(Context context, String text) {
Button button = new Button(context);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
0, dpToPx(36), 1
);
params.setMargins(0, 0, dpToPx(10), 0);
button.setLayoutParams(params);
button.setText(text);
button.setTextSize(TypedValue.COMPLEX_UNIT_SP, 15);
button.setTextColor(Color.WHITE);
button.setTypeface(button.getTypeface(), android.graphics.Typeface.BOLD);
button.setBackgroundColor(Color.parseColor("#FF9999")); // 临时背景色
return button;
}
private LinearLayout createInputCard(Context context, String title, String hint) {
LinearLayout card = new LinearLayout(context);
card.setOrientation(LinearLayout.HORIZONTAL);
card.setGravity(Gravity.CENTER_VERTICAL);
LinearLayout.LayoutParams cardParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
dpToPx(48)
);
cardParams.bottomMargin = dpToPx(12);
card.setLayoutParams(cardParams);
card.setBackgroundColor(Color.WHITE);
card.setPadding(dpToPx(15), 0, dpToPx(15), 0);
TextView titleLabel = createTitleLabel(context, title);
card.addView(titleLabel);
EditText editText = new EditText(context);
LinearLayout.LayoutParams editParams = new LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.MATCH_PARENT, 1
);
editParams.setMargins(dpToPx(15), 0, dpToPx(15), 0);
editText.setLayoutParams(editParams);
editText.setHint(hint);
editText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);
editText.setTextColor(Color.parseColor("#666666"));
editText.setGravity(Gravity.END);
editText.setBackground(null);
editText.setInputType(InputType.TYPE_CLASS_TEXT);
card.addView(editText);
return card;
}
private Button createTimeButton(Context context, int minutes) {
Button button = new Button(context);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
dpToPx(49), dpToPx(36)
);
params.setMargins(0, 0, dpToPx(10), 0);
button.setLayoutParams(params);
String text = minutes == 0 ? "立刻" : minutes + "分钟";
button.setText(text);
button.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
button.setTypeface(button.getTypeface(), android.graphics.Typeface.BOLD);
button.setBackgroundColor(Color.parseColor("#EEEEEE")); // 临时背景色
return button;
}
private Button createContentTypeButton(Context context, String text) {
Button button = new Button(context);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
dpToPx(63), ViewGroup.LayoutParams.MATCH_PARENT
);
button.setLayoutParams(params);
button.setText(text);
button.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
button.setBackgroundColor(Color.TRANSPARENT);
button.setTextColor(Color.parseColor("#FFC9C7"));
return button;
}
private Button createAuthButton(Context context, String text) {
Button button = new Button(context);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
0, dpToPx(36), 1
);
params.setMargins(0, 0, dpToPx(10), 0);
button.setLayoutParams(params);
button.setText(text);
button.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
button.setTypeface(button.getTypeface(), android.graphics.Typeface.BOLD);
button.setBackgroundColor(Color.parseColor("#EEEEEE")); // 临时背景色
return button;
}
private int dpToPx(int dp) {
return (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp,
getResources().getDisplayMetrics()
);
}
// Action Methods
private void nextAction() {
currentPage = 1;
moneyUnitLabel.setText(redBagContentType.equals("1") ? "金币" : "钻石");
backBtn.setVisibility(View.GONE);
helpBtn.setVisibility(View.VISIBLE);
nextBtn.setVisibility(View.GONE);
commitBtn.setVisibility(View.VISIBLE);
switchContentView(firstContentView, nextContentView);
}
private void commitAction() {
// 实现发红包逻辑
}
private void helpAction() {
isFromRule = true;
webView.loadUrl("http://www.baidu.com");
backBtn.setVisibility(View.VISIBLE);
helpBtn.setVisibility(View.GONE);
nextBtn.setVisibility(View.GONE);
commitBtn.setVisibility(View.GONE);
View currentView = currentPage == 1 ? nextContentView : firstContentView;
switchContentView(currentView, ruleContentView);
}
private void closeAction() {
hide();
}
private void backAction() {
backBtn.setVisibility(View.GONE);
helpBtn.setVisibility(View.VISIBLE);
View currentView = currentPage == 1 ? nextContentView : firstContentView;
nextBtn.setVisibility(currentPage == 0 ? View.VISIBLE : View.GONE);
commitBtn.setVisibility(currentPage == 1 ? View.VISIBLE : View.GONE);
switchContentView(ruleContentView, currentView);
isFromRule = false;
}
private void selectedRedBagTypeAction(Button sender) {
if (sender.isSelected()) return;
if (sender == normalRedBagBtn) {
pwdRedBagBtn.setSelected(false);
normalRedBagBtn.setSelected(true);
firstPwdView.setVisibility(View.GONE);
// 调整时间视图位置
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) firstTimeView.getLayoutParams();
params.topMargin = 0;
params.bottomMargin = dpToPx(12);
firstTimeView.setLayoutParams(params);
redBagType = "1";
} else {
pwdRedBagBtn.setSelected(true);
normalRedBagBtn.setSelected(false);
firstPwdView.setVisibility(View.VISIBLE);
// 调整时间视图位置
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) firstTimeView.getLayoutParams();
params.topMargin = dpToPx(60);
params.bottomMargin = dpToPx(12);
firstTimeView.setLayoutParams(params);
redBagType = "2";
}
// 更新按钮背景色
updateButtonSelection(normalRedBagBtn, sender == normalRedBagBtn);
updateButtonSelection(pwdRedBagBtn, sender == pwdRedBagBtn);
}
private void redBagContentTypeAction(Button sender) {
if (sender.isSelected()) return;
if (sender == coinRedBagBtn) {
diamondRedBagBtn.setSelected(false);
coinRedBagBtn.setSelected(true);
redBagContentType = "1";
animateSwitchBg(true);
} else {
coinRedBagBtn.setSelected(false);
diamondRedBagBtn.setSelected(true);
redBagContentType = "2";
animateSwitchBg(false);
}
// 更新按钮文字颜色
updateContentTypeButtonColor(coinRedBagBtn, coinRedBagBtn.isSelected());
updateContentTypeButtonColor(diamondRedBagBtn, diamondRedBagBtn.isSelected());
}
private void drawAuthAction(Button sender) {
if (sender == noDrawAuthBtn) {
noDrawAuthBtn.setSelected(true);
collectDrawAuthBtn.setSelected(false);
upSeatDrawAuthBtn.setSelected(false);
} else if (sender == collectDrawAuthBtn) {
collectDrawAuthBtn.setSelected(!collectDrawAuthBtn.isSelected());
if (upSeatDrawAuthBtn.isSelected() || collectDrawAuthBtn.isSelected()) {
noDrawAuthBtn.setSelected(false);
} else {
noDrawAuthBtn.setSelected(true);
}
} else if (sender == upSeatDrawAuthBtn) {
noDrawAuthBtn.setSelected(false);
upSeatDrawAuthBtn.setSelected(!upSeatDrawAuthBtn.isSelected());
if (upSeatDrawAuthBtn.isSelected() || collectDrawAuthBtn.isSelected()) {
noDrawAuthBtn.setSelected(false);
} else {
noDrawAuthBtn.setSelected(true);
}
}
// 更新按钮背景色
updateAuthButtonSelection(noDrawAuthBtn, noDrawAuthBtn.isSelected());
updateAuthButtonSelection(collectDrawAuthBtn, collectDrawAuthBtn.isSelected());
updateAuthButtonSelection(upSeatDrawAuthBtn, upSeatDrawAuthBtn.isSelected());
}
private void redBagTimeAction(Button sender) {
if (sender.isSelected()) return;
if (selectedRedBagTimeBtn != null) {
selectedRedBagTimeBtn.setSelected(false);
updateTimeButtonSelection(selectedRedBagTimeBtn, false);
}
sender.setSelected(true);
selectedRedBagTimeBtn = sender;
int minutes = 0;
try {
String text = sender.getText().toString();
if (text.equals("立刻")) {
minutes = 0;
} else {
minutes = Integer.parseInt(text.replace("分钟", ""));
}
} catch (Exception e) {
minutes = 0;
}
redBagTime = String.valueOf(minutes * 60);
updateTimeButtonSelection(sender, true);
}
// Helper Methods
private void switchContentView(View hideView, View showView) {
hideView.setVisibility(View.GONE);
showView.setVisibility(View.VISIBLE);
}
private void updateButtonSelection(Button button, boolean selected) {
button.setBackgroundColor(selected ?
Color.parseColor("#FF5555") : Color.parseColor("#FF9999"));
}
private void updateTimeButtonSelection(Button button, boolean selected) {
button.setBackgroundColor(selected ?
Color.parseColor("#FF5555") : Color.parseColor("#EEEEEE"));
}
private void updateAuthButtonSelection(Button button, boolean selected) {
button.setBackgroundColor(selected ?
Color.parseColor("#FF5555") : Color.parseColor("#EEEEEE"));
}
private void updateContentTypeButtonColor(Button button, boolean selected) {
button.setTextColor(selected ?
Color.parseColor("#D01717") : Color.parseColor("#FFC9C7"));
}
private void animateSwitchBg(boolean toLeft) {
int targetX = toLeft ? 0 : dpToPx(63);
ValueAnimator animator = ValueAnimator.ofInt(
((RelativeLayout.LayoutParams) scrollBgView.getLayoutParams()).leftMargin,
targetX
);
animator.addUpdateListener(animation -> {
int value = (int) animation.getAnimatedValue();
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) scrollBgView.getLayoutParams();
params.leftMargin = value;
scrollBgView.setLayoutParams(params);
});
animator.setDuration(300);
animator.setInterpolator(new DecelerateInterpolator());
animator.start();
}
// Public Methods
public void showInView(ViewGroup parent) {
parent.addView(this);
// 添加入场动画
mainContainer.setTranslationY(-getHeight());
mainContainer.animate()
.translationY(0)
.setDuration(300)
.setInterpolator(new DecelerateInterpolator())
.start();
}
public void hide() {
// 添加退场动画
mainContainer.animate()
.translationY(getHeight())
.setDuration(300)
.setInterpolator(new DecelerateInterpolator())
.withEndAction(() -> {
ViewGroup parent = (ViewGroup) getParent();
if (parent != null) {
parent.removeView(QXRedBagSendView.this);
}
})
.start();
}
// Getter methods
public String getRedBagType() {
return redBagType;
}
public String getRedBagContentType() {
return redBagContentType;
}
public String getRedBagTime() {
return redBagTime;
}
public String getPassword() {
return pwdTextField.getText().toString();
}
public String getMoney() {
return moneyTextField.getText().toString();
}
public String getCount() {
return countTextField.getText().toString();
}
public String getRemark() {
return remarkTextField.getText().toString();
}
public int getDrawAuth() {
if (noDrawAuthBtn.isSelected()) return 0;
if (collectDrawAuthBtn.isSelected()) return 1;
if (upSeatDrawAuthBtn.isSelected()) return 2;
return 0;
}
}

View File

@@ -0,0 +1,268 @@
package com.xscm.moduleutil.view;
import android.content.Context;
import android.graphics.Typeface;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.ScaleAnimation;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.xscm.moduleutil.R;
public class QXTimeDownView extends FrameLayout {
private ImageView bgImageView;
private TextView titleLabel;
private TextView timeLabel;
private TextView bigTimeLabel;
private long endTime;
private long startTime;
private Handler timerHandler;
private Runnable timerRunnable;
private TimeDownDelegate delegate;
public interface TimeDownDelegate {
void timeDownStartAnimation();
void timeDownUpdateAnimationWithTime(long time);
void timeDownStopAnimation();
void timeDownDidFinished();
}
public QXTimeDownView(Context context) {
super(context);
initSubviews(context);
}
public QXTimeDownView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initSubviews(context);
}
public QXTimeDownView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initSubviews(context);
}
private void initSubviews(Context context) {
// 背景图片(最大的圆圈)
bgImageView = new ImageView(context);
bgImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
bgImageView.setImageResource(R.drawable.ac_time_down_bg);
LayoutParams bgParams = new LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT
);
bgParams.gravity = Gravity.CENTER;
addView(bgImageView, bgParams);
// 时间标签(居中显示在背景图上)
timeLabel = new TextView(context);
timeLabel.setTextSize(16);
timeLabel.setTextColor(0xFFFFECA7);
timeLabel.setGravity(Gravity.CENTER);
timeLabel.setTypeface(android.graphics.Typeface.create("sans-serif-condensed", android.graphics.Typeface.NORMAL));
LayoutParams timeParams = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT
);
timeParams.gravity = Gravity.CENTER;
timeParams.topMargin = dpToPx(-10);
addView(timeLabel, timeParams);
// 标题标签(显示在时间标签下方)
titleLabel = new TextView(context);
titleLabel.setTextSize(12);
titleLabel.setTextColor(0xFFFFECA7);
titleLabel.setText("倒计时");
titleLabel.setGravity(Gravity.CENTER);
LayoutParams titleParams = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT
);
titleParams.gravity = Gravity.CENTER;
titleParams.topMargin = dpToPx(12); // 在timeLabel下方一定距离
addView(titleLabel, titleParams);
// 大时间标签初始隐藏用于最后30秒显示
bigTimeLabel = new TextView(context);
bigTimeLabel.setTextSize(20);
bigTimeLabel.setText("-");
bigTimeLabel.setTextColor(0xFFFFECA7);
bigTimeLabel.setGravity(Gravity.CENTER);
bigTimeLabel.setVisibility(View.GONE);
bigTimeLabel.setTypeface(Typeface.create("semibold", Typeface.NORMAL));
LayoutParams bigTimeParams = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT
);
bigTimeParams.gravity = Gravity.CENTER;
addView(bigTimeLabel, bigTimeParams);
// 初始化定时器
timerHandler = new Handler(Looper.getMainLooper());
}
public void setEndTime(long endTime) {
this.endTime = endTime;
// 获取当前时间(秒)
long currentTime = System.currentTimeMillis() / 1000;
this.startTime = endTime - currentTime;
if (this.startTime <= 0) {
// 时间错误不进行倒计时
showBigTimeLabel("0");
return;
}
stopTimer();
startTimer();
}
private void startTimer() {
timerRunnable = new Runnable() {
@Override
public void run() {
startTime--;
long min = (startTime % 3600) / 60;
long second = startTime % 60;
if (startTime <= 30) {
// 最后30秒显示大数字
showBigTimeLabel(String.valueOf(startTime));
if (startTime == 30) {
if (delegate != null) {
delegate.timeDownStartAnimation();
}
} else {
if (delegate != null) {
delegate.timeDownUpdateAnimationWithTime(startTime);
}
}
// 弹性动画
performScaleAnimation();
} else {
// 正常倒计时显示
showNormalTime(String.format("%02d:%02d", min, second));
if (delegate != null) {
delegate.timeDownStopAnimation();
}
}
if (startTime <= 0) {
stopTimer();
showBigTimeLabel("0");
if (delegate != null) {
delegate.timeDownDidFinished();
delegate.timeDownStopAnimation();
}
} else {
// 继续下一次计时
timerHandler.postDelayed(this, 1000);
}
}
};
// 立即开始第一次执行
timerHandler.post(timerRunnable);
}
public void stopTimer() {
if (timerHandler != null && timerRunnable != null) {
timerHandler.removeCallbacks(timerRunnable);
timerRunnable = null;
}
}
private void showBigTimeLabel(String text) {
bigTimeLabel.setVisibility(View.VISIBLE);
timeLabel.setVisibility(View.GONE);
titleLabel.setVisibility(View.GONE);
bigTimeLabel.setText(text);
}
private void showNormalTime(String timeText) {
bigTimeLabel.setVisibility(View.GONE);
timeLabel.setVisibility(View.VISIBLE);
titleLabel.setVisibility(View.VISIBLE);
timeLabel.setText(timeText);
}
private void performScaleAnimation() {
ScaleAnimation scaleAnimation = new ScaleAnimation(
1.0f, 1.5f, // X轴从1.0缩放到1.5
1.0f, 1.5f, // Y轴从1.0缩放到1.5
ScaleAnimation.RELATIVE_TO_SELF, 0.5f, // 缩放中心X
ScaleAnimation.RELATIVE_TO_SELF, 0.5f // 缩放中心Y
);
scaleAnimation.setDuration(600);
scaleAnimation.setFillAfter(false);
bigTimeLabel.startAnimation(scaleAnimation);
}
public void reset() {
stopTimer();
showNormalTime("00:00");
bigTimeLabel.setVisibility(View.GONE);
timeLabel.setVisibility(View.VISIBLE);
titleLabel.setVisibility(View.VISIBLE);
}
// 辅助方法dp 转 px
private int dpToPx(int dp) {
float density = getResources().getDisplayMetrics().density;
return Math.round(dp * density);
}
// Getter 和 Setter 方法
public void setDelegate(TimeDownDelegate delegate) {
this.delegate = delegate;
}
public long getEndTime() {
return endTime;
}
public long getStartTime() {
return startTime;
}
public TextView getTitleLabel() {
return titleLabel;
}
public TextView getTimeLabel() {
return timeLabel;
}
public TextView getBigTimeLabel() {
return bigTimeLabel;
}
public ImageView getBgImageView() {
return bgImageView;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
stopTimer();
}
}

View File

@@ -0,0 +1,207 @@
package com.xscm.moduleutil.view
import android.content.Context
import android.graphics.Color
import android.graphics.Typeface
import android.text.Html
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.TextPaint
import android.text.method.LinkMovementMethod
import android.text.style.*
import android.util.Log
import android.util.AttributeSet
import android.view.View
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.annotation.StringRes
/**
* 封装了富文本展示功能的自定义TextView
* 支持HTML格式、自定义样式文本和点击事件
*/
class RichTextView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : androidx.appcompat.widget.AppCompatTextView(context, attrs, defStyleAttr) {
// 富文本构建器
private val spannableBuilder = SpannableStringBuilder()
init {
// 初始化配置
movementMethod = LinkMovementMethod.getInstance()
highlightColor = Color.TRANSPARENT // 移除点击高亮
}
/**
* 设置HTML格式的富文本
*/
fun setHtmlText(html: String) {
val spanned = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
Html.fromHtml(html, Html.FROM_HTML_MODE_COMPACT)
} else {
@Suppress("DEPRECATION")
Html.fromHtml(html)
}
text = spanned
}
/**
* 从资源文件设置HTML富文本
*/
fun setHtmlText(@StringRes resId: Int) {
setHtmlText(context.getString(resId))
}
/**
* 开始构建富文本
*/
fun beginBuild(): RichTextBuilder {
spannableBuilder.clear()
return RichTextBuilder()
}
/**
* 富文本构建器
* 支持链式调用添加各种样式的文本
*/
inner class RichTextBuilder {
/**
* 添加普通文本
*/
fun addText(text: String): RichTextBuilder {
spannableBuilder.append(text)
return this
}
/**
* 添加普通文本(从资源文件)
*/
fun addText(@StringRes resId: Int): RichTextBuilder {
return addText(context.getString(resId))
}
/**
* 添加加粗文本
*/
fun addBoldText(text: String): RichTextBuilder {
return addStyledText(text, StyleSpan(Typeface.BOLD))
}
/**
* 添加斜体文本
*/
fun addItalicText(text: String): RichTextBuilder {
return addStyledText(text, StyleSpan(Typeface.ITALIC))
}
/**
* 添加下划线文本
*/
fun addUnderlineText(text: String): RichTextBuilder {
return addStyledText(text, UnderlineSpan())
}
/**
* 添加删除线文本
*/
fun addStrikethroughText(text: String): RichTextBuilder {
return addStyledText(text, StrikethroughSpan())
}
/**
* 添加指定颜色的文本
*/
fun addColoredText(text: String, @ColorInt color: Int): RichTextBuilder {
return addStyledText(text, ForegroundColorSpan(color))
}
/**
* 添加指定背景色的文本
*/
fun addBackgroundColoredText(text: String, @ColorInt color: Int): RichTextBuilder {
return addStyledText(text, BackgroundColorSpan(color))
}
/**
* 添加指定大小的文本
* @param proportion 相对于默认大小的比例
*/
fun addSizedText(text: String, proportion: Float): RichTextBuilder {
return addStyledText(text, RelativeSizeSpan(proportion))
}
/**
* 添加带有点击事件的文本
*/
fun addClickableText(
text: String,
@ColorInt linkColor: Int = Color.BLUE,
isUnderline: Boolean = false,
onClick: () -> Unit
): RichTextBuilder {
val start = spannableBuilder.length
spannableBuilder.append(text)
val end = spannableBuilder.length
val clickableSpan = object : ClickableSpan() {
override fun onClick(widget: View) {
onClick.invoke()
}
override fun updateDrawState(ds: TextPaint) {
super.updateDrawState(ds)
ds.color = linkColor
ds.isUnderlineText = isUnderline
}
}
spannableBuilder.setSpan(
clickableSpan,
start,
end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
return this
}
/**
* 添加换行
*/
fun addLineBreak(): RichTextBuilder {
spannableBuilder.append("\n")
return this
}
/**
* 添加自定义样式的文本
*/
fun addStyledText(text: String, vararg spans: Any): RichTextBuilder {
val start = spannableBuilder.length
spannableBuilder.append(text)
val end = spannableBuilder.length
spans.forEach { span ->
spannableBuilder.setSpan(
span,
start,
end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
return this
}
/**
* 完成构建并应用到TextView
*/
fun build() {
text = spannableBuilder
}
}
}

View File

@@ -0,0 +1,271 @@
package com.xscm.moduleutil.widget;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.RecyclerView;
/**
* @Author lxj$
* @Time $ 2025-9-4 21:04:34$
* @Description 自定义滚动辅助类$
*/
public class CenterScrollHelper {
private RecyclerView recyclerView;
public CenterScrollHelper(RecyclerView recyclerView) {
this.recyclerView = recyclerView;
}
/**
* 循环滚动指定圈数后停在目标位置
* @param targetPosition 目标位置
* @param circles 滚动圈数
* @param durationPerItem 每个item滚动的持续时间控制速度
* @param adapterSize 适配器总大小
*/
public void scrollWithCircles(int targetPosition, int circles, int durationPerItem, int adapterSize) {
scrollWithCircles(targetPosition, circles, durationPerItem, adapterSize, null);
}
/**
* 循环滚动指定圈数后停在目标位置(带回调)
* @param targetPosition 目标位置
* @param circles 滚动圈数
* @param durationPerItem 每个item滚动的持续时间控制速度
* @param adapterSize 适配器总大小
* @param onComplete 滚动完成回调
*/
public void scrollWithCircles(int targetPosition, int circles, int durationPerItem,
int adapterSize, Runnable onComplete) {
if (recyclerView.getLayoutManager() == null) {
if (onComplete != null) onComplete.run();
return;
}
// 计算总滚动位置(多圈+目标位置)
int totalItems = circles * adapterSize + targetPosition;
// 使用LinearSmoothScroller进行平滑滚动
LinearSmoothScroller smoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {
private static final float ACCELERATION = 0.5f; // 加速度
private static final float DECELERATION = 0.8f; // 减速度
@Override
protected int calculateTimeForScrolling(int dx) {
// 使用缓动函数计算时间,实现从慢到快再到慢的效果
// return calculateEasingTime(dx, durationPerItem);
// // 简单线性时间计算,确保滚动速度一致
// int screenWidth = recyclerView.getWidth();
// if (screenWidth <= 0) {
// return durationPerItem * 3;
// }
// int itemWidth = screenWidth / 3;
// int items = Math.max(1, dx / itemWidth);
// return durationPerItem * items;
// 使用缓动函数计算时间,实现从慢到快再到慢的效果
return calculateEasingTime(dx, durationPerItem);
}
@Override
protected void onStop() {
super.onStop();
// 滚动停止后确保目标位置居中
// scrollToCenter(targetPosition);
// 执行完成回调
if (onComplete != null) {
recyclerView.post(onComplete);
}
}
};
smoothScroller.setTargetPosition(totalItems);
recyclerView.getLayoutManager().startSmoothScroll(smoothScroller);
}
// 在 CenterScrollHelper 类中添加
public void reset() {
// 清理可能存在的回调或状态
if (recyclerView != null) {
recyclerView.stopScroll();
recyclerView.getHandler().removeCallbacksAndMessages(null);
}
}
/**
* 使用缓动函数计算滚动时间,实现加速减速效果
* @param dx 滚动距离
* @param baseDurationPerItem 基础时间
* @return 计算后的时间
*/
private int calculateEasingTime(int dx, int baseDurationPerItem) {
if (dx <= 0) return 0;
// 获取屏幕宽度作为参考
int screenWidth = recyclerView.getWidth();
if (screenWidth <= 0) {
return baseDurationPerItem * 3; // 默认值
}
// 计算item宽度假设每屏3个item
int itemWidth = screenWidth / 3;
// 计算滚动的item数量
int items = Math.max(1, dx / itemWidth);
// 使用easeInOutQuad缓动函数实现先慢中快后慢的效果
double progress = Math.min(1.0, (double) items / 100); // 假设100个item为完整过程
// easeInOutQuad缓动函数
double easeProgress;
if (progress < 0.5) {
easeProgress = 2 * progress * progress; // 先慢后快
} else {
easeProgress = 1 - Math.pow(-2 * progress + 2, 2) / 2; // 后慢
}
// 计算时间:开始慢(500ms),后来快(50ms)
int minDuration = 1000; // 最快速度
int maxDuration = 2000; // 最慢速度
int calculatedTime = (int) (maxDuration - (maxDuration - minDuration) * easeProgress);
return Math.max(minDuration, calculatedTime);
}
/**
* 将指定位置的item精确居中显示
* @param position 需要居中的位置(在循环列表中的实际位置)
* @param originalSize 原始数据大小
*/
public void scrollToCenter(int position, int originalSize) {
if (recyclerView.getLayoutManager() == null) return;
int screenWidth = recyclerView.getWidth();
if (screenWidth <= 0) return;
// 计算item宽度假设每个item等宽
int itemWidth = screenWidth / 3; // 每屏显示3个item
// 计算使item居中需要滚动的总距离
int targetScrollX = position * itemWidth - (screenWidth - itemWidth) / 2;
// 获取当前滚动位置
int currentScrollX = recyclerView.computeHorizontalScrollOffset();
// 计算需要滚动的距离
int scrollDistance = targetScrollX - currentScrollX;
// 执行滚动
recyclerView.smoothScrollBy(scrollDistance, 0);
}
/**
* 将指定位置的item精确居中显示简化版
* @param position 需要居中的位置
*/
public void scrollToCenter(int position) {
if (recyclerView.getLayoutManager() == null) return;
int screenWidth = recyclerView.getWidth();
if (screenWidth <= 0) return;
// 计算item宽度
int itemWidth = screenWidth / 3;
// 计算目标位置的左边缘
int targetLeft = position * itemWidth;
// 计算使item居中需要滚动到的位置
int targetScrollX = targetLeft - (screenWidth - itemWidth) / 2;
// 获取当前滚动位置
int currentScrollX = recyclerView.computeHorizontalScrollOffset();
// 计算需要滚动的距离
int scrollDistance = targetScrollX - currentScrollX;
// 如果距离很小,就不需要滚动了
if (Math.abs(scrollDistance) > 5) {
// 执行滚动
recyclerView.smoothScrollBy(scrollDistance, 0);
}
}
/**
* 将指定位置的item居中显示支持循环
*/
public void centerItem(int targetPosition, int adapterSize) {
if (recyclerView.getLayoutManager() == null) return;
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int screenWidth = recyclerView.getWidth();
if (screenWidth <= 0) return;
int itemWidth = screenWidth / 3; // 每个item占屏幕1/3
// 计算需要滚动的距离使item居中
// 这里需要根据实际的item宽度和屏幕宽度来计算居中位置
int targetScrollX = targetPosition * itemWidth - (screenWidth - itemWidth) / 2;
int currentScrollX = recyclerView.computeHorizontalScrollOffset();
int scrollDistance = targetScrollX - currentScrollX;
// 使用smoothScrollBy确保平滑滚动到居中位置
recyclerView.smoothScrollBy(scrollDistance, 0);
}
/**
* 使用更复杂的缓动函数计算滚动时间
* @param dx 滚动距离
* @param baseDurationPerItem 基础时间
* @return 计算后的时间
*/
private int calculateAdvancedEasingTime(int dx, int baseDurationPerItem) {
if (dx <= 0) return 0;
int screenWidth = recyclerView.getWidth();
if (screenWidth <= 0) {
return baseDurationPerItem * 3;
}
int itemWidth = screenWidth / 3;
int totalItems = dx / itemWidth;
// 分段处理:开始慢,中间快,结束慢
int totalTime = 0;
for (int i = 0; i < totalItems; i++) {
// 计算当前位置的进度 (0-1)
double progress = (double) i / Math.max(1, totalItems);
// 使用贝塞尔缓动函数:慢-快-慢
double easedProgress = bezierEasing(progress, 0.25, 0.1, 0.25, 1.0);
// 根据进度调整时间
int minTime = 30; // 最快时间
int maxTime = baseDurationPerItem * 2; // 最慢时间
int itemTime = (int) (maxTime - (maxTime - minTime) * easedProgress);
totalTime += itemTime;
}
return Math.max(100, totalTime); // 至少100ms
}
/**
* 贝塞尔缓动函数
* @param t 时间进度 (0-1)
* @param x1 控制点1 x
* @param y1 控制点1 y
* @param x2 控制点2 x
* @param y2 控制点2 y
* @return 缓动后的值
*/
private double bezierEasing(double t, double x1, double y1, double x2, double y2) {
// 简化的贝塞尔曲线计算
// 这里使用一个近似算法
double a = 1 - t;
return 3 * a * a * t * y1 + 3 * a * t * t * y2 + t * t * t;
}
}

View File

@@ -0,0 +1,285 @@
package com.xscm.moduleutil.widget;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import com.blankj.utilcode.util.ScreenUtils;
import com.xscm.moduleutil.utils.BarUtils;
/**
* 描述:小时榜的显示视图
*/
public class DropHourlView extends LinearLayout {
private int rightMargin = 0;
private float lastX, lastY;
private int screenWidth;
private int screenHeight; // 添加屏幕高度变量
public DropHourlView(Context context) {
super(context);
init();
}
public DropHourlView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DropHourlView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
void init() {
// 初始化屏幕尺寸
screenWidth = ScreenUtils.getScreenWidth();
screenHeight = ScreenUtils.getScreenHeight();
post(new Runnable() {
@Override
public void run() {
//设置初始位置
int sh = ScreenUtils.getScreenHeight();
int sw = ScreenUtils.getScreenWidth()-200;
// setBackgroundResource(R.drawable.bg_home_drop_view);
int y = (int) (0.5f * sh) - getHeight();
// 确保Y坐标不会超出屏幕范围
y = Math.max(0, Math.min(y, sh - getHeight()));
int x = sw - getWidth();
setTranslationX(x);
setTranslationY(y);
}
});
updateSize();
mStatusBarHeight = BarUtils.getStatusBarHeight();
}
/**
* 更新屏幕尺寸信息
*/
protected void updateSize() {
ViewGroup viewGroup = (ViewGroup) getParent();
if (viewGroup != null) {
mScreenWidth = viewGroup.getWidth();
mScreenHeight = viewGroup.getHeight();
} else {
// 如果父视图为空,使用屏幕的实际宽度和高度
mScreenWidth = getResources().getDisplayMetrics().widthPixels;
mScreenHeight = getResources().getDisplayMetrics().heightPixels;
}
}
boolean starDrap = false;
float X1;
float X2;
float Y1;
float Y2;
// 记录视图初始位置
private float originalX;
private float originalY;
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (starDrap) return true;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
X1 = event.getRawX();
Y1 = event.getRawY();
// 记录视图当前位置
originalX = getTranslationX();
originalY = getTranslationY();
break;
case MotionEvent.ACTION_MOVE:
X2 = event.getRawX();
Y2 = event.getRawY();
Action(X1, X2, Y1, Y2);
break;
}
return starDrap;
}
String TAG = "DropHourlView";
public boolean Action(float X1, float X2, float Y1, float Y2) {
float ComparedX = X2 - X1;//第二次的X坐标的位置减去第一次X坐标的位置代表X坐标上的变化情况
float ComparedY = Y2 - Y1;//同理
//当X坐标的变化量的绝对值大于Y坐标的变化量的绝对值以X坐标的变化情况作为判断依据
//上下左右的判断,都在一条直线上,但手指的操作不可能划直线,所有选择变化量大的方向上的量
//作为判断依据
if (Math.abs(ComparedX) > 30 || Math.abs(ComparedY) > 30) {
Log.i(TAG, "Action: 拖动");
starDrap = true;
// setBackgroundResource(R.drawable.bg_home_drop_view);
return true;
} else {
starDrap = false;
return false;
}
}
private float mOriginalRawX;
private float mOriginalRawY;
private float mOriginalX;
private float mOriginalY;
protected int mScreenWidth;
private int mScreenHeight;
private int mStatusBarHeight;
private void updateViewPosition(MotionEvent event) {
// 计算新的Y位置
float desY = mOriginalY + event.getRawY() - mOriginalRawY;
// 限制Y位置不超出屏幕边界
if (desY < mStatusBarHeight) {
desY = mStatusBarHeight;
}
if (desY > mScreenHeight - getHeight()) {
desY = mScreenHeight - getHeight();
}
// 计算新的X位置
float desX = mOriginalX + event.getRawX() - mOriginalRawX;
// 限制X位置不超出屏幕边界
if (desX < 0) {
desX = 0;
}
if (desX > mScreenWidth - getWidth()) {
desX = mScreenWidth - getWidth();
}
// 设置视图的新位置
setX(desX);
setY(desY);
}
private void changeOriginalTouchParams(MotionEvent event) {
mOriginalX = getX();//getX()相对于控件X坐标的距离
mOriginalY = getY();
mOriginalRawX = event.getRawX();//getRawX()指控件在屏幕上的X坐标
mOriginalRawY = event.getRawY();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event == null) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
changeOriginalTouchParams(event);
updateSize(); // 添加这行确保尺寸是最新的
// ... 其他现有代码 ...
break;
case MotionEvent.ACTION_MOVE:
updateViewPosition(event); // 使用更新后的带边界检查的方法
// setBackgroundResource(R.drawable.bg_home_drop_view);
// 使用屏幕绝对坐标计算新位置
// float newX = originalX + (event.getRawX() - X1);
// float newY = originalY + (event.getRawY() - Y1);
//
// // 限制X和Y坐标在屏幕范围内
// newX = Math.max(0, Math.min(newX, screenWidth - getWidth()));
// newY = Math.max(0, Math.min(newY, screenHeight - getHeight()));
//
// setTranslationX(newX);
// setTranslationY(newY);
// X2 = event.getRawX();
break;
case MotionEvent.ACTION_UP:
starDrap = false;
int sw = ScreenUtils.getScreenWidth();
Log.i(TAG, "onTouchEvent: " + sw + "," + X2);
boolean isR = getTranslationX() + getWidth() / 2 >= sw / 2;//贴边方向
// 获取当前Y坐标
float currentY = getTranslationY();
// 创建X轴和Y轴的动画
ObjectAnimator animX = ObjectAnimator.ofFloat(this, "translationX", isR ? sw - getWidth() : 0f).setDuration(200);
// Y轴保持当前位置但确保在屏幕范围内
currentY = Math.max(0, Math.min(currentY, screenHeight - getHeight()));
ObjectAnimator animY = ObjectAnimator.ofFloat(this, "translationY", currentY).setDuration(200);
animX.start();
animY.start();
break;
}
return true;
}
public void doRevealAnimation(View mPuppet, boolean flag) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
int[] vLocation = new int[2];
getLocationInWindow(vLocation);
int centerX = vLocation[0] + getMeasuredWidth() / 2;
int centerY = vLocation[1] + getMeasuredHeight() / 2;
int height = ScreenUtils.getScreenHeight();
int width = ScreenUtils.getScreenWidth();
int maxRradius = (int) Math.hypot(height, width);
Log.e("hei", maxRradius + "");
if (flag) {
mPuppet.setVisibility(VISIBLE);
Animator animator = ViewAnimationUtils.createCircularReveal(mPuppet, centerX, centerY, maxRradius, 0);
animator.setDuration(600);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mPuppet.setVisibility(View.GONE);
}
});
animator.start();
flag = false;
} else {
Animator animator = ViewAnimationUtils.createCircularReveal(mPuppet, centerX, centerY, 0, maxRradius);
animator.setDuration(1000);
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
mPuppet.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.start();
flag = true;
}
}
}
}

View File

@@ -0,0 +1,286 @@
package com.xscm.moduleutil.widget;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import com.blankj.utilcode.util.ScreenUtils;
import com.xscm.moduleutil.utils.BarUtils;
/**
* 这是红包入口悬浮框
*/
public class DropRedView extends LinearLayout {
private int rightMargin = 0;
private float lastX, lastY;
private int screenWidth;
private int screenHeight; // 添加屏幕高度变量
public DropRedView(Context context) {
super(context);
init();
}
public DropRedView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DropRedView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
void init() {
// 初始化屏幕尺寸
screenWidth = ScreenUtils.getScreenWidth();
screenHeight = ScreenUtils.getScreenHeight();
post(new Runnable() {
@Override
public void run() {
//设置初始位置
int sh = ScreenUtils.getScreenHeight();
int sw = ScreenUtils.getScreenWidth();
// setBackgroundResource(R.drawable.bg_home_drop_view);
int y = (int) (0.5f * sh) - getHeight();
// 确保Y坐标不会超出屏幕范围
y = Math.max(0, Math.min(y, sh - getHeight()));
// int x = sw - getWidth();//这是靠右边展示的
int x=20 ;//这里这只一小的数值,就是靠左展示的
setTranslationX(x);
setTranslationY(y);
}
});
updateSize();
mStatusBarHeight = BarUtils.getStatusBarHeight();
}
/**
* 更新屏幕尺寸信息
*/
protected void updateSize() {
ViewGroup viewGroup = (ViewGroup) getParent();
if (viewGroup != null) {
mScreenWidth = viewGroup.getWidth();
mScreenHeight = viewGroup.getHeight();
} else {
// 如果父视图为空,使用屏幕的实际宽度和高度
mScreenWidth = getResources().getDisplayMetrics().widthPixels;
mScreenHeight = getResources().getDisplayMetrics().heightPixels;
}
}
boolean starDrap = false;
float X1;
float X2;
float Y1;
float Y2;
// 记录视图初始位置
private float originalX;
private float originalY;
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (starDrap) return true;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
X1 = event.getRawX();
Y1 = event.getRawY();
// 记录视图当前位置
originalX = getTranslationX();
originalY = getTranslationY();
break;
case MotionEvent.ACTION_MOVE:
X2 = event.getRawX();
Y2 = event.getRawY();
Action(X1, X2, Y1, Y2);
break;
}
return starDrap;
}
String TAG = "DropHourlView";
public boolean Action(float X1, float X2, float Y1, float Y2) {
float ComparedX = X2 - X1;//第二次的X坐标的位置减去第一次X坐标的位置代表X坐标上的变化情况
float ComparedY = Y2 - Y1;//同理
//当X坐标的变化量的绝对值大于Y坐标的变化量的绝对值以X坐标的变化情况作为判断依据
//上下左右的判断,都在一条直线上,但手指的操作不可能划直线,所有选择变化量大的方向上的量
//作为判断依据
if (Math.abs(ComparedX) > 30 || Math.abs(ComparedY) > 30) {
Log.i(TAG, "Action: 拖动");
starDrap = true;
// setBackgroundResource(R.drawable.bg_home_drop_view);
return true;
} else {
starDrap = false;
return false;
}
}
private float mOriginalRawX;
private float mOriginalRawY;
private float mOriginalX;
private float mOriginalY;
protected int mScreenWidth;
private int mScreenHeight;
private int mStatusBarHeight;
private void updateViewPosition(MotionEvent event) {
// 计算新的Y位置
float desY = mOriginalY + event.getRawY() - mOriginalRawY;
// 限制Y位置不超出屏幕边界
if (desY < mStatusBarHeight) {
desY = mStatusBarHeight;
}
if (desY > mScreenHeight - getHeight()) {
desY = mScreenHeight - getHeight();
}
// 计算新的X位置
float desX = mOriginalX + event.getRawX() - mOriginalRawX;
// 限制X位置不超出屏幕边界
if (desX < 0) {
desX = 0;
}
if (desX > mScreenWidth - getWidth()) {
desX = mScreenWidth - getWidth();
}
// 设置视图的新位置
setX(desX);
setY(desY);
}
private void changeOriginalTouchParams(MotionEvent event) {
mOriginalX = getX();//getX()相对于控件X坐标的距离
mOriginalY = getY();
mOriginalRawX = event.getRawX();//getRawX()指控件在屏幕上的X坐标
mOriginalRawY = event.getRawY();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event == null) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
changeOriginalTouchParams(event);
updateSize(); // 添加这行确保尺寸是最新的
// ... 其他现有代码 ...
break;
case MotionEvent.ACTION_MOVE:
updateViewPosition(event); // 使用更新后的带边界检查的方法
// setBackgroundResource(R.drawable.bg_home_drop_view);
// 使用屏幕绝对坐标计算新位置
// float newX = originalX + (event.getRawX() - X1);
// float newY = originalY + (event.getRawY() - Y1);
//
// // 限制X和Y坐标在屏幕范围内
// newX = Math.max(0, Math.min(newX, screenWidth - getWidth()));
// newY = Math.max(0, Math.min(newY, screenHeight - getHeight()));
//
// setTranslationX(newX);
// setTranslationY(newY);
// X2 = event.getRawX();
break;
case MotionEvent.ACTION_UP:
starDrap = false;
int sw = ScreenUtils.getScreenWidth();
Log.i(TAG, "onTouchEvent: " + sw + "," + X2);
boolean isR = getTranslationX() + getWidth() / 2 >= sw / 2;//贴边方向
// 获取当前Y坐标
float currentY = getTranslationY();
// 创建X轴和Y轴的动画
ObjectAnimator animX = ObjectAnimator.ofFloat(this, "translationX", isR ? sw - getWidth() : 0f).setDuration(200);
// Y轴保持当前位置但确保在屏幕范围内
currentY = Math.max(0, Math.min(currentY, screenHeight - getHeight()));
ObjectAnimator animY = ObjectAnimator.ofFloat(this, "translationY", currentY).setDuration(200);
animX.start();
animY.start();
break;
}
return true;
}
public void doRevealAnimation(View mPuppet, boolean flag) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
int[] vLocation = new int[2];
getLocationInWindow(vLocation);
int centerX = vLocation[0] + getMeasuredWidth() / 2;
int centerY = vLocation[1] + getMeasuredHeight() / 2;
int height = ScreenUtils.getScreenHeight();
int width = ScreenUtils.getScreenWidth();
int maxRradius = (int) Math.hypot(height, width);
Log.e("hei", maxRradius + "");
if (flag) {
mPuppet.setVisibility(VISIBLE);
Animator animator = ViewAnimationUtils.createCircularReveal(mPuppet, centerX, centerY, maxRradius, 0);
animator.setDuration(600);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mPuppet.setVisibility(View.GONE);
}
});
animator.start();
flag = false;
} else {
Animator animator = ViewAnimationUtils.createCircularReveal(mPuppet, centerX, centerY, 0, maxRradius);
animator.setDuration(1000);
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
mPuppet.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.start();
flag = true;
}
}
}
}

View File

@@ -0,0 +1,184 @@
package com.xscm.moduleutil.widget;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.widget.LinearLayout;
import com.blankj.utilcode.util.ScreenUtils;
public class DropViewRoom extends LinearLayout {
private int rightMargin = 0;
private float lastX, lastY;
private int screenWidth;
public DropViewRoom(Context context) {
super(context);
init();
}
public DropViewRoom(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DropViewRoom(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
void init() {
post(new Runnable() {
@Override
public void run() {
//设置初始位置
int sh = ScreenUtils.getScreenHeight();
int sw = ScreenUtils.getScreenWidth()-100;
// setBackgroundResource(R.drawable.bg_home_drop_view);
int y = (int) (0.5f * sh) - getHeight();
int x = sw - getWidth();
setTranslationX(x);
setTranslationY(y);
}
});
}
boolean starDrap = false;
float X1;
float X2;
float Y1;
float Y2;
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (starDrap) return true;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
X1 = event.getX();
Y1 = event.getY();
break;
case MotionEvent.ACTION_MOVE:
X2 = event.getX();//当手指抬起时再次获取屏幕位置的X值
Y2 = event.getY();//同理
Action(X1, X2, Y1, Y2);
break;
}
return starDrap;
}
String TAG = "DropView";
public boolean Action(float X1, float X2, float Y1, float Y2) {
float ComparedX = X2 - X1;//第二次的X坐标的位置减去第一次X坐标的位置代表X坐标上的变化情况
float ComparedY = Y2 - Y1;//同理
//当X坐标的变化量的绝对值大于Y坐标的变化量的绝对值以X坐标的变化情况作为判断依据
//上下左右的判断,都在一条直线上,但手指的操作不可能划直线,所有选择变化量大的方向上的量
//作为判断依据
if (Math.abs(ComparedX) > 30 || Math.abs(ComparedY) > 30) {
Log.i(TAG, "Action: 拖动");
starDrap = true;
// setBackgroundResource(R.drawable.bg_home_drop_view);
return true;
} else {
starDrap = false;
return false;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
// setBackgroundResource(R.drawable.bg_home_drop_view);
setTranslationX(getX() + (event.getX() - X1));
setTranslationY(getY() + (event.getY() - Y1));
X2 = event.getX();
break;
case MotionEvent.ACTION_UP:
starDrap = false;
int sw = ScreenUtils.getScreenWidth();
Log.i(TAG, "onTouchEvent: " + sw + "," + X2);
boolean isR = getTranslationX() + getWidth() / 2 >= sw / 2;//贴边方向
ObjectAnimator anim = ObjectAnimator.ofFloat(this, "translationX", isR ? sw - getWidth()+10 : 0f).setDuration(200);
anim.start();
break;
}
return true;
}
public void doRevealAnimation(View mPuppet, boolean flag) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
int[] vLocation = new int[2];
getLocationInWindow(vLocation);
int centerX = vLocation[0] + getMeasuredWidth() / 2;
int centerY = vLocation[1] + getMeasuredHeight() / 2;
int height = ScreenUtils.getScreenHeight();
int width = ScreenUtils.getScreenWidth();
int maxRradius = (int) Math.hypot(height, width);
Log.e("hei", maxRradius + "");
if (flag) {
mPuppet.setVisibility(VISIBLE);
Animator animator = ViewAnimationUtils.createCircularReveal(mPuppet, centerX, centerY, maxRradius, 0);
animator.setDuration(600);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mPuppet.setVisibility(View.GONE);
}
});
animator.start();
flag = false;
} else {
Animator animator = ViewAnimationUtils.createCircularReveal(mPuppet, centerX, centerY, 0, maxRradius);
animator.setDuration(1000);
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
mPuppet.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.start();
flag = true;
}
}
}
}

View File

@@ -0,0 +1,51 @@
package com.xscm.moduleutil.widget;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
import androidx.recyclerview.widget.RecyclerView;
/**
* @Author lxj$
* @Time $ 2025-9-4 20:32:52$
* @Description 自定义的让在item平分屏幕宽度$
*/
public class EqualSpaceItemDecoration extends RecyclerView.ItemDecoration {
private int spanCount;
private int spacing;
public EqualSpaceItemDecoration(int spanCount, int spacing) {
this.spanCount = spanCount;
this.spacing = spacing;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
// 设置间距
outRect.left = spacing / 2;
outRect.right = spacing / 2;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
// 确保每个item宽度为屏幕的1/3
int childCount = parent.getChildCount();
int screenWidth = parent.getWidth();
int itemWidth = screenWidth / spanCount;
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
ViewGroup.LayoutParams params = child.getLayoutParams();
params.width = itemWidth;
child.setLayoutParams(params);
}
}
}

View File

@@ -0,0 +1,96 @@
package com.xscm.modulemain
import android.graphics.*
import android.graphics.drawable.Drawable
/**
* 这是设置图片设置拉伸区域
*/
class FakeNinePatchDrawable(
private val bitmap: Bitmap,
// 定义拉伸区域的坐标(相对图片的百分比,范围 0~1
private val left: Float, // 左边界(左侧不拉伸区域的宽度比例)
private val top: Float, // 上边界(上侧不拉伸区域的高度比例)
private val right: Float, // 右边界(右侧不拉伸区域的宽度比例)
private val bottom: Float // 下边界(下侧不拉伸区域的高度比例)
) : Drawable() {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val rect = Rect() // 绘制区域
override fun draw(canvas: Canvas) {
val bounds = bounds // 视图的实际尺寸(需要填充的区域)
val w = bitmap.width
val h = bitmap.height
// 计算分割点(像素值)
val splitLeft = (w * left).toInt()
val splitTop = (h * top).toInt()
val splitRight = (w * right).toInt()
val splitBottom = (h * bottom).toInt()
// 1. 绘制四个角(不拉伸)
// 左上
rect.set(0, 0, splitLeft, splitTop)
canvas.drawBitmap(bitmap, Rect(0, 0, splitLeft, splitTop), rect, paint)
// 右上
rect.set(bounds.width() - (w - splitRight), 0, bounds.width(), splitTop)
canvas.drawBitmap(bitmap, Rect(splitRight, 0, w, splitTop), rect, paint)
// 左下
rect.set(0, bounds.height() - (h - splitBottom), splitLeft, bounds.height())
canvas.drawBitmap(bitmap, Rect(0, splitBottom, splitLeft, h), rect, paint)
// 右下
rect.set(
bounds.width() - (w - splitRight),
bounds.height() - (h - splitBottom),
bounds.width(),
bounds.height()
)
canvas.drawBitmap(bitmap, Rect(splitRight, splitBottom, w, h), rect, paint)
// 2. 绘制四条边(单向拉伸)
// 上边(水平拉伸)
rect.set(splitLeft, 0, bounds.width() - (w - splitRight), splitTop)
canvas.drawBitmap(bitmap, Rect(splitLeft, 0, splitRight, splitTop), rect, paint)
// 下边(水平拉伸)
rect.set(
splitLeft,
bounds.height() - (h - splitBottom),
bounds.width() - (w - splitRight),
bounds.height()
)
canvas.drawBitmap(bitmap, Rect(splitLeft, splitBottom, splitRight, h), rect, paint)
// 左边(垂直拉伸)
rect.set(0, splitTop, splitLeft, bounds.height() - (h - splitBottom))
canvas.drawBitmap(bitmap, Rect(0, splitTop, splitLeft, splitBottom), rect, paint)
// 右边(垂直拉伸)
rect.set(
bounds.width() - (w - splitRight),
splitTop,
bounds.width(),
bounds.height() - (h - splitBottom)
)
canvas.drawBitmap(bitmap, Rect(splitRight, splitTop, w, splitBottom), rect, paint)
// 3. 绘制中间区域(双向拉伸)
rect.set(
splitLeft,
splitTop,
bounds.width() - (w - splitRight),
bounds.height() - (h - splitBottom)
)
canvas.drawBitmap(bitmap, Rect(splitLeft, splitTop, splitRight, splitBottom), rect, paint)
}
override fun setAlpha(alpha: Int) {
paint.alpha = alpha
invalidateSelf()
}
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
override fun getAlpha(): Int = paint.alpha
override fun setColorFilter(colorFilter: ColorFilter?) {
paint.colorFilter = colorFilter
invalidateSelf()
}
}

View File

@@ -0,0 +1,513 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.blankj.utilcode.util.LogUtils;
import com.opensource.svgaplayer.SVGACallback;
import com.opensource.svgaplayer.SVGADrawable;
import com.opensource.svgaplayer.SVGAImageView;
import com.opensource.svgaplayer.SVGAParser;
import com.opensource.svgaplayer.SVGAVideoEntity;
import com.tencent.qgame.animplayer.AnimConfig;
import com.tencent.qgame.animplayer.AnimView;
import com.tencent.qgame.animplayer.inter.IAnimListener;
import com.xscm.moduleutil.bean.GiftBean;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class GiftAnimView extends FrameLayout implements GiftSvgaView.OnAnimationListener {
private AnimView playerMp4View;
private GiftSvgaView svgaView;
private GiftBean playModel;
private boolean isLoadEffect = false;
private boolean isShow = true;//是否开启特效
private ReentrantLock lock = new ReentrantLock();
private List<String> giftArray = new ArrayList<>();
public ExecutorService queue = Executors.newSingleThreadExecutor();
private Context mContext;
private boolean isOnece;
// 添加带Context参数的构造函数
// 添加带Context和AttributeSet参数的构造函数解决XML inflate问题的关键
public GiftAnimView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
init();
}
// 添加带Context、AttributeSet和 defStyleAttr 参数的构造函数(更完整的实现)
public GiftAnimView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
init();
}
public void setQueue(ExecutorService queue) {
this.queue = queue;
}
public GiftAnimView(Context context) {
super(context);
this.mContext = context;
init();
}
private void init() {
isLoadEffect = false;
// 初始化SVGA视图
svgaView = new GiftSvgaView(getContext());
addView(svgaView);
playerMp4View = new AnimView(getContext());
addView(playerMp4View);
// 设置布局参数 - 在Android中通常使用LayoutParams
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
playerMp4View.setLoop(1);
playerMp4View.setLayoutParams(params);
playerMp4View.setAnimListener(new IAnimListener() {
@Override
public void onVideoDestroy() {
LogUtils.e("onVideoDestroy");
}
@Override
public void onVideoComplete() {
LogUtils.e("onVideoComplete");
post(() -> {
if (playerMp4View!=null) {
playerMp4View.setVisibility(View.GONE);
if (isOnece){
return;
}
// 通知播放完成
notifyPlaybackComplete();
loadStartSVGAPlayer();
}
});
}
@Override
public void onVideoRender(int i, @Nullable AnimConfig animConfig) {
// LogUtils.e("onVideoRender", i, animConfig);
}
@Override
public void onVideoStart() {
LogUtils.e("onVideoStart");
}
@Override
public boolean onVideoConfigReady(@NonNull AnimConfig animConfig) {
LogUtils.e("onVideoConfigReady", animConfig);
return true;
}
@Override
public void onFailed(int i, @Nullable String s) {
LogUtils.e("onFailed", s);
}
});
svgaView.setDidFinishedDisplay(this);
svgaView.setDidStartAnimation(this);
}
public void displayEffectView1(List<String> stringList){
queue.execute(new Runnable() {
@Override
public void run() {
/// 如果play_image不存在return
if (stringList == null || stringList.isEmpty()) {
return;
}
/// 锁住list
lock.lock();
try {
/// 添加礼物进list
giftArray.addAll(stringList);
/// 如果没有在加载则开始加载
if (!isLoadEffect) {
/// 更改加载状态标记
isLoadEffect = true;
loadStartSVGAPlayer();
}
} finally {
/// 解锁
lock.unlock();
}
}
});
}
/// 礼物特效进来 gift为礼物实体类
public void displayEffectView(final String gift) {
queue.execute(new Runnable() {
@Override
public void run() {
/// 如果play_image不存在return
if (gift == null || gift.isEmpty()) {
return;
}
/// 将play_image链接转为小写
String playImage = gift;
String pathExtension = getFileExtension(playImage).toLowerCase();
/// 判定礼物的后缀是否为svga或者mp4如果非这两种 则return
if (!("svga".equals(pathExtension) || "mp4".equals(pathExtension))) {
return;
}
/// 锁住list
lock.lock();
try {
/// 添加礼物进list
giftArray.add(gift);
/// 如果没有在加载则开始加载
if (!isLoadEffect) {
/// 更改加载状态标记
isLoadEffect = true;
loadStartSVGAPlayer();
}
} finally {
/// 解锁
lock.unlock();
}
}
});
}
public void previewEffectWith(String playImage){
this.isOnece=true;
if (playImage.endsWith("mp4")) {
downloadAndPlay(getContext(), playImage, new DownloadCallback() {
@Override
public void onSuccess(File file) {
post(() -> {
playerMp4View.setVisibility(View.VISIBLE);
svgaView.setVisibility(View.GONE);
playerMp4View.startPlay(file);
});
}
@Override
public void onFailure(Exception e) {
LogUtils.e("MP4下载或播放失败: " + e.getMessage());
// 处理失败情况,继续播放下一个
}
});
} else if (playImage.endsWith("svga")) {
// File file = downloadAndPlay(getContext(), playImage);
post(() -> {
playerMp4View.setVisibility(View.GONE);
svgaView.setVisibility(View.VISIBLE);
svgaView.loadSVGAPlayerWith(playImage, false);
});
}
}
public void openOrCloseEffectViewWith(boolean isShow) {
this.isShow = isShow;
removeSvgaQueueData();
playerMp4View.stopPlay();
playerMp4View.setVisibility(View.GONE);
setVisibility(isShow ? View.VISIBLE : View.GONE);
}
public void stopPlay() {
removeSvgaQueueData();
playerMp4View.stopPlay();
playerMp4View.setVisibility(View.GONE);
}
private void loadStartSVGAPlayer() {
if (!isShow) {
/// isshow 为是否开启特效 如果未开启则return
return;
}
String giftModel = null;
/// list加锁
lock.lock();
try {
/// 如果list长度大于0
if (!giftArray.isEmpty()) {
/// gift的实体类则赋值取list中的第一个数据
giftModel = giftArray.get(0);
/// 移除list的第一条数据
giftArray.remove(0);
isLoadEffect = true;
} else {
isLoadEffect = false;
}
} finally {
/// 解锁
lock.unlock();
}
if (isLoadEffect && giftModel != null && !TextUtils.isEmpty(giftModel)) {
String finalGiftModel = giftModel;
post(new Runnable() {
@Override
public void run() {
String playImage = finalGiftModel;
if (playImage.endsWith("mp4")) {
downloadAndPlay(getContext(), playImage, new DownloadCallback() {
@Override
public void onSuccess(File file) {
post(() -> {
playerMp4View.setVisibility(View.VISIBLE);
svgaView.setVisibility(View.GONE);
playerMp4View.startPlay(file);
});
}
@Override
public void onFailure(Exception e) {
LogUtils.e("MP4下载或播放失败: " + e.getMessage());
// 处理失败情况,继续播放下一个
post(() -> {
lock.lock();
try {
isLoadEffect = false;
} finally {
lock.unlock();
}
loadStartSVGAPlayer();
});
}
});
} else if (playImage.endsWith("svga")) {
// File file = downloadAndPlay(getContext(), playImage);
post(() -> {
playerMp4View.setVisibility(View.GONE);
svgaView.setVisibility(View.VISIBLE);
svgaView.loadSVGAPlayerWith(finalGiftModel, false);
});
} else {
lock.lock();
try {
isLoadEffect = false;
} finally {
lock.unlock();
}
loadStartSVGAPlayer();
// 直接播放缓存文件
}
}
});
}
}
public void downloadAndPlay(Context context, String playImage, DownloadCallback callback) {
String fileName = playImage.substring(playImage.lastIndexOf("/"));
String filePath = context.getCacheDir().getAbsolutePath() + fileName;
LogUtils.e("@@@@@filePath: " + filePath.toString());
File file = new File(filePath);
if (!file.exists()) {
LogUtils.e("无缓存");
// 使用OkHttp进行下载
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(playImage)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
LogUtils.e("MP4下载失败: " + e.toString());
// 在主线程中回调失败
post(() -> {
if (callback != null) {
callback.onFailure(e);
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
try (ResponseBody responseBody = response.body()) {
if (responseBody != null) {
File downloadedFile = new File(filePath);
FileOutputStream fos = new FileOutputStream(downloadedFile);
fos.write(responseBody.bytes());
fos.close();
// 在主线程中回调成功
post(() -> {
if (callback != null) {
callback.onSuccess(downloadedFile);
}
});
} else {
// 在主线程中回调失败
post(() -> {
if (callback != null) {
callback.onFailure(new IOException("Response body is null"));
}
});
}
} catch (Exception e) {
LogUtils.e("MP4文件保存失败: " + e.getMessage());
// 在主线程中回调失败
post(() -> {
if (callback != null) {
callback.onFailure(e);
}
});
}
} else {
LogUtils.e("MP4下载响应失败");
// 在主线程中回调失败
post(() -> {
if (callback != null) {
callback.onFailure(new IOException("Response not successful: " + response.code()));
}
});
}
}
});
} else {
// 文件已存在,直接回调成功
if (callback != null) {
callback.onSuccess(file);
}
}
}
// 添加回调接口
public interface DownloadCallback {
void onSuccess(File file);
void onFailure(Exception e);
}
public void destroyEffectView() {
removeSvgaQueueData();
svgaView.stopEffectSvgaPlay();
playerMp4View.stopPlay();
// 清理监听器
clearPlaybackCompleteListeners();
removeView(playerMp4View);
removeView(svgaView);
svgaView = null;
playerMp4View = null;
playModel = null;
if (queue != null) {
queue.shutdown();
queue = null;
}
}
private void removeSvgaQueueData() {
lock.lock();
try {
giftArray.clear();
isLoadEffect = false;
} finally {
lock.unlock();
}
}
private String getFileExtension(String url) {
if (url != null && url.contains(".")) {
return url.substring(url.lastIndexOf(".") + 1);
}
return "";
}
@Override
public void onStartAnimation(GiftSvgaView view) {
}
@Override
public void onFinishedDisplay(GiftSvgaView view) {
post(() -> {
if (svgaView!=null) {
svgaView.setVisibility(View.GONE);
}
if (isOnece){
return;
}
// 通知播放完成
notifyPlaybackComplete();
loadStartSVGAPlayer();
});
}
// 在 GiftAnimView 类中添加播放完成监听接口
public interface OnPlaybackCompleteListener {
void onPlaybackComplete();
}
// 在 GiftAnimView 类中添加成员变量
private List<OnPlaybackCompleteListener> playbackCompleteListeners = new ArrayList<>();
// 在 GiftAnimView 类中添加监听器管理方法
public void addOnPlaybackCompleteListener(OnPlaybackCompleteListener listener) {
if (listener != null && !playbackCompleteListeners.contains(listener)) {
playbackCompleteListeners.add(listener);
}
}
public void removeOnPlaybackCompleteListener(OnPlaybackCompleteListener listener) {
if (listener != null) {
playbackCompleteListeners.remove(listener);
}
}
public void clearPlaybackCompleteListeners() {
playbackCompleteListeners.clear();
}
// 触发播放完成回调的方法
private void notifyPlaybackComplete() {
for (OnPlaybackCompleteListener listener : new ArrayList<>(playbackCompleteListeners)) {
if (listener != null) {
listener.onPlaybackComplete();
}
}
}
}

View File

@@ -0,0 +1,306 @@
package com.xscm.moduleutil.widget;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import com.google.android.material.card.MaterialCardView;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.GiftBean;
import com.xscm.moduleutil.utils.ImageUtils;
/**
* 礼物卡片控件 - 支持选中效果和自定义样式
*/
public class GiftCardView extends FrameLayout {
private ImageView mIconImageView;
private TextView mNameTextView;
private TextView mCountTextView;
private TextView mResultTextView;
private boolean isSelected = false;
private Drawable selectedBackground;
private Drawable normalBackground;
// 添加GiftBean数据引用
private GiftBean giftBean;
private ObjectAnimator pulseAnimator;
public GiftCardView(Context context) {
this(context, null);
}
public GiftCardView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GiftCardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initViews();
initAttributes(attrs);
}
private void initViews() {
LayoutInflater.from(getContext()).inflate(R.layout.view_gift_card, this, true);
mIconImageView = findViewById(R.id.img_gift_icon);
mNameTextView = findViewById(R.id.tv_gift_name);
mCountTextView = findViewById(R.id.tv_gift_count);
mResultTextView= findViewById(R.id.result);
// 设置默认点击事件
setOnClickListener(v -> {
if (getOnGiftClickListener() != null) {
getOnGiftClickListener().onGiftClick(this);
}
});
}
private void initAttributes(AttributeSet attrs) {
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.GiftCardView);
try {
// 获取自定义属性
String name = typedArray.getString(R.styleable.GiftCardView_giftName);
String count = typedArray.getString(R.styleable.GiftCardView_giftCount);
int iconResId = typedArray.getResourceId(R.styleable.GiftCardView_giftIcon, 0);
// 设置默认值
if (name != null) {
setName(name);
}
if (count != null) {
setCount(count);
}
if (iconResId != 0) {
setIcon("");
}
// 获取选中状态相关属性
selectedBackground = typedArray.getDrawable(R.styleable.GiftCardView_selectedBackground);
normalBackground = typedArray.getDrawable(R.styleable.GiftCardView_normalBackground);
// 如果没有设置选中背景,则使用默认的选中效果
// if (selectedBackground == null) {
// selectedBackground = ContextCompat.getDrawable(getContext(), R.mipmap.tkzj_x);
// }
// if (normalBackground == null) {
// normalBackground = ContextCompat.getDrawable(getContext(), R.mipmap.tkzj_w);
// }
// 设置默认背景
if (normalBackground != null) {
setBackground(normalBackground);
}
} finally {
typedArray.recycle();
}
}
/**
* 绑定GiftBean数据
*/
public void bindGiftData(GiftBean giftBean) {
this.giftBean = giftBean;
if (giftBean != null) {
// 设置礼物图标
if (giftBean.getBase_image() != null && !giftBean.getBase_image().isEmpty()) {
setIcon(giftBean.getBase_image());
// 如果是资源ID
} else {
setIcon("");
}
// 设置礼物名称
setName(giftBean.getGift_name() != null ? giftBean.getGift_name() : "未知礼物");
// 设置礼物数量
setCount(giftBean.getGift_price() != null ? giftBean.getGift_price() : "0");
setmResultTextView(giftBean.getCount());
}
}
/**
* 获取绑定的GiftBean数据
*/
public GiftBean getGiftBean() {
return giftBean;
}
/**
* 设置礼物图标URL
*/
public void setIcon(String url) {
if (mIconImageView != null) {
if (url != null && !url.isEmpty()) {
ImageUtils.loadHeadCC(url, mIconImageView);
} else {
mIconImageView.setImageResource(R.mipmap.ic_launcher);
}
}
}
private android.animation.AnimatorSet animatorSet;
public void startPulseAnimationWithLayer() {
if (pulseAnimator != null && pulseAnimator.isRunning()) {
pulseAnimator.cancel();
}
pulseAnimator = ObjectAnimator.ofFloat(this, "scaleX", 0.9f, 1.1f);
pulseAnimator.setDuration(500);
pulseAnimator.setRepeatCount(ObjectAnimator.INFINITE);
pulseAnimator.setRepeatMode(ObjectAnimator.REVERSE);
ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(this, "scaleY", 0.9f, 1.1f);
scaleYAnimator.setDuration(500);
scaleYAnimator.setRepeatCount(ObjectAnimator.INFINITE);
scaleYAnimator.setRepeatMode(ObjectAnimator.REVERSE);
// 组合动画
animatorSet = new android.animation.AnimatorSet();
animatorSet.playTogether(pulseAnimator, scaleYAnimator);
animatorSet.start();
}
public void stopPulseAnimationWithLayer() {
if (pulseAnimator != null) {
pulseAnimator.cancel();
}
// 停止组合动画
if (animatorSet != null && animatorSet.isRunning()) {
animatorSet.cancel();
}
// 清除引用
animatorSet = null;
pulseAnimator = null;
// 重置缩放
this.setScaleX(1f);
this.setScaleY(1f);
// 强制重新绘制
this.invalidate();
}
/**
* 设置礼物名称
*/
public void setName(String name) {
if (mNameTextView != null) {
mNameTextView.setText(name);
}
}
/**
* 设置礼物数量
*/
public void setCount(String count) {
if (mCountTextView != null) {
mCountTextView.setText(count);
}
}
/**
* 设置选中状态
*/
public void setSelected(boolean selected) {
isSelected = selected;
if (selected) {
setBackground(selectedBackground);
// 可以添加额外的选中效果
// setElevation(8f); // 提高阴影
// 添加发光效果
// addGlowEffect();
} else {
setBackground(normalBackground);
// setElevation(4f); // 恢复默认阴影
// removeGlowEffect();
}
}
public void setmResultTextView(int num){
if (mResultTextView!=null) {
mResultTextView.setText("x" + num);
}
}
public void setVisibilitymResultTextView(boolean isVisible){
if (mResultTextView!=null) {
mResultTextView.setVisibility(isVisible?View.VISIBLE:View.GONE);
}
}
/**
* 添加发光效果
*/
private void addGlowEffect() {
// 使用LayerDrawable创建发光效果
GradientDrawable glow = new GradientDrawable();
glow.setColor(Color.TRANSPARENT);
glow.setStroke(8, Color.argb(150, 255, 255, 255));
glow.setCornerRadius(16f);
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{glow, getBackground()});
setBackground(layerDrawable);
}
/**
* 移除发光效果
*/
private void removeGlowEffect() {
setBackground(normalBackground);
}
/**
* 获取当前是否选中
*/
public boolean isSelected() {
return isSelected;
}
/**
* 切换选中状态
*/
public void toggleSelected() {
setSelected(!isSelected);
}
/**
* 设置点击监听器
*/
public void setOnGiftClickListener(OnGiftClickListener listener) {
this.onGiftClickListener = listener;
}
/**
* 获取点击监听器
*/
public OnGiftClickListener getOnGiftClickListener() {
return onGiftClickListener;
}
/**
* 点击监听器接口
*/
public interface OnGiftClickListener {
void onGiftClick(GiftCardView giftCardView);
}
private OnGiftClickListener onGiftClickListener;
}

View File

@@ -0,0 +1,285 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.opensource.svgaplayer.SVGACallback;
import com.opensource.svgaplayer.SVGADrawable;
import com.opensource.svgaplayer.SVGAImageView;
import com.opensource.svgaplayer.SVGAParser;
import com.opensource.svgaplayer.SVGAVideoEntity;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
public class GiftSvgaView extends FrameLayout implements SVGACallback {
private SVGAImageView player;
private SVGAParser parser;
private boolean isAutoPlay = true;
// 回调接口
public interface OnAnimationListener {
void onStartAnimation(GiftSvgaView view);
void onFinishedDisplay(GiftSvgaView view);
}
private OnAnimationListener didStartAnimation;
private OnAnimationListener didFinishedDisplay;
public GiftSvgaView(Context context) {
this(context, null);
}
public GiftSvgaView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GiftSvgaView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initializeData(true);
}
public GiftSvgaView(Context context, boolean isAutoPlay) {
super(context);
initializeData(isAutoPlay);
}
private void initializeData(boolean isAutoPlay) {
this.isAutoPlay = isAutoPlay;
setBackgroundColor(Color.TRANSPARENT); // clearColor
setClickable(false); // userInteractionEnabled = NO
initPlayer();
}
private void initPlayer() {
player = new SVGAImageView(getContext());
player.setBackgroundColor(Color.TRANSPARENT);
player.setScaleType(ImageView.ScaleType.CENTER_CROP); // UIViewContentModeScaleAspectFill
// 如果需要 ScaleAspectFit使用 ImageView.ScaleType.FIT_CENTER
// 设置布局参数 - 填满父视图
LayoutParams params = new LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT
);
addView(player, params);
// 初始化解析器
parser = new SVGAParser(getContext());
player.setCallback(this);
}
public void loadSVGAPlayerWith(String loadPath) {
loadSVGAPlayerWith(loadPath, false);
}
public void loadSVGAPlayerWith(String loadPath, boolean inBundle) {
loadSVGAPlayerWith(loadPath, inBundle, 1);
}
public void loadSVGAPlayerWith(String loadPath, boolean inBundle, int loop) {
if (loadPath == null || loadPath.isEmpty()) {
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(this);
}
return;
}
if (player == null) {
initPlayer();
}
stopEffectSvgaPlay();
player.setLoops(loop);
if (loadPath.startsWith("https:") || loadPath.startsWith("http:")) {
// URL加载
try {
parser.parse(new URL(loadPath), new SVGAParser.ParseCompletion() {
@Override
public void onComplete(@NotNull SVGAVideoEntity videoItem) {
SVGADrawable drawable = new SVGADrawable(videoItem);
player.setImageDrawable(drawable);
if (isAutoPlay) {
player.startAnimation();
if (didStartAnimation != null) {
didStartAnimation.onStartAnimation(GiftSvgaView.this);
}
}
}
@Override
public void onError() {
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(GiftSvgaView.this);
}
}
});
} catch (Exception e) {
e.printStackTrace();
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(this);
}
}
} else if (inBundle) {
// 从Assets加载
try {
parser.parse(loadPath, new SVGAParser.ParseCompletion() {
@Override
public void onComplete(@NotNull SVGAVideoEntity videoItem) {
SVGADrawable drawable = new SVGADrawable(videoItem);
player.setImageDrawable(drawable);
if (isAutoPlay) {
player.startAnimation();
if (didStartAnimation != null) {
didStartAnimation.onStartAnimation(GiftSvgaView.this);
}
}
}
@Override
public void onError() {
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(GiftSvgaView.this);
}
}
});
} catch (Exception e) {
e.printStackTrace();
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(this);
}
}
} else {
// 从文件路径加载
try {
File file = new File(loadPath);
if (!file.exists() || file.length() < 4) {
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(this);
}
return;
}
InputStream inputStream = new FileInputStream(file);
parser.parse(inputStream, loadPath, new SVGAParser.ParseCompletion() {
@Override
public void onComplete(@NotNull SVGAVideoEntity videoItem) {
SVGADrawable drawable = new SVGADrawable(videoItem);
player.setImageDrawable(drawable);
if (isAutoPlay) {
player.startAnimation();
if (didStartAnimation != null) {
didStartAnimation.onStartAnimation(GiftSvgaView.this);
}
}
}
@Override
public void onError() {
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(GiftSvgaView.this);
}
}
}, true);
} catch (IOException e) {
e.printStackTrace();
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(this);
}
}
}
}
// Public方法
public void startEffectSvgaPlay() {
if (player != null && player.getDrawable() != null) {
player.startAnimation();
}
}
public void pauseEffectSvgaPlay() {
if (player != null) {
player.pauseAnimation();
}
}
public void stopEffectSvgaPlay() {
if (player != null) {
player.stopAnimation();
player.clearAnimation();
player.setImageDrawable( null);
}
}
public void destroySvga() {
stopEffectSvgaPlay();
if (player != null) {
removeView(player);
player = null;
}
parser = null;
}
// SVGACallback接口实现
@Override
public void onPause() {
// 暂停回调
}
@Override
public void onFinished() {
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(this);
}
}
@Override
public void onRepeat() {
// 重复回调
}
@Override
public void onStep(int frame, double percentage) {
// 步骤回调
}
// Getter和Setter方法
public void setDidStartAnimation(OnAnimationListener listener) {
this.didStartAnimation = listener;
}
public void setDidFinishedDisplay(OnAnimationListener listener) {
this.didFinishedDisplay = listener;
if (player != null) {
player.setCallback(this);
}
}
public boolean isAutoPlay() {
return isAutoPlay;
}
public void setAutoPlay(boolean autoPlay) {
isAutoPlay = autoPlay;
}
public SVGAImageView getPlayer() {
return player;
}
public SVGAParser getParser() {
return parser;
}
}

View File

@@ -0,0 +1,273 @@
package com.xscm.moduleutil.widget;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.ImageView;
import android.widget.TextView;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.event.MqttBean;
import java.util.ArrayList;
import java.util.List;
public class QXGiftDriftView extends ViewGroup {
private static QXGiftDriftView instance;
private ImageView bgImageView;
private TextView titleLabel;
private ImageView giftImageView;
private TextView countLabel;
private boolean isPlaying=false;
private boolean isClose;
private List<MqttBean.ListBean> dataArray = new ArrayList<>();
private MqttBean.ListBean model;
private Context context;
private int screenWidth;
public static QXGiftDriftView getInstance(Context context) {
if (instance == null) {
instance = new QXGiftDriftView(context);
}
return instance;
}
private QXGiftDriftView(Context context) {
super(context);
this.context = context;
// 获取屏幕宽度
screenWidth = context.getResources().getDisplayMetrics().widthPixels;
// 初始化视图
initSubviews();
// 从SharedPreferences读取设置
SharedPreferences prefs = context.getSharedPreferences("AppPrefs", Context.MODE_PRIVATE);
isClose = prefs.getBoolean("kIsCloseDrifPop", false);
}
private void initSubviews() {
// 设置视图尺寸
int width = scaleWidth(316);
int height = scaleWidth(50);
setLayoutParams(new LayoutParams(width, height));
// 背景图片
bgImageView = new ImageView(context);
bgImageView.setImageResource(R.mipmap.gift_p_b);
addView(bgImageView);
// 标题标签
titleLabel = new TextView(context);
titleLabel.setTextSize(14);
titleLabel.setTextColor(Color.WHITE);
addView(titleLabel);
// 礼物图片
giftImageView = new ImageView(context);
giftImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
addView(giftImageView);
// 数量标签
countLabel = new TextView(context);
countLabel.setTextSize(14);
countLabel.setTextColor(Color.WHITE);
addView(countLabel);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// 布局子视图
int width = getWidth();
int height = getHeight();
// 背景图片填满整个视图
bgImageView.layout(0, 0, width, height);
// 标题标签居中偏左
int titleWidth = titleLabel.getMeasuredWidth();
int titleHeight = titleLabel.getMeasuredHeight();
int titleLeft = (width - titleWidth) / 2 - scaleWidth(30);
int titleTop = (height - titleHeight) / 2;
titleLabel.layout(titleLeft, titleTop, titleLeft + titleWidth, titleTop + titleHeight);
// 礼物图片在标题右侧
int giftSize = scaleWidth(20);
int giftLeft = titleLeft + titleWidth + scaleWidth(5);
int giftTop = titleTop + (titleHeight - giftSize) / 2;
giftImageView.layout(giftLeft, giftTop, giftLeft + giftSize, giftTop + giftSize);
// 数量标签在礼物图片右侧
int countWidth = countLabel.getMeasuredWidth();
int countLeft = giftLeft + giftSize;
countLabel.layout(countLeft, titleTop, countLeft + countWidth, titleTop + titleHeight);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = scaleWidth(316);
int height = scaleWidth(50);
setMeasuredDimension(width, height);
// 测量子视图
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
public void addGiftModel(MqttBean.ListBean model) {
if (isClose) {
return;
}
dataArray.add(model);
giftAction();
}
public void addGiftModelList(List<MqttBean.ListBean> list) {
dataArray.addAll(list);
giftAction();
}
private void giftAction() {
if (isPlaying) {
return;
}
if (dataArray.isEmpty()) {
return;
}
isPlaying = true;
model = dataArray.get(0);
// 添加到窗口(这里需要根据实际情况获取根布局)
ViewGroup rootView = (ViewGroup) ((Activity) context).getWindow().getDecorView();
rootView.addView(QXGiftDriftView.getInstance( context));
// 设置初始位置(屏幕右侧外)
setX(screenWidth);
// 进入动画
TranslateAnimation enterAnim = new TranslateAnimation(
Animation.ABSOLUTE, screenWidth,
Animation.ABSOLUTE, (screenWidth - scaleWidth(316)) / 2,
Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, 0
);
enterAnim.setDuration(1500);
enterAnim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
// 停留后退出
postDelayed(() -> {
TranslateAnimation exitAnim = new TranslateAnimation(
Animation.ABSOLUTE, (screenWidth - scaleWidth(316)) / 2,
Animation.ABSOLUTE, -screenWidth,
Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, 0
);
exitAnim.setDuration(2000);
exitAnim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
// 移除视图并处理下一个
ViewGroup rootView = (ViewGroup) getParent();
if (rootView != null) {
rootView.removeView(QXGiftDriftView.getInstance( context));
}
if (!dataArray.isEmpty()) {
dataArray.remove(0);
}
isPlaying = false;
if (!dataArray.isEmpty()) {
giftAction();
}
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
startAnimation(exitAnim);
}, 1000); // 停留1秒
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
startAnimation(enterAnim);
}
public void setModel(MqttBean.ListBean model) {
this.model = model;
String text = String.format("%s送给%s", model.getFromUserName(), model.getToUserName());
// 这里需要使用SpannableString来实现富文本效果
// 简化处理,直接设置文本
titleLabel.setText(text);
// 加载图片 - 使用图片加载库如Glide
// Glide.with(context).load(model.getGiftPicture()).into(giftImageView);
countLabel.setText(String.format("X%s", model.getNumber()));
}
public void drifPopIsClose(boolean isClose) {
this.isClose = isClose;
setVisibility(isClose ? View.GONE : View.VISIBLE);
SharedPreferences prefs = context.getSharedPreferences("AppPrefs", Context.MODE_PRIVATE);
prefs.edit().putBoolean("kIsCloseDrifPop", isClose).apply();
if (isClose) {
ViewGroup rootView = (ViewGroup) getParent();
if (rootView != null) {
rootView.removeView(this);
}
dataArray.clear();
isPlaying = false;
}
}
private int scaleWidth(int width) {
// 根据屏幕密度进行缩放
float density = context.getResources().getDisplayMetrics().density;
return (int) (width * density);
}
// QXGiftScrollModel 类(需要单独定义)
public static class QXGiftScrollModel {
private String fromUserName;
private String toUserName;
private String giftPicture;
private String number;
// getters and setters
public String getFromUserName() { return fromUserName; }
public void setFromUserName(String fromUserName) { this.fromUserName = fromUserName; }
public String getToUserName() { return toUserName; }
public void setToUserName(String toUserName) { this.toUserName = toUserName; }
public String getGiftPicture() { return giftPicture; }
public void setGiftPicture(String giftPicture) { this.giftPicture = giftPicture; }
public String getNumber() { return number; }
public void setNumber(String number) { this.number = number; }
}
}

View File

@@ -0,0 +1,144 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.xscm.moduleutil.bean.GiftBean;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class QXGiftPlayerManager {
private static QXGiftPlayerManager instance;
private View bgEffectView;
private GiftAnimView fullEffectView;
private GiftAnimView chatEffectView;
private Context context;
private QXGiftPlayerManager(Context context) {
this.context = context.getApplicationContext();
}
public static synchronized QXGiftPlayerManager getInstance(Context context) {
if (instance == null) {
instance = new QXGiftPlayerManager(context);
}
return instance;
}
public View getDefaultBgEffectView() {
if (bgEffectView == null) {
initBgEffectView();
}
return bgEffectView;
}
public GiftAnimView getDefaultFullEffectView() {
if (fullEffectView == null) {
initFullEffectView();
}
return fullEffectView;
}
public GiftAnimView getDefaultChatEffectView() {
if (chatEffectView == null) {
initChatEffectView();
}
return chatEffectView;
}
private void initBgEffectView() {
bgEffectView = new FrameLayout(context);
bgEffectView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
));
bgEffectView.setBackgroundColor(0x00000000);
bgEffectView.setClickable(false); // userInteractionEnabled = NO
// 添加全屏特效视图和聊天特效视图
((ViewGroup) bgEffectView).addView(getDefaultFullEffectView());
((ViewGroup) bgEffectView).addView(getDefaultChatEffectView());
}
public void displayFullEffectView(String gift) {
getDefaultFullEffectView().displayEffectView(gift);
}
public void displayFullEffectView1(List<String> stringList){
getDefaultFullEffectView().displayEffectView1(stringList);
}
public void displayChatEffectView(String gift) {
getDefaultChatEffectView().displayEffectView(gift);
}
public void openOrCloseEffectViewWith(boolean isShow) {
getDefaultFullEffectView().openOrCloseEffectViewWith(isShow);
getDefaultChatEffectView().openOrCloseEffectViewWith(isShow);
}
public void destroyEffectSvga() {
if (fullEffectView != null) {
fullEffectView.destroyEffectView();
if (bgEffectView != null) {
((ViewGroup) bgEffectView).removeView(fullEffectView);
}
fullEffectView = null;
}
if (chatEffectView != null) {
chatEffectView.destroyEffectView();
if (bgEffectView != null) {
((ViewGroup) bgEffectView).removeView(chatEffectView);
}
chatEffectView = null;
}
if (bgEffectView != null) {
bgEffectView = null;
}
}
public void stopPlay() {
if (fullEffectView != null) {
fullEffectView.stopPlay();
}
if (chatEffectView != null) {
chatEffectView.stopPlay();
}
}
private void initFullEffectView() {
fullEffectView = new GiftAnimView(context);
fullEffectView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
));
// 创建专用线程池替代GCD队列
ExecutorService queue = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
Executors.defaultThreadFactory());
fullEffectView.setQueue(queue);
}
private void initChatEffectView() {
chatEffectView = new GiftAnimView(context);
chatEffectView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
));
// 创建专用线程池替代GCD队列
ExecutorService queue = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
Executors.defaultThreadFactory());
chatEffectView.setQueue(queue);
}
}

View File

@@ -0,0 +1,169 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.opensource.svgaplayer.SVGAImageView;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.room.RoomPitBean;
import com.xscm.moduleutil.utils.ImageUtils;
public class RoomSingWheatView extends LinearLayout {
public ImageView mRiv;
public ImageView mIvGift;
public WheatCharmView mCharmView;
public TextView mTvName;
public ImageView mIvSex;
public AvatarFrameView mIvFrame;
public AvatarFrameView mIvRipple;
public ExpressionImgView mIvFace;
public ImageView mIvShutup;
public TextView tvTime;
public TextView mTvNo;
public TextView tv_time_pk;
public RoomPitBean pitBean;//麦位数据
public String roomId;//房间id
public static final String WHEAT_BOSS = "8";//老板位
public static final String WHEAT_HOST = "9";//主持位
public float oX;
public float oY;
boolean closePhone = false;//自己麦位关闭话筒,用于判断声纹显示
public String pitNumber;
public int pitImageVId;
// public ImageView iv_on_line;
private boolean showGiftAnim = true;//显示麦位动画
private ImageView iv_tag_type;
private TextView tv_zhul;
public RoomSingWheatView(@NonNull Context context) {
this(context, null);
}
public RoomSingWheatView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RoomSingWheatView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context) {
// 确保布局被正确加载
inflate(context, getLayoutId(), this);
// 初始化所有视图组件
mRiv = findViewById(R.id.riv);
mIvGift = findViewById(R.id.iv_gift);
mCharmView = findViewById(R.id.charm_view);
mTvName = findViewById(R.id.tv_name);
mIvSex = findViewById(R.id.iv_sex);
mIvFrame = findViewById(R.id.iv_frame);
mIvRipple = findViewById(R.id.iv_ripple);
mIvFace = findViewById(R.id.iv_face);
mIvShutup = findViewById(R.id.iv_shutup);
tvTime = findViewById(R.id.tv_time);
tv_time_pk = findViewById(R.id.tv_time_pk);
mTvNo = findViewById(R.id.tv_no);
// iv_on_line = findViewById(R.id.iv_online);
iv_tag_type = findViewById(R.id.iv_tag_type);
tv_zhul = findViewById(R.id.tv_zhul);
// 设置初始位置
if (mIvGift != null) {
oX = mIvGift.getX();
oY = mIvGift.getY();
}
}
protected int getLayoutId() {
return R.layout.room_view_sing_wheat;
}
public void setData(RoomPitBean bean) {
this.pitBean = bean;
if (bean == null) return;
// 添加空值检查防止NPE
if (mTvName == null) {
// 可能布局未正确加载,尝试重新初始化
initView(getContext());
if (mTvName == null) {
// 如果仍然为null记录日志并返回
android.util.Log.e("RoomSingWheatView", "mTvName is still null after re-initialization");
return;
}
}
if (isOn()) {
//开启声浪
mIvRipple.startLoopingSvga("ripple3695.svga");
mIvRipple.setVisibility(VISIBLE);
mTvName.setText(bean.getNickname());
ImageUtils.loadHeadCC(bean.getAvatar(), mRiv);
if (TextUtils.isEmpty(pitBean.getDress())) {
if (mIvFrame != null) mIvFrame.setVisibility(INVISIBLE);
} else {
if (mIvFrame != null) {
mIvFrame.setVisibility(VISIBLE);
mIvFrame.setSource(pitBean.getDress(), 3);
}
}
} else {
String pitText = "-1".equals(pitNumber) ? "" :
"9".equals(pitNumber) ? "主持位" :
"10".equals(pitNumber) ? "嘉宾位" :
pitNumber + "号麦位";
mTvName.setText(pitText);
if (mIvFrame != null) mIvFrame.setVisibility(INVISIBLE);
if (mIvFace != null) mIvFace.remove();
//停止声浪
mIvRipple.stopSvga();
mIvRipple.setVisibility(GONE);
}
// 更新魅力值视图
if (mCharmView != null) {
if (pitBean.getNickname() == null || pitBean.getNickname().isEmpty()) {
mCharmView.setVisibility(GONE);
} else {
mCharmView.setVisibility(VISIBLE);
}
}
// 更新PK状态
if (tv_time_pk != null) {
if (pitBean.is_pk() && pitBean.getUser_id() != null &&
!pitBean.getUser_id().equals("0") && !pitBean.getUser_id().isEmpty()) {
tv_time_pk.setVisibility(VISIBLE);
if (mCharmView != null) mCharmView.setVisibility(GONE);
} else {
tv_time_pk.setVisibility(GONE);
if (mCharmView != null) mCharmView.setVisibility(VISIBLE);
}
}
}
private boolean isOn() {
return pitBean != null && !TextUtils.isEmpty(pitBean.getUser_id()) && !"0".equals(pitBean.getUser_id());
}
}

View File

@@ -0,0 +1,70 @@
package com.xscm.moduleutil.widget;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.xscm.moduleutil.bean.room.RoomInfoResp;
import com.xscm.moduleutil.event.QXRoomSeatViewType;
// 在 common 模块中或相应模块中
public class SharedViewModel extends ViewModel {
// 给roomFragment传递数据
private final MutableLiveData<RoomInfoResp> dataForFragment = new MutableLiveData<>();
private final MutableLiveData<Boolean> fragmentReady = new MutableLiveData<>(false);
private MutableLiveData<QXRoomSeatViewType> seatViewTypeData = new MutableLiveData<>();
//给子fragment传递数据
private MutableLiveData<RoomInfoResp> childFragmentData = new MutableLiveData<>();
// 为子Fragment设置数据的方法
public void setChildFragmentData(RoomInfoResp data) {
childFragmentData.setValue(data);
}
// 获取子Fragment数据的LiveData
public LiveData<RoomInfoResp> getChildFragmentData() {
return childFragmentData;
}
public void setSeatViewType(QXRoomSeatViewType type) {
seatViewTypeData.setValue(type);
}
public LiveData<QXRoomSeatViewType> getSeatViewType() {
return seatViewTypeData;
}
public LiveData<RoomInfoResp> getDataForFragment() {
return dataForFragment;
}
public void setDataForFragment(RoomInfoResp data) {
dataForFragment.setValue(data);
}
public LiveData<Boolean> getFragmentReady() {
return fragmentReady;
}
public void setFragmentReady(boolean ready) {
fragmentReady.setValue(ready);
}
// 清除数据,避免重复接收
// 清理所有数据的方法
public void clearAllData() {
dataForFragment.setValue(null);
childFragmentData.setValue(null);
seatViewTypeData.setValue(null);
fragmentReady.setValue(false);
}
// 清理子Fragment数据
public void clearChildFragmentData() {
childFragmentData.setValue(null);
}
// 清理主Fragment数据
public void clearFragmentData() {
dataForFragment.setValue(null);
}
}

View File

@@ -0,0 +1,137 @@
package com.xscm.moduleutil.widget.img;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* Created by xscm on 2021/5/27.
* 创建类似.9图效果的拉伸背景
*/
public class BubbleBackgroundHelper {
/**
* 创建类似.9图效果的拉伸背景
*/
public static Drawable createStretchableBubble(Context context, Bitmap originalBitmap,
int leftBorder, int topBorder,
int rightBorder, int bottomBorder) {
try {
// 将 Bitmap 转换为 NinePatchDrawable
byte[] chunk = createNinePatchChunk(
originalBitmap.getWidth(),
originalBitmap.getHeight(),
leftBorder, topBorder, rightBorder, bottomBorder
);
NinePatchDrawable drawable = new NinePatchDrawable(
context.getResources(),
originalBitmap,
chunk,
new Rect(),
null
);
return drawable;
} catch (Exception e) {
e.printStackTrace();
// 如果失败,返回原始 BitmapDrawable
return new BitmapDrawable(context.getResources(), originalBitmap);
}
}
/**
* 创建 NinePatch 的 chunk 数据
*/
private static byte[] createNinePatchChunk(int bitmapWidth, int bitmapHeight,
int leftBorder, int topBorder,
int rightBorder, int bottomBorder) {
ByteBuffer buffer = ByteBuffer.allocate(84).order(ByteOrder.nativeOrder());
// 写入魔数
buffer.put((byte) 0x01);
buffer.put((byte) 0x02);
buffer.put((byte) 0x02);
buffer.put((byte) 0x02);
// 水平拉伸区域数量
buffer.putInt(1);
// 垂直拉伸区域数量
buffer.putInt(1);
// 颜色数量
buffer.putInt(0);
// 跳过填充
buffer.putInt(0);
buffer.putInt(0);
// 水平拉伸区域
buffer.putInt(leftBorder);
buffer.putInt(bitmapWidth - rightBorder);
// 垂直拉伸区域
buffer.putInt(topBorder);
buffer.putInt(bitmapHeight - bottomBorder);
// 填充剩余部分
for (int i = 0; i < 9; i++) {
buffer.putInt(0);
}
return buffer.array();
}
/**
* 加载网络图片并创建拉伸背景
*/
public static void loadBubbleBackground(Context context, String imageUrl,
OnBubbleBackgroundLoadedListener listener) {
Glide.with(context)
.asBitmap()
.load(imageUrl)
.into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource,
@Nullable Transition<? super Bitmap> transition) {
// 假设气泡的四个角都是 30px根据你的图片调整
int borderSize = 10;
Drawable bubbleDrawable = createStretchableBubble(
context, resource,
borderSize, borderSize, borderSize, borderSize
);
if (listener != null) {
listener.onBackgroundLoaded(bubbleDrawable);
}
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
// 清理资源
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
if (listener != null) {
listener.onLoadFailed();
}
}
});
}
public interface OnBubbleBackgroundLoadedListener {
void onBackgroundLoaded(Drawable bubbleDrawable);
void onLoadFailed();
}
}

View File

@@ -0,0 +1,8 @@
package com.xscm.moduleutil.widget.room
import java.io.IOException
/**
* 自定义异常信息显示
*/
data class PassRoomException(var msg: String,var code: Int) : IOException()

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="200"
android:fromXDelta="100%p"
android:toXDelta="0" />
</set>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="200"
android:fromXDelta="0"
android:toXDelta="100%p" />
</set>

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Some files were not shown because too many files have changed in this diff Show More