1:添加cp进场动画效果

2:修改cp礼物弹框
3:添加组成cp后进入心动空间跳转
This commit is contained in:
2025-11-24 18:48:14 +08:00
parent a1e123f0bf
commit 0dd7c367cb
13 changed files with 1334 additions and 475 deletions

View File

@@ -4534,8 +4534,7 @@ public class RetrofitClient {
});
}
public void roomUserCharmList(String room_id, String
user_id, BaseObserver<List<RoomUserCharmListBean>> observer) {
public void roomUserCharmList(String room_id, String user_id, BaseObserver<List<RoomUserCharmListBean>> observer) {
sApiServer.roomUserCharmList(room_id, user_id).enqueue(new Callback<BaseModel<List<RoomUserCharmListBean>>>() {
@Override
public void onResponse(Call<BaseModel<List<RoomUserCharmListBean>>> call, Response<BaseModel<List<RoomUserCharmListBean>>> response) {
@@ -4566,7 +4565,8 @@ public class RetrofitClient {
if (baseModel.getCode() == 1) {
observer.onNext(baseModel.getData());
} else if (baseModel.getCode() == 0) {
observer.onNext(null);
ToastUtils.showLong(baseModel.getMsg());
// observer.onNext(null);
}
}
}

View File

@@ -0,0 +1,919 @@
package com.xscm.moduleutil.utils.roomview;
import android.content.Context;
import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.Interpolator;
import android.view.animation.OvershootInterpolator;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.blankj.utilcode.util.LogUtils;
import com.makeramen.roundedimageview.RoundedImageView;
import com.tencent.qgame.animplayer.AnimConfig;
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 java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
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;
/**
* 项目名称:羽声语音
* 时间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;
private RoundedImageView room_cp_head1;
private RoundedImageView room_cp_head2;
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 final Queue<DownloadTask> downloadQueue = new LinkedList<>();
private boolean isDownloadRunning = false;
private final Object downloadLock = new Object();
private String currPlayPath = "";
public void setQueue(ExecutorService queue) {
this.queue = queue;
}
public RoomCPView(@NonNull Context context) {
super(context);
this.mContext = context;
init();
}
public RoomCPView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
init();
}
public RoomCPView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
init();
}
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) {
anim_cp.setAnimListener(new IAnimListener() {
@Override
public void onVideoDestroy() {
LogUtils.e("onVideoDestroy");
}
@Override
public void onVideoComplete() {
LogUtils.e("onVideoComplete");
// 确保所有UI操作在主线程中执行
post(() -> {
if (anim_cp != null) {
// 停止头像动画
stopAvatarAnimation();
// 隐藏动画视图和头像
anim_cp.setVisibility(View.GONE);
avatarContainer1.setVisibility(View.GONE);
if (isOnece) {
return;
}
// 通知播放完成
notifyPlaybackComplete();
loadStartAnimation();
}
});
}
@Override
public void onVideoRender(int i, @Nullable AnimConfig animConfig) {
// LogUtils.e("onVideoRender", i, animConfig);
}
@Override
public void onVideoStart() {
LogUtils.e("onVideoStart");
// 确保所有UI操作在主线程中执行
post(() -> {
// 动画开始,显示视图和头像
setVisibility(View.VISIBLE);
anim_cp.setVisibility(View.VISIBLE);
avatarContainer1.setVisibility(View.VISIBLE);
// 启动头像上下浮动动画
// startAvatarFloatAnimation();
});
}
@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);
// 确保所有UI操作在主线程中执行
post(() -> {
// 动画失败,隐藏视图
setVisibility(View.GONE);
anim_cp.setVisibility(View.GONE);
// 继续播放下一个
loadStartAnimation();
});
}
});
}
}
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);
}
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();
});
}
} 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();
// 开始播放动画
anim_cp.setLoop(1);
});
}
@Override
public void onError(String error) {
LogUtils.e("MP4下载或播放失败: " + error);
// 处理失败情况,继续播放下一个
post(() -> {
lock.lock();
try {
isLoadEffect = 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动画文件路径
*/
public void displayEffectView(String room_head1, String room_head2, String room_name1, String room_name2, String mp4Path) {
// 确保视图已初始化
reinitView();
// 设置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);
// 确保视图可见
setVisibility(View.VISIBLE);
// 检查队列是否已初始化
if (queue == null) {
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
if (!("svga".equals(pathExtension) || "mp4".equals(pathExtension))) {
return;
}
// 锁住list
lock.lock();
try {
// 添加动画数据进list
animationArray.add(playImage);
// 如果没有在加载则开始加载
if (!isLoadEffect) {
// 更改加载状态标记
isLoadEffect = true;
loadStartAnimation();
}
} finally {
// 解锁
lock.unlock();
}
}
});
}
/**
* 原始的动画方法,现在只被内部调用
*/
public void startAnimation(AnimationListener listener) {
// 设置进入动画
AlphaAnimation fadeIn = new AlphaAnimation(0f, 1f);
fadeIn.setDuration(500);
fadeIn.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
// 保持显示1秒后开始退出动画
postDelayed(() -> {
AlphaAnimation fadeOut = new AlphaAnimation(1f, 0f);
fadeOut.setDuration(500);
fadeOut.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
if (listener != null) {
listener.onAnimationEnd();
}
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
startAnimation(fadeOut);
}, 1000);
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
startAnimation(fadeIn);
}
public interface AnimationListener {
void onAnimationEnd();
}
// 添加播放完成监听接口
public interface OnPlaybackCompleteListener {
void onPlaybackComplete();
}
// 添加成员变量
private List<OnPlaybackCompleteListener> playbackCompleteListeners = new ArrayList<>();
// 添加监听器管理方法
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();
}
}
}
/**
* 动画任务类,用于存储动画所需的数据
*/
public static class AnimationTask {
String room_head1;
String room_head2;
String room_cp_name1;
String room_cp_name2;
String mp4Path; // MP4动画文件路径
AnimationListener listener;
AnimationTask(String room_head1, String room_head2, String room_cp_name1, String room_cp_name2, String mp4Path, AnimationListener listener) {
this.room_head1 = room_head1;
this.room_head2 = room_head2;
this.room_cp_name1 = room_cp_name1;
this.room_cp_name2 = room_cp_name2;
this.mp4Path = mp4Path;
this.listener = listener;
}
}
/**
* 处理MP4文件如果是网络URL则下载到本地
* @param path 文件路径或URL
* @param callback 下载回调
*/
private void handleMP4File(String path, DownloadCallback callback) {
// 判断是否是网络URL
if (path != null && (path.startsWith("http://") || path.startsWith("https://"))) {
// 是网络URL需要下载
downloadFile(path, callback);
} else {
// 本地路径,直接使用
if (callback != null) {
callback.onSuccess(path);
}
}
}
/**
* 下载文件到本地
* @param url 下载URL
* @param callback 下载回调
*/
private void downloadFile(String url, DownloadCallback callback) {
synchronized (downloadLock) {
// 创建下载任务并加入队列
DownloadTask task = new DownloadTask(url, callback);
downloadQueue.offer(task);
// 如果当前没有下载任务在执行,则开始下载
if (!isDownloadRunning) {
processNextDownload();
}
}
}
/**
* 处理下一个下载任务
*/
private void processNextDownload() {
synchronized (downloadLock) {
if (downloadQueue.isEmpty()) {
isDownloadRunning = false;
return;
}
isDownloadRunning = true;
DownloadTask currentTask = downloadQueue.poll();
// 执行下载任务
new DownloadAsyncTask(currentTask.callback).execute(currentTask.url);
}
}
/**
* 异步下载任务
*/
private class DownloadAsyncTask extends AsyncTask<String, Void, String> {
private DownloadCallback callback;
private String errorMessage;
public DownloadAsyncTask(DownloadCallback callback) {
this.callback = callback;
}
@Override
protected String doInBackground(String... urls) {
String url = urls[0];
try {
// 获取文件名
int lastSlashIndex = url.lastIndexOf('/');
String fileName = lastSlashIndex != -1 ? url.substring(lastSlashIndex + 1) : url;
int queryIndex = fileName.indexOf("?");
if (queryIndex != -1) {
fileName = fileName.substring(0, queryIndex);
}
// 创建本地文件
File dir = new File(getContext().getCacheDir(), "animations");
if (!dir.exists()) {
dir.mkdirs();
}
File file = new File(dir, fileName);
// 如果文件已存在,直接返回本地路径
if (file.exists()) {
return file.getAbsolutePath();
}
// 下载文件
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.connect();
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
errorMessage = "服务器返回HTTP错误代码: " + connection.getResponseCode();
return null;
}
InputStream input = connection.getInputStream();
FileOutputStream output = new FileOutputStream(file);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = input.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
output.close();
input.close();
connection.disconnect();
return file.getAbsolutePath();
} catch (IOException e) {
errorMessage = "下载失败: " + e.getMessage();
return null;
}
}
@Override
protected void onPostExecute(String result) {
synchronized (downloadLock) {
// 下载完成,处理下一个下载任务
processNextDownload();
}
// 通知回调
if (callback != null) {
if (result != null) {
callback.onSuccess(result);
} else {
callback.onError(errorMessage != null ? errorMessage : "未知错误");
}
}
}
}
/**
* 下载任务类
*/
private static class DownloadTask {
String url;
DownloadCallback callback;
DownloadTask(String url, DownloadCallback callback) {
this.url = url;
this.callback = callback;
}
}
/**
* 下载回调接口
*/
private interface DownloadCallback {
void onSuccess(String localPath);
void onError(String error);
}
/**
* 获取文件扩展名
* @param url 文件URL
* @return 文件扩展名
*/
private String getFileExtension(String url) {
if (url != null && url.contains(".")) {
return url.substring(url.lastIndexOf(".") + 1);
}
return "";
}
/**
* 销毁特效视图
*/
public void destroyEffectView() {
// 停止头像动画
stopAvatarAnimation();
// 清理监听器
clearPlaybackCompleteListeners();
// 停止动画视图但保留组件以便重用
if (anim_cp != null) {
anim_cp.stopPlay();
anim_cp.setVisibility(View.GONE);
}
// 隐藏视图但不移除,以便再次使用
setVisibility(View.GONE);
// 清空动画队列
lock.lock();
try {
animationArray.clear();
isLoadEffect = false;
} finally {
lock.unlock();
}
}
/**
* 从底部弹起动画
*/
private void startBottomUpAnimation() {
// 获取父视图的高度如果为0则使用屏幕高度
float parentHeight = 0;
if (getParent() != null) {
parentHeight = ((View) getParent()).getHeight();
}
if (parentHeight <= 0) {
parentHeight = getResources().getDisplayMetrics().heightPixels;
}
// 设置初始位置在屏幕底部外
// setTranslationY(parentHeight);
// 创建从底部弹起的动画
TranslateAnimation bottomUpAnimation = new TranslateAnimation(
Animation.ABSOLUTE, 0, Animation.ABSOLUTE, 0,
Animation.RELATIVE_TO_PARENT, 1.0f, Animation.RELATIVE_TO_PARENT, 0.0f);
bottomUpAnimation.setDuration(2000); // 动画持续时间
// 创建弹性插值器
Interpolator overshootInterpolator = new OvershootInterpolator(0.5f);
bottomUpAnimation.setInterpolator(overshootInterpolator);
// 应用动画
startAnimation(bottomUpAnimation);
// 动画结束后重置位置
bottomUpAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
setVisibility(View.VISIBLE);
anim_cp.setVisibility(View.VISIBLE);
avatarContainer1.setVisibility(VISIBLE);
anim_cp.startPlay(new File(currPlayPath));
}
@Override
public void onAnimationEnd(Animation animation) {
startAvatarFloatAnimation();
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
avatarsParentContainer.startAnimation(bottomUpAnimation);
}
/**
* 头像上下动画
*/
private void startAvatarFloatAnimation() {
// 确保包含两个头像的父布局已初始化
if (avatarsParentContainer == null) {
return;
}
// 包含两个头像的父布局上下浮动动画
AnimationSet avatarsAnimationSet = new AnimationSet(true);
// 使用适中的移动距离,确保头像框显示完整
TranslateAnimation avatarsFloatUp = new TranslateAnimation(
Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f,
Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, -0.01f);
avatarsFloatUp.setDuration(900); // 增加动画持续时间,使动画更平滑
avatarsFloatUp.setRepeatCount(Animation.INFINITE);
avatarsFloatUp.setRepeatMode(Animation.REVERSE);
avatarsAnimationSet.addAnimation(avatarsFloatUp);
avatarsParentContainer.startAnimation(avatarsAnimationSet);
}
/**
* 停止头像动画
*/
private void stopAvatarAnimation() {
if (avatarsParentContainer != null) {
avatarsParentContainer.clearAnimation();
}
}
/**
* 停止播放动画
*/
public void stopPlay() {
stopAvatarAnimation();
if (anim_cp != null) {
anim_cp.stopPlay();
anim_cp.setVisibility(View.GONE);
}
setVisibility(View.GONE);
}
/**
* 开启或关闭特效视图
* @param isShow 是否显示
*/
public void openOrCloseEffectViewWith(boolean isShow) {
this.isShow = isShow;
if (anim_cp != null) {
anim_cp.stopPlay();
anim_cp.setVisibility(View.GONE);
}
setVisibility(isShow ? View.VISIBLE : View.GONE);
}
/**
* 下载并播放动画
* @param context 上下文
* @param playImage 动画URL
* @param callback 下载回调
*/
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.onError(e.getMessage());
}
});
}
@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.getAbsolutePath());
}
});
} else {
// 在主线程中回调失败
post(() -> {
if (callback != null) {
callback.onError("Response body is null");
}
});
}
} catch (Exception e) {
LogUtils.e("MP4文件保存失败: " + e.getMessage());
// 在主线程中回调失败
post(() -> {
if (callback != null) {
callback.onError(e.getMessage());
}
});
}
} else {
LogUtils.e("MP4下载响应失败");
// 在主线程中回调失败
post(() -> {
if (callback != null) {
callback.onError("Response not successful: " + response.code());
}
});
}
}
});
} else {
// 文件已存在,直接回调成功
if (callback != null) {
callback.onSuccess(file.getAbsolutePath());
}
}
}
/**
* 重新初始化视图,以便再次播放
*/
public void reinitView() {
if (anim_cp == null) {
// 如果动画视图已被销毁,重新初始化
initView();
// 重新设置动画监听器
setupAnimListener();
}
// 确保线程池可用
if (queue == null || queue.isShutdown()) {
queue = Executors.newSingleThreadExecutor();
}
// 重置状态
isLoadEffect = false;
isShow = true;
}
/**
* 完全销毁视图,释放所有资源
* 当不再需要此视图时调用
*/
public void completeDestroy() {
// 停止头像动画
stopAvatarAnimation();
// 清理监听器
clearPlaybackCompleteListeners();
// 停止并移除动画视图
if (anim_cp != null) {
anim_cp.stopPlay();
removeView(anim_cp);
anim_cp = null;
}
// 停止线程池
if (queue != null) {
queue.shutdown();
queue = null;
}
// 隐藏并移除整个视图
setVisibility(View.GONE);
if (getParent() != null) {
((ViewGroup) getParent()).removeView(this);
}
}
}

View File

@@ -6,6 +6,7 @@ import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.xscm.moduleutil.bean.GiftBean;
import com.xscm.moduleutil.utils.roomview.RoomCPView;
import java.util.List;
import java.util.concurrent.ExecutorService;
@@ -20,6 +21,8 @@ public class QXGiftPlayerManager {
private GiftAnimView fullEffectView;
private GiftAnimView chatEffectView;
private RoomCPView roomCPView;
private Context context;
private QXGiftPlayerManager(Context context) {
@@ -39,6 +42,15 @@ public class QXGiftPlayerManager {
return bgEffectView;
}
public RoomCPView getRoomCPView(){
if (roomCPView==null){
initRoomCPView();
}
return roomCPView;
}
public GiftAnimView getDefaultFullEffectView() {
if (fullEffectView == null) {
initFullEffectView();
@@ -62,10 +74,25 @@ public class QXGiftPlayerManager {
bgEffectView.setBackgroundColor(0x00000000);
bgEffectView.setClickable(false); // userInteractionEnabled = NO
// 添加全屏特效视图聊天特效视图
// 添加全屏特效视图聊天特效视图和CP视图
((ViewGroup) bgEffectView).addView(getDefaultFullEffectView());
((ViewGroup) bgEffectView).addView(getDefaultChatEffectView());
((ViewGroup) bgEffectView).addView(getRoomCPView());
}
private void initRoomCPView() {
roomCPView=new RoomCPView(context);
roomCPView.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());
roomCPView.setQueue(queue);
}
public void displayFullEffectView(String gift) {
getDefaultFullEffectView().displayEffectView(gift);
}
@@ -78,6 +105,31 @@ public class QXGiftPlayerManager {
getDefaultChatEffectView().displayEffectView(gift);
}
/**
* 显示CP动画
* @param room_head1 头像1的URL
* @param room_head2 头像2的URL
* @param room_cp_name1 名称1
* @param room_cp_name2 名称2
* @param mp4Path MP4动画文件路径
*/
public void displayCPView(String room_head1, String room_head2, String room_cp_name1, String room_cp_name2, String mp4Path) {
getRoomCPView().displayEffectView(room_head1, room_head2, room_cp_name1, room_cp_name2, mp4Path);
}
/**
* 开启或关闭CP动画
* @param isShow 是否显示
*/
public void openOrCloseCPViewWith(boolean isShow) {
}
/**
* 停止CP动画
*/
public void stopCPPlay() {
}
public void openOrCloseEffectViewWith(boolean isShow) {
@@ -101,6 +153,12 @@ public class QXGiftPlayerManager {
}
chatEffectView = null;
}
if (roomCPView != null) {
// 先调用destroyEffectView方法它会自动从父视图中移除
roomCPView.destroyEffectView();
roomCPView = null;
}
if (bgEffectView != null) {
bgEffectView = null;
@@ -114,6 +172,8 @@ public class QXGiftPlayerManager {
if (chatEffectView != null) {
chatEffectView.stopPlay();
}
if (roomCPView != null) {
}
}
private void initFullEffectView() {

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:duration="2000"
android:fromYDelta="0"
android:toYDelta="-20"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:interpolator="@android:anim/accelerate_decelerate_interpolator" />
</set>

View File

@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false">
<com.tencent.qgame.animplayer.AnimView
android:id="@+id/anim_cp"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.58" />
<LinearLayout
android:id="@+id/ll_head"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/guideline">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.makeramen.roundedimageview.RoundedImageView
android:id="@+id/room_cp_head1"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:src="@mipmap/ic_launcher_app"
app:riv_oval="true" />
<TextView
android:id="@+id/room_cp_name1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:enabled="true"
android:maxEms="5"
android:paddingLeft="10dp"
android:paddingTop="2dp"
android:paddingRight="10dp"
android:paddingBottom="2dp"
android:singleLine="true"
android:textColor="@color/color_999999"
android:textSize="8sp"
tools:text="" />
</LinearLayout>
<View
android:layout_width="@dimen/dp_40"
android:layout_height="0dp"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.makeramen.roundedimageview.RoundedImageView
android:id="@+id/room_cp_head2"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:src="@mipmap/ic_launcher_app"
app:riv_oval="true" />
<TextView
android:id="@+id/room_cp_name2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:enabled="true"
android:maxEms="5"
android:paddingLeft="10dp"
android:paddingTop="2dp"
android:paddingRight="10dp"
android:paddingBottom="2dp"
android:singleLine="true"
android:textColor="@color/color_999999"
android:textSize="8sp"
tools:text="" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>