1:修改CP特效出现头像不规则的问题

This commit is contained in:
2025-12-04 10:18:51 +08:00
parent 22a1f420ea
commit 23a07562b7
3 changed files with 266 additions and 117 deletions

View File

@@ -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
// 检查文件格式是否支持(仅支持svgamp4
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();
}
}
}

View File

@@ -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"

View File

@@ -50,6 +50,7 @@
android:id="@+id/tv_gift_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_5"
tools:text="礼物名称"
android:gravity="center"
android:textColor="@color/color_FF333333"