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

View File

@@ -38,7 +38,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<com.makeramen.roundedimageview.RoundedImageView <com.xscm.moduleutil.widget.CircularImage
android:id="@+id/room_cp_head1" android:id="@+id/room_cp_head1"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
@@ -73,7 +73,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<com.makeramen.roundedimageview.RoundedImageView <com.xscm.moduleutil.widget.CircularImage
android:id="@+id/room_cp_head2" android:id="@+id/room_cp_head2"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"

View File

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