1:修改CP特效出现头像不规则的问题
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
|
||||
package com.xscm.moduleutil.utils.roomview;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -28,6 +29,7 @@ import com.tencent.qgame.animplayer.AnimView;
|
||||
import com.tencent.qgame.animplayer.inter.IAnimListener;
|
||||
import com.xscm.moduleutil.R;
|
||||
import com.xscm.moduleutil.utils.ImageUtils;
|
||||
import com.xscm.moduleutil.widget.CircularImage;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
@@ -51,79 +53,145 @@ import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
/**
|
||||
* 项目名称:羽声语音
|
||||
* 羽声语音房间CP特效视图
|
||||
* 用于显示CP(情侣)相关的动画特效,包括头像、名称和动画效果
|
||||
* 支持MP4和SVGA格式的动画文件
|
||||
* 时间:2025/11/24 9:50
|
||||
* 用途:
|
||||
*/
|
||||
public class RoomCPView extends FrameLayout {
|
||||
|
||||
// ==================== 视图组件 ====================
|
||||
/** 动画视图组件 */
|
||||
private AnimView anim_cp;
|
||||
|
||||
private boolean isLoadEffect = false;
|
||||
private boolean isShow = true; // 是否开启特效
|
||||
private ReentrantLock lock = new ReentrantLock();
|
||||
private List<String> animationArray = new ArrayList<>();
|
||||
public ExecutorService queue = Executors.newSingleThreadExecutor();
|
||||
private Context mContext;
|
||||
private boolean isOnece;
|
||||
/** CP头像组件 */
|
||||
private CircularImage room_cp_head1, room_cp_head2;
|
||||
|
||||
private RoundedImageView room_cp_head1;
|
||||
private RoundedImageView room_cp_head2;
|
||||
/** CP名称组件 */
|
||||
private TextView room_cp_name1, room_cp_name2;
|
||||
|
||||
/** 头像容器 */
|
||||
private LinearLayout avatarContainer1;
|
||||
|
||||
/** 头像父容器 */
|
||||
private ConstraintLayout avatarsParentContainer;
|
||||
|
||||
// 动画队列和锁机制
|
||||
private final Queue<AnimationTask> animationQueue = new LinkedList<>();
|
||||
private boolean isAnimationRunning = false;
|
||||
private final Object animationLock = new Object();
|
||||
// ==================== 状态控制 ====================
|
||||
/** 是否正在加载特效 */
|
||||
private boolean isLoadEffect = false;
|
||||
|
||||
// 下载任务缓存
|
||||
private final Queue<DownloadTask> downloadQueue = new LinkedList<>();
|
||||
private boolean isDownloadRunning = false;
|
||||
private final Object downloadLock = new Object();
|
||||
/** 是否开启特效显示 */
|
||||
private boolean isShow = true;
|
||||
|
||||
/** 是否仅播放一次 */
|
||||
private boolean isOnece;
|
||||
|
||||
/** 当前播放的动画路径 */
|
||||
private String currPlayPath = "";
|
||||
|
||||
// ==================== 线程安全 ====================
|
||||
/** 动画数组锁 */
|
||||
private ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
/** 动画队列锁 */
|
||||
private final Object animationLock = new Object();
|
||||
|
||||
/** 下载队列锁 */
|
||||
private final Object downloadLock = new Object();
|
||||
|
||||
// ==================== 数据存储 ====================
|
||||
/** 动画路径列表 */
|
||||
private List<String> animationArray = new ArrayList<>();
|
||||
|
||||
/** 动画任务队列 */
|
||||
private final Queue<AnimationTask> animationQueue = new LinkedList<>();
|
||||
|
||||
/** 下载任务队列 */
|
||||
private final Queue<DownloadTask> downloadQueue = new LinkedList<>();
|
||||
|
||||
// ==================== 状态标志 ====================
|
||||
/** 是否有动画正在运行 */
|
||||
private boolean isAnimationRunning = false;
|
||||
|
||||
/** 是否有下载任务正在运行 */
|
||||
private boolean isDownloadRunning = false;
|
||||
|
||||
// ==================== 线程池 ====================
|
||||
/** 任务执行线程池 */
|
||||
public ExecutorService queue = Executors.newSingleThreadExecutor();
|
||||
|
||||
// ==================== 其他 ====================
|
||||
/** 上下文对象 */
|
||||
private Context mContext;
|
||||
|
||||
/** 播放完成监听器列表 */
|
||||
private List<OnPlaybackCompleteListener> playbackCompleteListeners = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 设置线程池
|
||||
* @param queue 线程池
|
||||
*/
|
||||
public void setQueue(ExecutorService queue) {
|
||||
this.queue = queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 基本构造函数
|
||||
* @param context 上下文
|
||||
*/
|
||||
public RoomCPView(@NonNull Context context) {
|
||||
super(context);
|
||||
this.mContext = context;
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 带属性集的构造函数
|
||||
* @param context 上下文
|
||||
* @param attrs 属性集
|
||||
*/
|
||||
public RoomCPView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
this.mContext = context;
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 带属性集和样式属性的构造函数
|
||||
* @param context 上下文
|
||||
* @param attrs 属性集
|
||||
* @param defStyleAttr 默认样式属性
|
||||
*/
|
||||
public RoomCPView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
this.mContext = context;
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 完整构造函数
|
||||
* @param context 上下文
|
||||
* @param attrs 属性集
|
||||
* @param defStyleAttr 默认样式属性
|
||||
* @param defStyleRes 默认样式资源
|
||||
*/
|
||||
public RoomCPView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
this.mContext = context;
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化视图和状态
|
||||
*/
|
||||
private void init() {
|
||||
isLoadEffect = false;
|
||||
|
||||
initView();
|
||||
|
||||
// 设置动画监听器
|
||||
setupAnimListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置动画监听器
|
||||
* 为动画视图设置各种状态回调,包括开始、完成、失败等事件
|
||||
*/
|
||||
private void setupAnimListener() {
|
||||
if (anim_cp != null) {
|
||||
@@ -149,9 +217,28 @@ public class RoomCPView extends FrameLayout {
|
||||
if (isOnece) {
|
||||
return;
|
||||
}
|
||||
// 通知播放完成
|
||||
notifyPlaybackComplete();
|
||||
loadStartAnimation();
|
||||
|
||||
// 添加延迟,确保当前动画完全结束
|
||||
postDelayed(() -> {
|
||||
// 使用动画队列锁确保线程安全
|
||||
synchronized (animationLock) {
|
||||
// 重置播放状态
|
||||
lock.lock();
|
||||
try {
|
||||
isLoadEffect = false;
|
||||
// 标记动画已停止运行
|
||||
isAnimationRunning = false;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
// 通知播放完成
|
||||
notifyPlaybackComplete();
|
||||
}
|
||||
|
||||
// 继续播放下一个动画
|
||||
loadStartAnimation();
|
||||
}, 100); // 添加100毫秒的延迟
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -177,6 +264,7 @@ public class RoomCPView extends FrameLayout {
|
||||
// startAvatarFloatAnimation();
|
||||
});
|
||||
}
|
||||
/**************** c361dac4fe23498d925fba2efe376ca8 ****************/
|
||||
|
||||
@Override
|
||||
public boolean onVideoConfigReady(@NonNull AnimConfig animConfig) {
|
||||
@@ -192,6 +280,20 @@ public class RoomCPView extends FrameLayout {
|
||||
// 动画失败,隐藏视图
|
||||
setVisibility(View.GONE);
|
||||
anim_cp.setVisibility(View.GONE);
|
||||
|
||||
// 使用动画队列锁确保线程安全
|
||||
synchronized (animationLock) {
|
||||
// 重置播放状态
|
||||
lock.lock();
|
||||
try {
|
||||
isLoadEffect = false;
|
||||
// 标记动画已停止运行
|
||||
isAnimationRunning = false;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// 继续播放下一个
|
||||
loadStartAnimation();
|
||||
});
|
||||
@@ -200,126 +302,169 @@ public class RoomCPView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化视图组件
|
||||
* 加载布局文件并获取各个视图组件的引用
|
||||
*/
|
||||
private void initView() {
|
||||
// 加载布局文件
|
||||
LayoutInflater.from(getContext()).inflate(R.layout.room_cp_vip_view, this, true);
|
||||
|
||||
// 获取动画视图组件
|
||||
anim_cp = findViewById(R.id.anim_cp);
|
||||
|
||||
// 获取头像组件
|
||||
room_cp_head1 = findViewById(R.id.room_cp_head1);
|
||||
room_cp_head2 = findViewById(R.id.room_cp_head2);
|
||||
|
||||
// 获取名称组件
|
||||
room_cp_name1 = findViewById(R.id.room_cp_name1);
|
||||
room_cp_name2 = findViewById(R.id.room_cp_name2);
|
||||
|
||||
// 获取头像的父布局
|
||||
// 获取头像容器
|
||||
avatarContainer1 = findViewById(R.id.ll_head);
|
||||
|
||||
// 获取包含两个头像的父 LinearLayout
|
||||
// 获取包含两个头像的父容器
|
||||
avatarsParentContainer = (ConstraintLayout) avatarContainer1.getParent();
|
||||
|
||||
// // 初始状态隐藏头像和名称
|
||||
// room_cp_head1.setVisibility(View.GONE);
|
||||
// room_cp_head2.setVisibility(View.GONE);
|
||||
// room_cp_name1.setVisibility(View.GONE);
|
||||
// room_cp_name2.setVisibility(View.GONE);
|
||||
// 注释:初始状态隐藏头像和名称(如需要可取消注释)
|
||||
// room_cp_head1.setVisibility(View.GONE);
|
||||
// room_cp_head2.setVisibility(View.GONE);
|
||||
// room_cp_name1.setVisibility(View.GONE);
|
||||
// room_cp_name2.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置CP信息数据
|
||||
* @param room_head1 第一个用户头像URL
|
||||
* @param room_head2 第二个用户头像URL
|
||||
* @param room_cp_name1 第一个用户名称
|
||||
* @param room_cp_name2 第二个用户名称
|
||||
*/
|
||||
public void setCPTextData(String room_head1, String room_head2, String room_cp_name1, String room_cp_name2) {
|
||||
// 加载用户头像
|
||||
ImageUtils.loadHead(room_head1, room_cp_head1);
|
||||
ImageUtils.loadHead(room_head2, room_cp_head2);
|
||||
|
||||
// 设置用户名称
|
||||
this.room_cp_name1.setText(room_cp_name1);
|
||||
this.room_cp_name2.setText(room_cp_name2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载并开始播放动画
|
||||
* 从动画队列中取出下一个动画进行播放
|
||||
*/
|
||||
private void loadStartAnimation() {
|
||||
// 检查是否开启特效
|
||||
if (!isShow) {
|
||||
// isshow 为是否开启特效 如果未开启则return
|
||||
return;
|
||||
}
|
||||
|
||||
String animationPath = null;
|
||||
|
||||
// list加锁
|
||||
lock.lock();
|
||||
try {
|
||||
// 如果list长度大于0
|
||||
if (!animationArray.isEmpty()) {
|
||||
// 动画路径则赋值,取list中的第一个数据
|
||||
animationPath = animationArray.get(0);
|
||||
// 移除list的第一条数据
|
||||
animationArray.remove(0);
|
||||
isLoadEffect = true;
|
||||
} else {
|
||||
isLoadEffect = false;
|
||||
// 队列为空,释放资源但不销毁视图
|
||||
post(() -> {
|
||||
destroyEffectView();
|
||||
});
|
||||
// 使用动画队列锁确保线程安全
|
||||
synchronized (animationLock) {
|
||||
// 如果已经有动画在运行,则直接返回,等待当前动画完成后再处理下一个
|
||||
if (isAnimationRunning) {
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
// 解锁
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
if (isLoadEffect && animationPath != null && !TextUtils.isEmpty(animationPath)) {
|
||||
String finalAnimationPath = animationPath;
|
||||
post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// 处理MP4动画文件(可能是网络URL)
|
||||
handleMP4File(finalAnimationPath, new DownloadCallback() {
|
||||
@Override
|
||||
public void onSuccess(String localPath) {
|
||||
post(() -> {
|
||||
// 设置MP4动画文件
|
||||
currPlayPath = localPath;
|
||||
// 启动从底部弹起动画
|
||||
startBottomUpAnimation();
|
||||
String animationPath = null;
|
||||
|
||||
// 开始播放动画
|
||||
anim_cp.setLoop(1);
|
||||
});
|
||||
}
|
||||
// 对动画列表加锁
|
||||
lock.lock();
|
||||
try {
|
||||
// 检查动画队列是否为空
|
||||
if (!animationArray.isEmpty()) {
|
||||
// 获取队列中的第一个动画路径
|
||||
animationPath = animationArray.get(0);
|
||||
// 从队列中移除已获取的动画
|
||||
animationArray.remove(0);
|
||||
|
||||
@Override
|
||||
public void onError(String error) {
|
||||
LogUtils.e("MP4下载或播放失败: " + error);
|
||||
// 处理失败情况,继续播放下一个
|
||||
post(() -> {
|
||||
lock.lock();
|
||||
try {
|
||||
isLoadEffect = false;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
loadStartAnimation();
|
||||
});
|
||||
}
|
||||
// 设置状态标记
|
||||
isLoadEffect = true;
|
||||
isAnimationRunning = true;
|
||||
} else {
|
||||
// 队列为空,重置状态并释放资源
|
||||
isLoadEffect = false;
|
||||
post(() -> {
|
||||
destroyEffectView();
|
||||
});
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
// 解锁
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
// 如果有动画需要播放
|
||||
if (isLoadEffect && animationPath != null && !TextUtils.isEmpty(animationPath)) {
|
||||
String finalAnimationPath = animationPath;
|
||||
post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// 处理MP4动画文件(可能是网络URL)
|
||||
handleMP4File(finalAnimationPath, new DownloadCallback() {
|
||||
@Override
|
||||
public void onSuccess(String localPath) {
|
||||
post(() -> {
|
||||
// 设置当前播放路径
|
||||
currPlayPath = localPath;
|
||||
// 启动从底部弹起动画
|
||||
startBottomUpAnimation();
|
||||
// 设置播放次数为1次
|
||||
anim_cp.setLoop(1);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String error) {
|
||||
LogUtils.e("MP4下载或播放失败: " + error);
|
||||
// 处理失败情况,继续播放下一个
|
||||
post(() -> {
|
||||
// 使用动画队列锁确保线程安全
|
||||
synchronized (animationLock) {
|
||||
lock.lock();
|
||||
try {
|
||||
// 重置状态标记
|
||||
isLoadEffect = false;
|
||||
isAnimationRunning = false;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
// 尝试播放下一个动画
|
||||
loadStartAnimation();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* CP特效进来
|
||||
* @param room_head1 头像1
|
||||
* @param room_head2 头像2
|
||||
* @param room_name1 名称1
|
||||
* @param room_name2 名称2
|
||||
* @param mp4Path MP4动画文件路径
|
||||
* 显示CP特效
|
||||
* 设置CP信息并添加动画到队列中播放
|
||||
* @param room_head1 第一个用户头像URL
|
||||
* @param room_head2 第二个用户头像URL
|
||||
* @param room_name1 第一个用户名称
|
||||
* @param room_name2 第二个用户名称
|
||||
* @param mp4Path 动画文件路径(支持mp4和svga格式)
|
||||
*/
|
||||
public void displayEffectView(String room_head1, String room_head2, String room_name1, String room_name2, String mp4Path) {
|
||||
// 确保视图已初始化
|
||||
reinitView();
|
||||
|
||||
// 设置CP数据,但不显示头像
|
||||
// 设置CP数据
|
||||
setCPTextData(room_head1, room_head2, room_name1, room_name2);
|
||||
|
||||
// 确保头像初始为隐藏状态
|
||||
// room_cp_head1.setVisibility(View.GONE);
|
||||
// room_cp_head2.setVisibility(View.GONE);
|
||||
// room_cp_name1.setVisibility(View.GONE);
|
||||
// room_cp_name2.setVisibility(View.GONE);
|
||||
// 注释:确保头像初始为隐藏状态(如需要可取消注释)
|
||||
// room_cp_head1.setVisibility(View.GONE);
|
||||
// room_cp_head2.setVisibility(View.GONE);
|
||||
// room_cp_name1.setVisibility(View.GONE);
|
||||
// room_cp_name2.setVisibility(View.GONE);
|
||||
|
||||
// 确保视图可见
|
||||
setVisibility(View.VISIBLE);
|
||||
@@ -329,32 +474,32 @@ public class RoomCPView extends FrameLayout {
|
||||
queue = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
// 在后台线程中处理动画
|
||||
queue.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// 如果mp4Path不存在return
|
||||
// 检查动画路径是否有效
|
||||
if (mp4Path == null || mp4Path.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 将mp4Path链接转为小写
|
||||
// 获取文件扩展名并转为小写
|
||||
String playImage = mp4Path;
|
||||
String pathExtension = getFileExtension(playImage).toLowerCase();
|
||||
|
||||
// 判定礼物的后缀是否为svga或者mp4如果非这两种 则return
|
||||
// 检查文件格式是否支持(仅支持svga和mp4)
|
||||
if (!("svga".equals(pathExtension) || "mp4".equals(pathExtension))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 锁住list
|
||||
// 对动画列表加锁
|
||||
lock.lock();
|
||||
try {
|
||||
// 添加动画数据进list
|
||||
// 将动画路径添加到队列
|
||||
animationArray.add(playImage);
|
||||
|
||||
// 如果没有在加载则开始加载
|
||||
// 如果当前没有动画在加载,则开始加载新动画
|
||||
if (!isLoadEffect) {
|
||||
// 更改加载状态标记
|
||||
isLoadEffect = true;
|
||||
loadStartAnimation();
|
||||
}
|
||||
@@ -420,7 +565,6 @@ public class RoomCPView extends FrameLayout {
|
||||
}
|
||||
|
||||
// 添加成员变量
|
||||
private List<OnPlaybackCompleteListener> playbackCompleteListeners = new ArrayList<>();
|
||||
|
||||
// 添加监听器管理方法
|
||||
public void addOnPlaybackCompleteListener(OnPlaybackCompleteListener listener) {
|
||||
@@ -658,12 +802,16 @@ public class RoomCPView extends FrameLayout {
|
||||
setVisibility(View.GONE);
|
||||
|
||||
// 清空动画队列
|
||||
lock.lock();
|
||||
try {
|
||||
animationArray.clear();
|
||||
isLoadEffect = false;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
synchronized (animationLock) {
|
||||
lock.lock();
|
||||
try {
|
||||
animationArray.clear();
|
||||
isLoadEffect = false;
|
||||
// 重置动画运行状态
|
||||
isAnimationRunning = false;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.makeramen.roundedimageview.RoundedImageView
|
||||
<com.xscm.moduleutil.widget.CircularImage
|
||||
android:id="@+id/room_cp_head1"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
@@ -73,7 +73,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.makeramen.roundedimageview.RoundedImageView
|
||||
<com.xscm.moduleutil.widget.CircularImage
|
||||
android:id="@+id/room_cp_head2"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
|
||||
Reference in New Issue
Block a user