1:羽声新版本
This commit is contained in:
BIN
moduleUtil/src/main/assets/draw_music.mp3
Normal file
BIN
moduleUtil/src/main/assets/draw_music.mp3
Normal file
Binary file not shown.
BIN
moduleUtil/src/main/assets/heart_line_31.svga
Normal file
BIN
moduleUtil/src/main/assets/heart_line_31.svga
Normal file
Binary file not shown.
BIN
moduleUtil/src/main/assets/xuanz.mp3
Normal file
BIN
moduleUtil/src/main/assets/xuanz.mp3
Normal file
Binary file not shown.
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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:无
|
||||
}
|
||||
@@ -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;//开户行
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.xscm.moduleutil.bean;
|
||||
|
||||
public class GiftAvatarBean {
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.xscm.moduleutil.bean;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class GiftPackEvent {
|
||||
private String bdid;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.xscm.moduleutil.bean;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RedPackGrab {
|
||||
private int code;//1:正常抢 2:已经抢过了 3:手慢了
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 ;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;//中奖礼物数量
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 ;
|
||||
}
|
||||
@@ -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 ;//关系类型图标
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.xscm.moduleutil.bean.room
|
||||
|
||||
class Emotion {
|
||||
var type_name: String? = ""
|
||||
var id: Int? = 0
|
||||
}
|
||||
@@ -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? = "",
|
||||
)
|
||||
@@ -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;//什么关系
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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 没有红包
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.xscm.moduleutil.event;
|
||||
/**
|
||||
* 红包打开状态
|
||||
*/
|
||||
public enum RedEnvelopeStatus {
|
||||
|
||||
/// 打开红包
|
||||
QXRedBagDrawTypeOpen,
|
||||
/// 仅倒计时
|
||||
QXRedBagDrawTypeTimeDown,
|
||||
/// 仅收藏房间
|
||||
QXRedBagDrawTypeCollect,
|
||||
/// 手慢了被领完了
|
||||
QXRedBagDrawTypeFinished,
|
||||
/// 发送评论领红包
|
||||
QXRedBagDrawTypePwdSend,
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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已关闭");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向某个主题发布消息 默认qos:1
|
||||
*/
|
||||
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: 消息质量 Qos:0、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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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); // 上传失败
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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=""
|
||||
)
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.xscm.moduleutil.widget.room
|
||||
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* 自定义异常信息显示
|
||||
*/
|
||||
data class PassRoomException(var msg: String,var code: Int) : IOException()
|
||||
9
moduleUtil/src/main/res/anim/slide_in_right.xml
Normal file
9
moduleUtil/src/main/res/anim/slide_in_right.xml
Normal 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>
|
||||
9
moduleUtil/src/main/res/anim/slide_out_right.xml
Normal file
9
moduleUtil/src/main/res/anim/slide_out_right.xml
Normal 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>
|
||||
BIN
moduleUtil/src/main/res/drawable-xxxhdpi/ac_left_gift_bg.png
Normal file
BIN
moduleUtil/src/main/res/drawable-xxxhdpi/ac_left_gift_bg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
BIN
moduleUtil/src/main/res/drawable-xxxhdpi/ac_lock_gift_bg.png
Normal file
BIN
moduleUtil/src/main/res/drawable-xxxhdpi/ac_lock_gift_bg.png
Normal file
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
Reference in New Issue
Block a user