修改交友布局

This commit is contained in:
2025-08-26 19:34:44 +08:00
commit 8eb8ac5397
3152 changed files with 1442170 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
package com.xscm.moduleutil.widget;
import com.google.android.material.appbar.AppBarLayout;
public abstract class AppBarStateChangeListener implements AppBarLayout.OnOffsetChangedListener {
public enum State {
EXPANDED,
COLLAPSED,
IDLE
}
private State mCurrentState = State.IDLE;
@Override
public final void onOffsetChanged(AppBarLayout appBarLayout, int i) {
if (i == 0) {
if (mCurrentState != State.EXPANDED) {
onStateChanged(appBarLayout, State.EXPANDED);
}
mCurrentState = State.EXPANDED;
} else if (Math.abs(i) >= appBarLayout.getTotalScrollRange()) {
if (mCurrentState != State.COLLAPSED) {
onStateChanged(appBarLayout, State.COLLAPSED);
}
mCurrentState = State.COLLAPSED;
} else {
if (mCurrentState != State.IDLE) {
onStateChanged(appBarLayout, State.IDLE);
}
mCurrentState = State.IDLE;
}
}
public abstract void onStateChanged(AppBarLayout appBarLayout, State state);
}

View File

@@ -0,0 +1,883 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.net.Uri;
import android.opengl.GLSurfaceView;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import com.blankj.utilcode.util.LogUtils;
import com.blankj.utilcode.util.PathUtils;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.ui.PlayerView;
import com.liulishuo.okdownload.DownloadTask;
import com.liulishuo.okdownload.core.cause.EndCause;
import com.liulishuo.okdownload.core.cause.ResumeFailedCause;
import com.liulishuo.okdownload.core.listener.DownloadListener1;
import com.liulishuo.okdownload.core.listener.assist.Listener1Assist;
import com.opensource.svgaplayer.SVGACallback;
import com.opensource.svgaplayer.SVGADrawable;
import com.opensource.svgaplayer.SVGADynamicEntity;
import com.opensource.svgaplayer.SVGAImageView;
import com.opensource.svgaplayer.SVGAParser;
import com.opensource.svgaplayer.SVGAVideoEntity;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.databinding.RoomViewSvgaAnimBinding;
import com.xscm.moduleutil.utils.Md5Utils;
import com.xscm.moduleutil.utils.MemoryOptimizationUtils;
import com.xscm.moduleutil.utils.SpUtil;
import com.xscm.moduleutil.utils.logger.Logger;
import com.tencent.qgame.animplayer.AnimConfig;
import com.tencent.qgame.animplayer.inter.IAnimListener;
import java.io.File;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
public class AvatarFrameView extends FrameLayout implements IAnimListener {
@Override
public void onFailed(int i, @Nullable String s) {
}
@Override
public boolean onVideoConfigReady(@NonNull AnimConfig animConfig) {
return true;
}
@Override
public void onVideoStart() {
}
@Override
public void onVideoRender(int i, @Nullable AnimConfig animConfig) {
}
@Override
public void onVideoComplete() {
// if (mType == 1) {
// mBinding.playView.startPlay(mFile); // 循环播放
// } else {
// isPlaying = false;
// playNextFromQueue(); // 播放下一项
// }
// if (isDestroyed) return;
// 确保在主线程中执行
if (Looper.myLooper() == Looper.getMainLooper()) {
handleVideoComplete();
} else {
mainHandler.post(() -> {
// if (!isDestroyed) {
handleVideoComplete();
// }
});
}
}
private void handleVideoComplete() {
// if (isDestroyed) return;
if (mType == 1) {
if (mBinding != null && mFile != null) {
mBinding.playView.startPlay(mFile); // 循环播放
}
} else {
isPlaying = false;
// playNextFromQueue(); // 播放下一项
mainHandler.postDelayed(this::playNextFromQueue, 50);
}
}
@Override
public void onVideoDestroy() {
}
public enum RenderType {SVGA, MP4}
private RenderType renderType;
private ExoPlayer exoPlayer;
private PlayerView playerView;
private SVGAImageView svgaSurface;
private GLSurfaceView glSurfaceView;
private final Handler mainHandler = new Handler(Looper.getMainLooper());
private ChannelSplitRenderer1 renderer;
private int mType;//1:循环播放 2:播放一次停止播放
private File mFile;
private final Queue<PlayItem> playQueue = new LinkedList<>();
private boolean isPlaying = false;
// 添加销毁标记
private boolean isDestroyed = false;
private static final String TAG = "AvatarFrameView";
private RoomViewSvgaAnimBinding mBinding;
// 内存监控
private static final long MAX_MEMORY_THRESHOLD = 300 * 1024 * 1024; // 300MB
private static final int MAX_SVGA_CACHE_SIZE = 3;
private final Map<String, WeakReference<SVGAVideoEntity>> svgaCache = new LruCache<>(MAX_SVGA_CACHE_SIZE);
public AvatarFrameView(@NonNull Context context) {
this(context, null);
}
public AvatarFrameView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public AvatarFrameView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mBinding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.room_view_svga_anim, this, true);
initViews();
}
private void initViews() {
// if (isDestroyed) return;
// 初始化 ExoPlayer View
playerView = new PlayerView(getContext());
playerView.setUseController(false);
playerView.setVisibility(View.GONE);
addView(playerView);
// 初始化 SVGA View
svgaSurface = new SVGAImageView(getContext());
svgaSurface.setVisibility(View.GONE);
addView(svgaSurface);
// // 初始化 GLSurfaceView
// glSurfaceView = new GLSurfaceView(getContext());
// glSurfaceView.setEGLContextClientVersion(2);
// renderer = new ChannelSplitRenderer1();
// glSurfaceView.setRenderer(renderer);
// glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
// glSurfaceView.setVisibility(View.GONE); // 默认隐藏
// addView(glSurfaceView);
// 初始化 ExoPlayer
// if (!isDestroyed) {
try {
exoPlayer = new ExoPlayer.Builder(getContext()).build();
playerView.setPlayer(exoPlayer);
} catch (Exception e) {
LogUtils.e("AvatarFrameView", "Failed to initialize ExoPlayer: " + e.getMessage());
}
// }
if (mBinding != null) {
mBinding.playView.setAnimListener(this);
}
}
private String getFileExtension(String url) {
if (url == null || url.isEmpty()) return "";
int dotIndex = url.lastIndexOf(".");
if (dotIndex > 0 && dotIndex < url.length() - 1) {
return url.substring(dotIndex + 1).toLowerCase(); // 返回 "mp4", "svga" 等
}
return "";
}
private void playNextFromQueue() {
// if (isDestroyed) return;
// 确保在主线程中执行
if (Looper.myLooper() != Looper.getMainLooper()) {
mainHandler.post(this::playNextFromQueue);
return;
}
// 再次检查内存状态
// if (isMemoryLow()) {
// LogUtils.w(TAG, "Low memory, clearing queue");
// clearQueue();
// return;
// }
// 检查特效是否开启
if (SpUtil.getOpenEffect() != 1) {
clearQueue();
return;
}
// 关键:即使 isPlaying 为 true也要检查实际播放状态
if (isPlaying && isActuallyPlaying()) {
Logger.d("AvatarFrameView", "Already playing, skip");
return;
}
PlayItem item = playQueue.poll();
if (item != null) {
isPlaying = true;
Logger.d("AvatarFrameView", "Playing item, remaining queue size: " + playQueue.size());
RenderType type = null;
String ext = getFileExtension(item.url);
if ("svga".equalsIgnoreCase(ext)) {
type = RenderType.SVGA;
} else if ("mp4".equalsIgnoreCase(ext)) {
type = RenderType.MP4;
}
if (type == null) {
isPlaying = false;
playNextFromQueue(); // 跳过无效项
return;
}
clearPrevious();
renderType = type;
mType = item.type;
switch (type) {
case SVGA:
mBinding.playView.stopPlay();
mBinding.playView.setVisibility(View.GONE);
loadSVGA(item.url);
break;
case MP4:
mBinding.playView.setVisibility(View.VISIBLE);
downloadAndPlayMp4(item.url);
break;
}
} else {
isPlaying = false;
Logger.d("AvatarFrameView", "Queue is empty, stop playing");
}
}
// 添加实际播放状态检查方法
private boolean isActuallyPlaying() {
if (renderType == RenderType.SVGA && svgaSurface != null) {
return svgaSurface.isAnimating();
} else if (renderType == RenderType.MP4 && mBinding != null) {
// 检查播放器状态
return mBinding.playView.isRunning();
}
return false;
}
public void setSource(String url, int type2) {
// if (isDestroyed) return;
// 确保在主线程中执行
if (Looper.myLooper() != Looper.getMainLooper()) {
mainHandler.post(() -> setSource(url, type2));
return;
}
// // 检查内存状态
// if (isMemoryLow()) {
// LogUtils.w(TAG, "Low memory, skipping animation");
// clearQueue();
// return;
// }
// 检查特效是否开启
if (SpUtil.getOpenEffect() != 1) {
// 特效关闭时清空队列并停止播放
clearQueue();
return;
}
// 添加到播放队列
// playQueue.offer(new PlayItem(url, type2));
playQueue.add(new PlayItem(url, type2));
Logger.d("AvatarFrameView", "Added to queue, queue size: " + playQueue.size() + ", url: " + url);
// 如果当前没有在播放,则开始播放
// if (!isPlaying) {
// playNextFromQueue();
// }
// 改进播放检查逻辑
checkAndStartPlayback();
}
private void checkAndStartPlayback() {
// 如果队列不为空且当前没有在播放,则开始播放
if (!playQueue.isEmpty() && (!isPlaying || !isActuallyPlaying())) {
playNextFromQueue();
}
}
private boolean isMemoryLow() {
Runtime runtime = Runtime.getRuntime();
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
long maxMemory = runtime.maxMemory();
double memoryUsage = (double) usedMemory / maxMemory;
// 内存使用超过80%或绝对使用量超过阈值
return memoryUsage > 0.8 || usedMemory > MAX_MEMORY_THRESHOLD;
}
// public void setSource(String url, int type2) {
// if (SpUtil.getOpenEffect()==1) {
// playQueue.offer(new PlayItem(url, type2));
// if (!isPlaying) {
// playNextFromQueue();
// }
// }else {
// playQueue.clear();
// isPlaying = false;
// }
//
//// RenderType type = null;
//// if ("svga".equalsIgnoreCase(getFileExtension(url))){
//// type = RenderType.SVGA;
//// }else if ("mp4".equalsIgnoreCase(getFileExtension(url))){
//// type = RenderType.MP4;
//// }
////
//// clearPrevious();
//// renderType = type;
//// mType = type2;
//// switch (type) {
//// case SVGA:
//// mBinding.playView.stopPlay();
//// mBinding.playView.setVisibility(View.GONE);
//// loadSVGA(url);
//// break;
//// case MP4:
////// loadMP4(url);
//// mBinding.playView.setVisibility(View.VISIBLE);
//// downloadAndPlayMp4(url);
//// break;
//// }
// }
private void downloadAndPlayMp4(String url) {
String filePath = PathUtils.getInternalAppCachePath() + Md5Utils.getStringMD5(url) + ".mp4";
File file = new File(filePath);
if (file.exists()) {
playMp4(file);
mFile = file;
} else {
DownloadTask task = new DownloadTask.Builder(url, PathUtils.getInternalAppCachePath()
, Md5Utils.getStringMD5(url) + ".mp4")
.setMinIntervalMillisCallbackProcess(100)
.setPassIfAlreadyCompleted(false)
.setAutoCallbackToUIThread(true)
.build();
// if (StatusUtil.isCompleted(task)) {
// playMp4(task.getFile());
// mFile = task.getFile();
// } else {
task.enqueue(new DownloadListener1() {
@Override
public void taskStart(@NonNull DownloadTask task, @NonNull Listener1Assist.Listener1Model model) {
Logger.e("taskStart", model);
}
@Override
public void retry(@NonNull DownloadTask task, @NonNull ResumeFailedCause cause) {
Logger.e("retry", cause);
task.cancel();
// CrashReport.postCatchedException(new RuntimeException("下载文件重试:" + cause == null ? "" : cause.name()));
}
@Override
public void connected(@NonNull DownloadTask task, int blockCount, long currentOffset, long totalLength) {
Logger.e("connected", blockCount);
}
@Override
public void progress(@NonNull DownloadTask task, long currentOffset, long totalLength) {
Logger.e("progress", currentOffset);
}
@Override
public void taskEnd(@NonNull DownloadTask task, @NonNull EndCause cause, @Nullable Exception realCause, @NonNull Listener1Assist.Listener1Model model) {
Logger.e("taskEnd", model);
playMp4(task.getFile());
mFile = task.getFile();
if (cause != null && cause != EndCause.COMPLETED) {
// CrashReport.postCatchedException(new RuntimeException("下载任务结束:" + cause == null ? "" : cause.name() + "_realCause:" + realCause == null ? "" : realCause.getMessage()));
}
}
});
// }
}
}
private void playMp4(File file) {
if (file != null) {
mBinding.playView.startPlay(file);
} else {
// showAnim();
// playMp4(file);
// CrashReport.postCatchedException(new RuntimeException("播放MP4失败:File is null"));
}
}
private void handleSVGAComplete(SVGAVideoEntity videoItem, String url) {
// if (isDestroyed || svgaSurface == null) return;
try {
// 缓存实体(使用弱引用)
svgaCache.put(url, new WeakReference<>(videoItem));
SVGADrawable drawable = new SVGADrawable(videoItem, new SVGADynamicEntity());
svgaSurface.setImageDrawable(drawable);
svgaSurface.setCallback(new SVGACallback() {
@Override
public void onStep(int i, double v) {
}
@Override
public void onRepeat() {
}
@Override
public void onPause() {
}
@Override
public void onFinished() {
// if (isDestroyed) return;
if (Looper.myLooper() != Looper.getMainLooper()) {
mainHandler.post(() -> {
isPlaying = false;
playNextFromQueue();
});
} else {
isPlaying = false;
playNextFromQueue();
}
}
});
svgaSurface.startAnimation();
} catch (Exception e) {
LogUtils.e(TAG, "Error handling SVGA completion: " + e.getMessage());
isPlaying = false;
playNextFromQueue();
}
}
private void playCachedSVGA(SVGAVideoEntity videoItem) {
// if (isDestroyed || svgaSurface == null) return;
try {
SVGADrawable drawable = new SVGADrawable(videoItem, new SVGADynamicEntity());
svgaSurface.setImageDrawable(drawable);
svgaSurface.setCallback(new SVGACallback() {
@Override
public void onStep(int i, double v) {
}
@Override
public void onRepeat() {
}
@Override
public void onPause() {
}
@Override
public void onFinished() {
// if (isDestroyed) return;
if (Looper.myLooper() != Looper.getMainLooper()) {
mainHandler.post(() -> {
isPlaying = false;
// 添加延迟确保状态更新
mainHandler.postDelayed(AvatarFrameView.this::playNextFromQueue, 50);
// playNextFromQueue();
});
} else {
isPlaying = false;
// playNextFromQueue();
mainHandler.postDelayed(AvatarFrameView.this::playNextFromQueue, 50);
}
}
});
svgaSurface.startAnimation();
} catch (Exception e) {
LogUtils.e(TAG, "Error playing cached SVGA: " + e.getMessage());
isPlaying = false;
mainHandler.postDelayed(AvatarFrameView.this::playNextFromQueue, 50);
// playNextFromQueue();
}
}
private void loadNewSVGA(String url) {
// if (isDestroyed) return;
try {
new SVGAParser(getContext()).parse(new URL(url), new SVGAParser.ParseCompletion() {
@Override
public void onComplete(SVGAVideoEntity videoItem) {
// if (isDestroyed) return;
if (Looper.myLooper() != Looper.getMainLooper()) {
mainHandler.post(() -> handleSVGAComplete(videoItem, url));
} else {
handleSVGAComplete(videoItem, url);
}
}
@Override
public void onError() {
// if (isDestroyed) return;
if (Looper.myLooper() != Looper.getMainLooper()) {
mainHandler.post(() -> {
isPlaying = false;
playNextFromQueue();
});
} else {
isPlaying = false;
playNextFromQueue();
}
}
});
} catch (Exception e) {
LogUtils.e(TAG, "Error parsing SVGA: " + e.getMessage());
isPlaying = false;
playNextFromQueue();
}
}
private void loadSVGA(String url) {
// if (isDestroyed || svgaSurface == null) return;
if (Looper.myLooper() != Looper.getMainLooper()) {
mainHandler.post(() -> loadSVGA(url));
return;
}
try {
svgaSurface.setVisibility(View.VISIBLE);
// 检查缓存
WeakReference<SVGAVideoEntity> cachedRef = svgaCache.get(url);
SVGAVideoEntity cachedEntity = cachedRef != null ? cachedRef.get() : null;
if (cachedEntity != null) {
// 使用缓存的实体
playCachedSVGA(cachedEntity);
} else {
// 加载新的SVGA
loadNewSVGA(url);
}
} catch (Exception e) {
LogUtils.e(TAG, "Error loading SVGA: " + e.getMessage());
isPlaying = false;
playNextFromQueue();
}
svgaSurface.setVisibility(View.VISIBLE);
try {
new SVGAParser(getContext()).parse(new URL(url), new SVGAParser.ParseCompletion() {
@Override
public void onComplete(SVGAVideoEntity videoItem) {
SVGADrawable drawable = new SVGADrawable(videoItem, new SVGADynamicEntity());
svgaSurface.setImageDrawable(drawable);
svgaSurface.setCallback(new SVGACallback() {
@Override
public void onStep(int i, double v) {
}
@Override
public void onRepeat() {
}
@Override
public void onPause() {
}
@Override
public void onFinished() {
isPlaying = false;
playNextFromQueue();
}
});
// svgaSurface.setCallback(new SVGAImageViewCallback() {
// @Override
// public void onAnimationFinished() {
// isPlaying = false;
// playNextFromQueue();
// }
// });
svgaSurface.startAnimation();
}
@Override
public void onError() {
isPlaying = false;
playNextFromQueue();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
private void loadMP4(String url) {
svgaSurface.setVisibility(View.GONE);
playerView.setVisibility(View.GONE);
glSurfaceView.setVisibility(View.VISIBLE);
glSurfaceView.onResume();
glSurfaceView.requestRender();
// 使用 post 确保 GLSurfaceView 已完成 layout
glSurfaceView.post(() -> {
Log.d("@@@", "GLSurfaceView size after layout: " + glSurfaceView.getWidth() + "x" + glSurfaceView.getHeight());
glSurfaceView.onResume();
glSurfaceView.requestRender();
renderer.setOnSurfaceTextureReadyListener(surfaceTexture -> {
mainHandler.post(() -> {
Surface surface = new Surface(surfaceTexture);
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(url));
exoPlayer.setMediaItem(mediaItem);
exoPlayer.setVideoSurface(surface);
exoPlayer.prepare();
exoPlayer.play();
Log.d("@@@", "ExoPlayer state after play: " + exoPlayer.getPlaybackState());
});
});
});
}
private void clearPrevious() {
// if (isDestroyed) return;
// 确保在主线程中执行
if (Looper.myLooper() != Looper.getMainLooper()) {
mainHandler.post(() -> {
// if (!isDestroyed) {
clearPrevious();
// }
});
return;
}
try {
// 停止并清理 ExoPlayer
if (exoPlayer != null) {
try {
exoPlayer.stop(); // 这里可能会在错误线程中调用
exoPlayer.clearVideoSurface();
} catch (Exception e) {
Logger.e("Error stopping ExoPlayer: " + e.getMessage());
// 如果在错误线程中,切换到主线程重试
mainHandler.post(() -> {
try {
if (exoPlayer != null) {
exoPlayer.stop();
exoPlayer.clearVideoSurface();
}
} catch (Exception ex) {
Logger.e("Error stopping ExoPlayer on main thread: " + ex.getMessage());
}
});
}
}
// 停止并清理 SVGA 动画
if (svgaSurface != null && svgaSurface.getDrawable() instanceof SVGADrawable) {
SVGADrawable drawable = (SVGADrawable) svgaSurface.getDrawable();
if (drawable != null) {
drawable.stop();
// 清理 SVGADrawable 中的资源
svgaSurface.clearAnimation();
svgaSurface.setImageDrawable(null);
}
}
// 隐藏所有视图
if (playerView != null) playerView.setVisibility(View.GONE);
if (svgaSurface != null) svgaSurface.setVisibility(View.GONE);
mBinding.playView.setVisibility(View.GONE);
// 停止播放器
if (mBinding.playView != null) {
mBinding.playView.stopPlay();
}
} catch (Exception e) {
LogUtils.e(TAG, "Error in clearPrevious: " + e.getMessage());
}
// if (svgaSurface.getDrawable() instanceof SVGADrawable) {
// ((SVGADrawable) svgaSurface.getDrawable()).stop();
// }
// if (playerView != null) playerView.setVisibility(View.GONE);
// if (svgaSurface != null) svgaSurface.setVisibility(View.GONE);
// if (glSurfaceView != null) glSurfaceView.setVisibility(View.GONE);
// mBinding.playView.setVisibility(View.GONE);
}
// 简单的 LRU Cache 实现
private static class LruCache<K, V> extends LinkedHashMap<K, V> {
private final int maxSize;
public LruCache(int maxSize) {
super(16, 0.75f, true);
this.maxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > maxSize;
}
}
/**
* 释放所有资源
*/
private void releaseResources() {
LogUtils.d(TAG, "Releasing all resources");
// if (isDestroyed) return;
try {
// 清理 SVGA 资源
if (svgaSurface != null && svgaSurface.getDrawable() instanceof SVGADrawable) {
SVGADrawable drawable = (SVGADrawable) svgaSurface.getDrawable();
if (drawable != null) {
try {
drawable.stop();
svgaSurface.clearAnimation();
svgaSurface.setImageDrawable(null);
} catch (Exception e) {
LogUtils.e(TAG, "Error releasing SVGA resources: " + e.getMessage());
}
}
}
// 停止并清理播放器
if (mBinding != null && mBinding.playView != null) {
mBinding.playView.stopPlay();
}
// 清理 ExoPlayer 资源
if (exoPlayer != null) {
try {
exoPlayer.stop();
exoPlayer.clearVideoSurface();
} catch (Exception e) {
LogUtils.e(TAG, "Error releasing ExoPlayer resources: " + e.getMessage());
}
}
} catch (Exception e) {
LogUtils.e(TAG, "Error in releaseResources: " + e.getMessage());
}
}
/**
* 公共释放方法,用于外部主动释放资源
*/
public void release() {
Logger.d("AvatarFrameView", "Public release called");
// if (isDestroyed) return;
// 确保在主线程中执行
if (Looper.myLooper() != Looper.getMainLooper()) {
mainHandler.post(this::release);
return;
}
// isDestroyed = true;
try {
// 清空播放队列
clearQueue();
// 释放所有资源
releaseResources();
// 释放 ExoPlayer
if (exoPlayer != null) {
try {
exoPlayer.stop();
exoPlayer.release();
} catch (Exception e) {
LogUtils.e(TAG, "Error releasing ExoPlayer: " + e.getMessage());
}
exoPlayer = null;
}
// 清理 PlayerView
if (playerView != null) {
try {
playerView.setPlayer(null);
} catch (Exception e) {
LogUtils.e(TAG, "Error releasing PlayerView: " + e.getMessage());
}
playerView = null;
}
// 清理 SVGAImageView
if (svgaSurface != null) {
try {
svgaSurface.pauseAnimation();
svgaSurface.clearAnimation();
svgaSurface.setImageDrawable(null);
} catch (Exception e) {
LogUtils.e(TAG, "Error releasing SVGAImageView: " + e.getMessage());
}
svgaSurface = null;
}
// 清理 binding
if (mBinding != null) {
mBinding = null;
}
// 清理队列
playQueue.clear();
} catch (Exception e) {
LogUtils.e(TAG, "Error in AvatarFrameView release: " + e.getMessage());
} finally {
// 建议进行垃圾回收
MemoryOptimizationUtils.forceGC();
}
}
public void clearQueue() {
// if (isDestroyed) return;
playQueue.clear();
isPlaying = false;
// 清理当前正在播放的内容
clearPrevious();
}
private static class PlayItem {
String url;
int type;
PlayItem(String url, int type) {
this.url = url;
this.type = type;
}
}
/**
* 关闭特效
*/
public void closeEffect() {
// 清空队列
clearQueue();
// 释放资源
releaseResources();
//清空队列
// playQueue.clear();
//关闭动画
// if (mBinding.image != null && isPlaying && mBinding.image.isAnimating()) {
// mBinding.image.setAnimation(null);
// mBinding.image.clearAnimation();
// mBinding.image.stopAnimation();
// }
}
}

View File

@@ -0,0 +1,762 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.os.CountDownTimer;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.opensource.svgaplayer.SVGAImageView;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.base.RoomRollModel;
import com.xscm.moduleutil.bean.FaceBean;
import com.xscm.moduleutil.bean.UserOnlineStatusBean;
import com.xscm.moduleutil.bean.room.ClosePhone;
import com.xscm.moduleutil.bean.room.RoomClearCardiacAllModel;
import com.xscm.moduleutil.bean.room.RoomClearCardiacModel;
import com.xscm.moduleutil.bean.room.RoomClosePitModel;
import com.xscm.moduleutil.bean.room.RoomCountDownModel;
import com.xscm.moduleutil.bean.room.RoomDownWheatModel;
import com.xscm.moduleutil.bean.room.RoomGiveGiftModel;
import com.xscm.moduleutil.bean.room.RoomPitBean;
import com.xscm.moduleutil.bean.room.RoomWheatModel;
import com.xscm.moduleutil.event.RoomBanWheatEvent;
import com.xscm.moduleutil.event.RoomBeckoningEvent;
import com.xscm.moduleutil.event.RoomCardiacValueChangedEvent;
import com.xscm.moduleutil.event.RoomFaceEvent;
import com.xscm.moduleutil.interfaces.IBaseWheat;
import com.xscm.moduleutil.interfaces.SoundLevelUpdateListener;
import com.xscm.moduleutil.rtc.AgoraManager;
import com.xscm.moduleutil.utils.SpUtil;
import com.xscm.moduleutil.utils.logger.Logger;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
public abstract class BaseWheatView extends ConstraintLayout implements IBaseWheat {
public ImageView mRiv;
public ImageView mIvGift;
public WheatCharmView mCharmView;
public TextView mTvName;
public ImageView mIvSex;
public AvatarFrameView mIvFrame;
public SVGAImageView 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
CountDownTimer mCountDownTimer;
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 BaseWheatView(Context context) {
this(context, null, 0);
}
public BaseWheatView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BaseWheatView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
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);
setClipChildren(false);
setClipToPadding(false);
oX = mIvGift.getX();
oY = mIvGift.getY();
initPit(context, attrs);
}
protected abstract void initPit(Context context, AttributeSet attrs);
protected abstract int getLayoutId();
protected abstract void setPitData(RoomPitBean bean);
protected float getTzbl() {
return 0.0f;
}
private @DrawableRes int mOriginImage = 0;
private @DrawableRes int mOriginNoImage = 0;
public @DrawableRes int getOriginImage() {
return mOriginImage;
}
public @DrawableRes int getOriginNoImage() {
return mOriginNoImage;
}
public void setImageResource(@DrawableRes int image, @DrawableRes int noImage, CharSequence no) {
mOriginImage = image;
mOriginNoImage = noImage;
mRiv.setImageResource(mOriginImage);
if (mTvNo != null) {
mTvNo.setText(no);
mTvNo.setBackgroundResource(mOriginNoImage);
mTvNo.setVisibility(VISIBLE);
}
}
/**
* 设置麦位数据
*
* @param bean
*/
@Override
@Subscribe(threadMode = ThreadMode.MAIN)
public void setData(RoomPitBean bean) {
if (!pitNumber.equals(bean.getPit_number())) {
return;
}
this.pitBean = bean;
this.roomId = bean.getRoom_id();
countDownTime(bean.getCount_down());
setCardiac(pitBean.getCharm(), getTzbl());
setPitData(bean);
// if (bean.getIs_online() == 0 &&bean.getUser_id() != null && !bean.getUser_id().equals("0") && !bean.getUser_id().isEmpty()) {
// iv_on_line.setVisibility(VISIBLE);
// } else {
// iv_on_line.setVisibility(GONE);
// }
// if (isOn() && bean.getBall_state() == 1) {
// gameImgView.startGame();
// } else {
// gameImgView.overGame();
// }
// String userOnlineStatusBean = SpUtil.getUserOnline();
// if (userOnlineStatusBean!= null){
//// if (userOnlineStatusBean.getUser_id().equals(bean.getUser_id()) && userOnlineStatusBean.getIs_online() == 1){
//// iv_on_line.setVisibility(GONE);
//// }else {
//// iv_on_line.setVisibility(VISIBLE);
//// }
// }
//心动值
//显示心动
if ("1".equals(pitBean.getShutup())) {
mIvShutup.setVisibility(VISIBLE);
} else {
mIvShutup.setVisibility(GONE);
}
//自动调节麦位波纹
// if (!TextUtils.isEmpty(bean.getDress_picture())) {
mIvRipple.setScaleX(1.1f);
mIvRipple.setScaleY(1.1f);
// } else {
// mIvRipple.setScaleX(0.9f);
// mIvRipple.setScaleY(0.9f);
// }
if (pitNumber.equals("9")) {
iv_tag_type.setImageResource(R.mipmap.zc);
if (mRiv.getLayoutParams() instanceof ConstraintLayout.LayoutParams) {
ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) mRiv.getLayoutParams();
params.matchConstraintPercentWidth = 0.66f; // 设置为 52%
params.width = 0; // 必须设为 0dpMATCH_CONSTRAINT
mRiv.setLayoutParams(params);
}
} else if (pitNumber.equals("10")) {
iv_tag_type.setImageResource(R.mipmap.jb);
if (mRiv.getLayoutParams() instanceof ConstraintLayout.LayoutParams) {
ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) mRiv.getLayoutParams();
params.matchConstraintPercentWidth = 0.66f; // 设置为 52%
params.width = 0; // 必须设为 0dpMATCH_CONSTRAINT
mRiv.setLayoutParams(params);
}
} else if (pitNumber.equals("-1")) {
iv_tag_type.setImageResource(R.mipmap.mu_yc);
if (mRiv.getLayoutParams() instanceof ConstraintLayout.LayoutParams) {
ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) mRiv.getLayoutParams();
params.matchConstraintPercentWidth = 0.66f; // 设置为 52%
params.width = 0; // 必须设为 0dpMATCH_CONSTRAINT
mRiv.setLayoutParams(params);
}
} else if (pitNumber.equals("0")) {
iv_tag_type.setVisibility(GONE);
mIvShutup.setVisibility(VISIBLE);
} else if (pitNumber.equals("888")) {
iv_tag_type.setVisibility(GONE);
mIvShutup.setVisibility(GONE);
} else {
iv_tag_type.setVisibility(GONE);
}
AgoraManager.getInstance(getContext()).addSoundLevelListener(new SoundLevelUpdateListener() {
@Override
public void onRemoteSoundLevelUpdate(String userId, int soundLevel) {
if (userId.equals(pitBean.getUser_id())) {
if (soundLevel == 0) {
mIvRipple.post(() -> {
mIvRipple.stopAnimation();
mIvRipple.setVisibility(GONE);
});
mIvRipple.setVisibility(GONE);
} else {
mIvRipple.setVisibility(VISIBLE);
mIvRipple.post(() -> {
mIvRipple.startAnimation();
iv_on_line.setVisibility(GONE);
// mIvRipple.setSource(getResources().getResourceName(R.raw.ripple3695), 2);
});
}
// if (pitBean.getUser_id().equals(SpUtil.getUserId()) && closePhone) {
// mIvRipple.post(() -> {
// mIvRipple.setVisibility(GONE);
// });
// }
}
// else {
// mIvRipple.post(() -> {
// mIvRipple.setVisibility(GONE);
// });
// }
}
@Override
public void onLocalSoundLevelUpdate(int volume) {
if (volume == 0) {
mIvRipple.post(() -> {
mIvRipple.setVisibility(GONE);
});
} else {
mIvRipple.setVisibility(VISIBLE);
mIvRipple.post(() -> {
mIvRipple.startAnimation();
iv_on_line.setVisibility(GONE);
// mIvRipple.setSource(getResources().getResourceName(R.raw.ripple3695), 2);
});
}
}
@Override
public void userJoined(int userId, int elapsd) {
if (pitBean != null && pitBean.getUser_id() != null && !pitBean.getUser_id().equals("0")) {
if (pitBean.getUser_id().equals(userId + "")) {
iv_on_line.setVisibility(GONE);
}
}
}
@Override
public void userOffline(int userId, int reason) {
if (pitBean != null && pitBean.getUser_id() != null && !pitBean.getUser_id().equals("0")) {
if (pitBean.getUser_id().equals(userId + "")) {
iv_on_line.setVisibility(VISIBLE);
}
}else if (pitBean.getUser_id()==null || pitBean.getUser_id().equals("0") || pitBean.getUser_id().equals("")){
iv_on_line.setVisibility(GONE);
}
}
});
if (pitBean.getUser_id()==null || pitBean.getUser_id().equals("0") || pitBean.getUser_id().equals("") ){
iv_on_line.setVisibility(GONE);
}
}
/**
* 开始倒计时
*
* @param time
*/
public void countDownTime(int time) {
try {
if (time <= 0) {
setTime(0);
releaseCountDownTimer();
return;
}
releaseCountDownTimer();
mCountDownTimer = new CountDownTimer(time * 1000L, 1000L) {
@Override
public void onTick(long millisUntilFinished) {
int time1 = (int) (millisUntilFinished / 1000);
pitBean.setCount_down(time1);
setTime(time1);
}
@Override
public void onFinish() {
setTime(0);
}
};
mCountDownTimer.start();
} catch (Exception e) {
Logger.e("countDownTime", e);
}
}
public void setTime(int time) {
if (time == 0) {
tvTime.setText("");
tvTime.setVisibility(View.INVISIBLE);
} else {
tvTime.setText(String.format("%s'%s", time / 60, time % 60));
tvTime.setVisibility(View.VISIBLE);
}
}
// @Subscribe(threadMode = ThreadMode.MAIN)
public void setOnlineStatus(UserOnlineStatusBean isOnline) {
// iv_on_line.setVisibility(isOnline ? GONE : VISIBLE);
if (pitBean.getUser_id() != null && !pitBean.getUser_id().equals("0") && !pitBean.getUser_id().isEmpty()) {
//// for (UserOnlineStatusBean userOnlineStatus : userOnlineStatusBean) {
if (pitBean.getUser_id().equals(isOnline.getUser_id())) {
if (isOnline.getIs_online() == 1) {
iv_on_line.setVisibility(GONE);
} else {
iv_on_line.setVisibility(VISIBLE);
}
//// }
}
}
}
@Override
protected void onAttachedToWindow() {
showGiftAnim = true;
super.onAttachedToWindow();
}
@Override
protected void onDetachedFromWindow() {
showGiftAnim = false;
releaseCountDownTimer();
super.onDetachedFromWindow();
}
/**
* 释放倒计时
*/
private void releaseCountDownTimer() {
if (mCountDownTimer != null) {
mCountDownTimer.cancel();
mCountDownTimer = null;
}
}
/**
* 麦位是否有人
*
* @return
*/
@Override
public boolean isOn() {
return pitBean != null && !TextUtils.isEmpty(pitBean.getUser_id()) && !"0".equals(pitBean.getUser_id());
}
/**
* 显示麦位礼物动画
*
* @param listBean
*/
@Subscribe(threadMode = ThreadMode.MAIN)
@Override
public void showGift(RoomGiveGiftModel.GiftListBean listBean) {
if (!showGiftAnim) {
mIvGift.setVisibility(GONE);
return;
}
if (listBean.getUser_id() == null || !listBean.getUser_id().equals(pitBean.getUser_id())) {
return;
}
// ImageUtils.loadImageView(listBean.getPicture(), mIvGift);
WheatGiftAnim.addGift(mIvGift, listBean.getPicture());
}
/**
* 设置心动值
*
* @param rough_number
*/
@Override
public void setCardiac(String rough_number, float bl) {
if (mCharmView != null) {
pitBean.setCharm((rough_number != null && !rough_number.isEmpty()) ? rough_number : "0");
if (pitBean.getUser_id() == null || pitBean.getUser_id().equals("0") || pitBean.getUser_id().equals("")) {
// mCharmView.setVisibility(GONE);
mCharmView.setSex(pitBean.getSex(), pitBean.getUser_id(), pitBean.getCharm(), false);
} else {
mCharmView.setVisibility(VISIBLE);
mCharmView.setSex(pitBean.getSex(), pitBean.getUser_id(), pitBean.getCharm(), false);
}
//
// } else {
// try {
// String d = pitBean.getXin_dong();
// long xd = Long.parseLong(d);
// if (xd <= 0) {
// mCharmView.setSex(pitBean.getSex(), pitBean.getUser_id(), d, false);
// } else {
// float xxd = xd * bl;
// mCharmView.setSex(pitBean.getSex(), pitBean.getUser_id(), String.format(Locale.CHINESE, "%sx%.2f=%.2f", d, bl, xxd), true);
// }
// } catch (Exception e) {
// mCharmView.setSex(pitBean.getSex(), pitBean.getUser_id(), pitBean.getXin_dong(), false);
// }
// }
EventBus.getDefault().post(new RoomCardiacValueChangedEvent(pitNumber, pitBean.getCharm()));
}
}
/**
* 清空心动值
*/
@Override
public void clearCardiac() {
if (mCharmView != null) {
pitBean.setCharm("0");
mCharmView.setSex(pitBean.getSex(), pitBean.getUser_id(), pitBean.getCharm(), true);
EventBus.getDefault().post(new RoomCardiacValueChangedEvent(pitNumber, pitBean.getCharm()));
}
}
public int getCardiac() {
if (pitBean == null) {
return 0;
} else {
try {
return Integer.parseInt(pitBean.getCharm());
} catch (Throwable e) {
return 0;
}
}
}
public String getUserId() {
if (pitBean != null && pitBean.getUser_id() != null && !pitBean.getUser_id().equals("0")) {
return pitBean.getUser_id();
}
return "";
}
@Override
public void register(Object obj) {
EventBus.getDefault().register(this);
}
@Override
public void unRegister(Object obj) {
AgoraManager.getInstance(getContext()).removeSoundLevelListener(this);
EventBus.getDefault().unregister(this);
}
/**
* 心动值显示开关
*
* @param roomBeckoningEvent
*/
@Subscribe(threadMode = ThreadMode.MAIN)
@Override
public void subscribeMessages(RoomBeckoningEvent roomBeckoningEvent) {
if (roomId.equals(roomBeckoningEvent.getRoomId())) {
mCharmView.setVisibility(roomBeckoningEvent.isOpen() ? VISIBLE : INVISIBLE);
}
}
/**
* 心动值变化
*
* @param cardiacListBean
*/
@Subscribe(threadMode = ThreadMode.MAIN)
@Override
public void subscribeMessages(RoomGiveGiftModel.CardiacListBean cardiacListBean) {
if (!roomId.equals(cardiacListBean.getRoom_id())) {
return;
}
if (this.pitNumber.equals(cardiacListBean.getPit_number())) {
this.setCardiac(cardiacListBean.getXin_dong(), getTzbl());
}
}
/**
* 清空单个麦位心动值
*
* @param roomClearCardiacModel
*/
@Subscribe(threadMode = ThreadMode.MAIN)
@Override
public void subscribeMessages(RoomClearCardiacModel roomClearCardiacModel) {
if (!roomId.equals(roomClearCardiacModel.getRoom_id())) {
return;
}
if (this.pitNumber.equals(roomClearCardiacModel.getPit_number())) {
clearCardiac();
}
}
/**
* 清空所有心动值
*
* @param roomClearCardiacAllModel
*/
@Subscribe(threadMode = ThreadMode.MAIN)
@Override
public void subscribeMessages(RoomClearCardiacAllModel roomClearCardiacAllModel) {
if (!roomId.equals(roomClearCardiacAllModel.getRoom_id())) {
return;
}
clearCardiac();
}
/**
* 上麦
*
* @param roomWheatModel
*/
@Subscribe(threadMode = ThreadMode.MAIN)
@Override
public void subscribeMessages(RoomWheatModel roomWheatModel) {
if (!roomId.equals(roomWheatModel.getRoom_id()) || !pitNumber.equals(roomWheatModel.getPit_number())) {
return;
}
pitBean.setNickname(roomWheatModel.getNickname());
pitBean.setHead_picture(roomWheatModel.getHead_picture());
pitBean.setBanned(roomWheatModel.getBanned());
pitBean.setUser_id(roomWheatModel.getUser_id());
pitBean.setDress_picture(roomWheatModel.getDress_picture());
pitBean.setSex(roomWheatModel.getSex());
pitBean.setBall_state(roomWheatModel.getBall_state());
pitBean.setPit_number(roomWheatModel.getPit_number());
pitBean.setCharm(roomWheatModel.getXin_dong());
setData(pitBean);
}
/**
* 下麦
*
* @param roomDownWheatModel
*/
@Subscribe(threadMode = ThreadMode.MAIN)
@Override
public void subscribeMessages(RoomDownWheatModel roomDownWheatModel) {
if (!roomId.equals(roomDownWheatModel.getRoom_id()) || !pitNumber.equals(roomDownWheatModel.getPit_number())) {
return;
}
closePhone = SpUtil.getUserId() == roomDownWheatModel.getUser_id();
pitBean.setUser_id("0");
pitBean.setPit_number(roomDownWheatModel.getPit_number());
setData(pitBean);
}
/**
* 倒计时
*
* @param roomCountDownModel
*/
@Subscribe(threadMode = ThreadMode.MAIN)
@Override
public void subscribeMessages(RoomCountDownModel roomCountDownModel) {
if (!roomId.equals(roomCountDownModel.getRoom_id()) || !pitNumber.equals(roomCountDownModel.getPit_number())) {
return;
}
pitBean.setCount_down(roomCountDownModel.getSeconds());
countDownTime(roomCountDownModel.getSeconds());
}
/**
* 禁麦
*
* @param roomBanWheatEvent
*/
@Subscribe(threadMode = ThreadMode.MAIN)
@Override
public void subscribeMessages(RoomBanWheatEvent roomBanWheatEvent) {
if (!roomId.equals(roomBanWheatEvent.getRoomId()) || !pitNumber.equals(roomBanWheatEvent.getPit_number())) {
return;
}
pitBean.setShutup(roomBanWheatEvent.isBanWheat() ? "1" : "2");
setData(pitBean);
}
/**
* 锁麦
*
* @param roomClosePitModel
*/
@Subscribe(threadMode = ThreadMode.MAIN)
@Override
public void subscribeMessages(RoomClosePitModel roomClosePitModel) {
if (!roomId.equals(roomClosePitModel.getRoom_id()) || !pitNumber.equals(roomClosePitModel.getPit_number())) {
return;
}
pitBean.setState(roomClosePitModel.getAction());
//麦位上锁
setData(pitBean);
}
/**
* 用户关闭麦克风
*
* @param closePhone
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void subscribeMessages(ClosePhone closePhone) {
if (pitBean.getUser_id().equals(SpUtil.getUserId())) {
this.closePhone = closePhone.isClosePhone();
}
}
/**
* 麦位是否被锁
*
* @return
*/
@Override
public boolean isLocked() {
return "1".equals(pitBean.getIs_lock());
}
public boolean isMute() {
if (pitBean != null) {
if (pitBean.getUser_id() != null && !pitBean.getUser_id().isEmpty() && "1".equals(pitBean.getIs_mute())) {
return true;
} else {
return false;
}
} else {
return false;
}
}
/**
* 表情
*
* @param roomFaceEvent
*/
@Subscribe(threadMode = ThreadMode.MAIN)
@Override
public void subscribeMessages(RoomFaceEvent roomFaceEvent) {
if (!roomId.equals(roomFaceEvent.getRoom_id()) || !pitNumber.equals(roomFaceEvent.getPit_number())) {
return;
}
mIvFace.addData(new FaceBean(roomFaceEvent.getSpecial(), roomFaceEvent.getTime(), 1));
}
/**
* 抽签
*
* @param roomRollModel
*/
@Subscribe(threadMode = ThreadMode.MAIN)
@Override
public void subscribeMessages(RoomRollModel roomRollModel) {
if (!roomId.equals(roomRollModel.getRoom_id()) || !pitNumber.equals(roomRollModel.getPit_number())) {
return;
}
mIvFace.addData(new FaceBean(roomRollModel.getNumber(), 2));
}
/**
* 球球大作战开球
*
* @param event
*/
/**
* 是否主持
*
* @return
*/
@Override
public boolean isHost() {
return WHEAT_HOST.equals(pitNumber);
}
// @Override
// public void onRemoteSoundLevelUpdate(String userId, int volume) {
// if (userId.equals(pitBean.getUser_id())) {
// if (volume == 0) {
// mIvRipple.post(() -> {
// mIvRipple.setVisibility(GONE);
// });
// } else {
// mIvRipple.post(() -> {
// if (!mIvRipple.isAnimating()) {
// mIvRipple.startAnimation();
// }
// mIvRipple.setVisibility(VISIBLE);
// });
// }
// if (pitBean.getUser_id().equals(SpUtil.getUserId()) && closePhone) {
// mIvRipple.post(() -> {
// mIvRipple.setVisibility(GONE);
// });
// }
// } else if (userId.equals("0")) {
// mIvRipple.post(() -> {
// mIvRipple.setVisibility(GONE);
// });
// }
// }
}

View File

@@ -0,0 +1,109 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.utils.ImageUtils;
/**
* ProjectName: isolated-island
* Package: com.qpyy.libcommon.widget
* Description: 靓号View(有靓号一定有id特效(靓字+id)有id特效不一是靓号(只显示id))
* Author: 姚闻达
* CreateDate: 2021/3/16
* UpdateUser: 更新者
* UpdateDate: 2021/3/16 9:59
* UpdateRemark: 更新说明
* Version: 1.0
*/
public class BeautifulNameView extends LinearLayout {
private ImageView ivIcon;
private ScanTextView stvBeautiful;
private int textColor;
private int lightColor;
private float textSize;
private int speed;
public BeautifulNameView(Context context) {
this(context, null);
}
public BeautifulNameView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public BeautifulNameView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(@Nullable AttributeSet attrs) {
LayoutInflater.from(getContext()).inflate(R.layout.view_beautiful_name, this, true);
ivIcon = findViewById(R.id.iv_liang);
stvBeautiful = findViewById(R.id.stv_beautiful_value);
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.BeautifulNameView);
textColor = typedArray.getInt(R.styleable.BeautifulNameView_textColorc, Color.WHITE);
lightColor = typedArray.getInt(R.styleable.BeautifulNameView_lightColor, Color.WHITE);
textSize = typedArray.getDimension(R.styleable.BeautifulNameView_textSizec, 12);
speed = typedArray.getInt(R.styleable.BeautifulNameView_speed, 50);
typedArray.recycle();
stvBeautiful.setTextColor(textColor);
stvBeautiful.setLightColor(lightColor);
stvBeautiful.setTextSize(textSize);
stvBeautiful.setSpeed(speed);
ImageUtils.loadRes(R.mipmap.me_icon_liang, ivIcon);
}
public void setImg(String url) {
if (!TextUtils.isEmpty(url)) {
ImageUtils.loadIcon(url, ivIcon);
}
}
public void setImg(int resId) {
ImageUtils.loadRes(resId, ivIcon);
}
public void setImgVisible(boolean visible) {
ivIcon.setVisibility(visible ? VISIBLE : GONE);
}
public void setTextColor(String color) {
if (TextUtils.isEmpty(color) || !color.startsWith("#")) {
return;
}
stvBeautiful.setTextColor(Color.parseColor(color));
}
public void setTextColor(int color) {
stvBeautiful.setTextColor(color);
}
public void setTextSize(float textSize) {
stvBeautiful.setTextSize(textSize);
}
public void setText(String value) {
if (!TextUtils.isEmpty(value)) {
stvBeautiful.setText(value);
}
}
/**
* 只有服务端返回颜色(id_color字段)才播动效
* @param play
*/
public void setPlay(boolean play) {
stvBeautiful.setScan(play);
}
}

View File

@@ -0,0 +1,122 @@
package com.xscm.moduleutil.widget
import android.graphics.SurfaceTexture
import android.opengl.GLES20
import android.opengl.Matrix
class ChannelSplitRenderer {
private val vertexShaderCode = """
attribute vec4 aPosition;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
void main() {
gl_Position = aPosition;
vTexCoord = aTexCoord;
}
"""
private val fragmentShaderCode = """
precision mediump float;
uniform sampler2D uTexture;
varying vec2 vTexCoord;
void main() {
// 只使用左半部分作为最终颜色
vec2 leftCoord = vec2(vTexCoord.x * 0.5, vTexCoord.y);
vec4 color = texture2D(uTexture, leftCoord);
// 设置 alpha 为 1.0 表示完全不透明,或根据需求设为 0.0 表示全透明
gl_FragColor = vec4(color.rgb, 0.0); // 左通道颜色 + 不透明
}"""
private var program = 0
private var positionHandle = 0
private var texCoordHandle = 0
private var textureHandle = 0
private val projectionMatrix = FloatArray(16)
private val modelMatrix = FloatArray(16)
fun onSurfaceCreated(surface: SurfaceTexture, width: Int, height: Int) {
// 初始化着色器
val vertexShader = ShaderUtils.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)
val fragmentShader = ShaderUtils.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode)
program = GLES20.glCreateProgram().also {
GLES20.glAttachShader(it, vertexShader)
GLES20.glAttachShader(it, fragmentShader)
GLES20.glLinkProgram(it)
}
positionHandle = GLES20.glGetAttribLocation(program, "aPosition")
texCoordHandle = GLES20.glGetAttribLocation(program, "aTexCoord")
textureHandle = GLES20.glGetUniformLocation(program, "uTexture")
// 初始化矩阵
Matrix.setIdentityM(projectionMatrix, 0)
Matrix.setIdentityM(modelMatrix, 0)
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f)
GLES20.glEnable(GLES20.GL_BLEND)
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA)
}
fun onSurfaceChanged(width: Int, height: Int) {
GLES20.glViewport(0, 0, width, height)
}
fun onDrawFrame(textureId: Int) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
GLES20.glUseProgram(program)
// 定义顶点坐标(全屏)
val vertices = floatArrayOf(
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
-1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f
)
// 修改纹理坐标,只映射左半部分视频内容到左侧屏幕
val texCoords = floatArrayOf(
0.0f, 1.0f, // 左下角
0.5f, 1.0f, // 右下角(对应视频中间)
0.0f, 0.0f, // 左上角
0.5f, 0.0f // 右上角
)
val vertexBuffer = ShaderUtils.createFloatBuffer(vertices)
val texBuffer = ShaderUtils.createFloatBuffer(texCoords)
GLES20.glEnableVertexAttribArray(positionHandle)
GLES20.glVertexAttribPointer(
positionHandle, 3,
GLES20.GL_FLOAT, false,
0, vertexBuffer
)
GLES20.glEnableVertexAttribArray(texCoordHandle)
GLES20.glVertexAttribPointer(
texCoordHandle, 2,
GLES20.GL_FLOAT, false,
0, texBuffer
)
// 绑定纹理
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId)
GLES20.glUniform1i(textureHandle, 0)
// 绘制
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)
// 清理
GLES20.glDisableVertexAttribArray(positionHandle)
GLES20.glDisableVertexAttribArray(texCoordHandle)
}
fun release() {
GLES20.glDeleteProgram(program)
}
}

View File

@@ -0,0 +1,196 @@
package com.xscm.moduleutil.widget;
import android.graphics.SurfaceTexture;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.os.Build;
import android.util.Log;
import androidx.annotation.RequiresApi;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.opengles.GL10;
@RequiresApi(api = Build.VERSION_CODES.CUPCAKE)
public class ChannelSplitRenderer1 implements GLSurfaceView.Renderer {
private static final String VERTEX_SHADER =
"uniform mat4 uMVPMatrix;\n" +
"uniform mat4 uSTMatrix;\n" +
"attribute vec4 aPosition;\n" +
"attribute vec4 aTextureCoord;\n" +
"varying vec2 vTextureCoord;\n" +
"void main() {\n" +
" gl_Position = uMVPMatrix * aPosition;\n" +
" vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
"}\n";
private static final String FRAGMENT_SHADER =
"#extension GL_OES_EGL_image_external : require\n" +
"precision mediump float;\n" +
"varying vec2 vTextureCoord;\n" +
"uniform samplerExternalOES sTexture;\n" +
"void main() {\n" +
" vec2 uv = vTextureCoord;\n" +
" // 左边部分的 UV 坐标范围 (0.0, 0.1) 到 (0.6, 0.9)\n" +
" if (uv.x >= 0.0 && uv.x <= 0.6 && uv.y >= 0.1 && uv.y <= 0.9) {\n" +
" // 居中显示\n" +
" vec2 newUV = vec2(uv.x * (1.0 / 0.6), uv.y); // 水平方向拉伸\n" +
" gl_FragColor = texture2D(sTexture, newUV);\n" +
" } else {\n" +
" gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); // 设置为完全透明\n" +
" }\n" +
"}\n";
private int mProgram;
private int maPositionHandle;
private int maTextureHandle;
private int muMVPMatrixHandle;
private int muSTMatrixHandle;
private float[] mMVPMatrix = new float[16];
private float[] mSTMatrix = new float[16];
private SurfaceTexture surfaceTexture;
private int externalTextureId = -1;
private final float[] mTriangleVerticesData = {
// X, Y, Z, U, V
-1.0f, -1.0f, 0, 0.f, 0.f,
1.0f, -1.0f, 0, 1.f, 0.f,
-1.0f, 1.0f, 0, 0.f, 1.f,
1.0f, 1.0f, 0, 1.f, 1.f,
};
private FloatBuffer mTriangleVertices;
public ChannelSplitRenderer1() {
mTriangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mTriangleVertices.put(mTriangleVerticesData).position(0);
Matrix.setIdentityM(mSTMatrix, 0);
}
public interface OnSurfaceTextureReadyListener {
void onSurfaceTextureReady(SurfaceTexture surfaceTexture);
}
private OnSurfaceTextureReadyListener surfaceTextureReadyListener;
public void setOnSurfaceTextureReadyListener(OnSurfaceTextureReadyListener listener) {
this.surfaceTextureReadyListener = listener;
}
@Override
public void onSurfaceCreated(GL10 gl, javax.microedition.khronos.egl.EGLConfig config) {
Log.d("@@@@", "onDrawFrame called");
GLES20.glDisable(GLES20.GL_DEPTH_TEST);
GLES20.glDisable(GLES20.GL_STENCIL_TEST);
GLES20.glDisable(GLES20.GL_BLEND);
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 设置透明背景
int[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
externalTextureId = textures[0];
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, externalTextureId);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
surfaceTexture = new SurfaceTexture(externalTextureId);
mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
if (surfaceTextureReadyListener != null) {
surfaceTextureReadyListener.onSurfaceTextureReady(surfaceTexture);
}
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
private long lastTimestamp = -1;
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
if (surfaceTexture != null) {
surfaceTexture.updateTexImage();
long timestamp = surfaceTexture.getTimestamp();
if (timestamp != lastTimestamp) {
Log.d("@@@", "New frame received, timestamp: " + timestamp);
lastTimestamp = timestamp;
}
surfaceTexture.getTransformMatrix(mSTMatrix);
} else {
Log.e("@@@", "SurfaceTexture is null in onDrawFrame");
}
GLES20.glUseProgram(mProgram);
Matrix.setIdentityM(mMVPMatrix, 0);
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0);
mTriangleVertices.position(0);
GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false, 20, mTriangleVertices);
GLES20.glEnableVertexAttribArray(maPositionHandle);
mTriangleVertices.position(3);
GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false, 20, mTriangleVertices);
GLES20.glEnableVertexAttribArray(maTextureHandle);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, externalTextureId);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
}
public SurfaceTexture getSurfaceTexture() {
return surfaceTexture;
}
private int loadShader(int shaderType, String source) {
int shader = GLES20.glCreateShader(shaderType);
GLES20.glShaderSource(shader, source);
GLES20.glCompileShader(shader);
int[] compiled = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
return 0;
}
return shader;
}
private int createProgram(String vertexSource, String fragmentSource) {
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
int program = GLES20.glCreateProgram();
GLES20.glAttachShader(program, vertexShader);
GLES20.glAttachShader(program, pixelShader);
GLES20.glLinkProgram(program);
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] != GLES20.GL_TRUE) {
return 0;
}
return program;
}
}

View File

@@ -0,0 +1,40 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
/**
* 圆形头像
*/
public class CircularImage extends MaskedImage {
public CircularImage(Context paramContext) {
super(paramContext);
}
public CircularImage(Context paramContext, AttributeSet paramAttributeSet) {
super(paramContext, paramAttributeSet);
}
public CircularImage(Context paramContext, AttributeSet paramAttributeSet, int paramInt) {
super(paramContext, paramAttributeSet, paramInt);
}
public Bitmap createMask() {
int i = getWidth();
int j = getHeight();
Bitmap.Config localConfig = Bitmap.Config.ARGB_8888;
Bitmap localBitmap = Bitmap.createBitmap(i, j, localConfig);
Canvas localCanvas = new Canvas(localBitmap);
Paint localPaint = new Paint(1);
float f1 = getWidth();
float f2 = getHeight();
RectF localRectF = new RectF(0.0F, 0.0F, f1, f2);
localCanvas.drawOval(localRectF, localPaint);
return localBitmap;
}
}

View File

@@ -0,0 +1,211 @@
package com.xscm.moduleutil.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.ColorRes;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import com.xscm.moduleutil.R;
/**
* 圆形进度条控件
* Author: JueYes jueyes_1024@163.com
* Time: 2019-08-07 15:38
*/
public class CircularProgressView extends View {
private Paint mBackPaint, mProgPaint; // 绘制画笔
private RectF mRectF; // 绘制区域
private int[] mColorArray; // 圆环渐变色
private int mProgress; // 圆环进度(0-100)
public CircularProgressView(Context context) {
this(context, null);
}
public CircularProgressView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CircularProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
@SuppressLint("Recycle")
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircularProgressView);
// 初始化背景圆环画笔
mBackPaint = new Paint();
mBackPaint.setStyle(Paint.Style.STROKE); // 只描边,不填充
mBackPaint.setStrokeCap(Paint.Cap.ROUND); // 设置圆角
mBackPaint.setAntiAlias(true); // 设置抗锯齿
mBackPaint.setDither(true); // 设置抖动
mBackPaint.setStrokeWidth(typedArray.getDimension(R.styleable.CircularProgressView_backWidth, 5));
mBackPaint.setColor(typedArray.getColor(R.styleable.CircularProgressView_backColor, Color.LTGRAY));
// 初始化进度圆环画笔
mProgPaint = new Paint();
mProgPaint.setStyle(Paint.Style.STROKE); // 只描边,不填充
mProgPaint.setStrokeCap(Paint.Cap.ROUND); // 设置圆角
mProgPaint.setAntiAlias(true); // 设置抗锯齿
mProgPaint.setDither(true); // 设置抖动
mProgPaint.setStrokeWidth(typedArray.getDimension(R.styleable.CircularProgressView_progWidth, 10));
mProgPaint.setColor(typedArray.getColor(R.styleable.CircularProgressView_progColor, Color.BLUE));
// 初始化进度圆环渐变色
int startColor = typedArray.getColor(R.styleable.CircularProgressView_progStartColor, -1);
int firstColor = typedArray.getColor(R.styleable.CircularProgressView_progFirstColor, -1);
if (startColor != -1 && firstColor != -1) mColorArray = new int[]{startColor, firstColor};
else mColorArray = null;
// 初始化进度
mProgress = typedArray.getInteger(R.styleable.CircularProgressView_progressC, 0);
typedArray.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int viewWide = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
int viewHigh = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
int mRectLength = (int) ((viewWide > viewHigh ? viewHigh : viewWide) - (mBackPaint.getStrokeWidth() > mProgPaint.getStrokeWidth() ? mBackPaint.getStrokeWidth() : mProgPaint.getStrokeWidth()));
int mRectL = getPaddingLeft() + (viewWide - mRectLength) / 2;
int mRectT = getPaddingTop() + (viewHigh - mRectLength) / 2;
mRectF = new RectF(mRectL, mRectT, mRectL + mRectLength, mRectT + mRectLength);
// 设置进度圆环渐变色
if (mColorArray != null && mColorArray.length > 1)
mProgPaint.setShader(new LinearGradient(0, 0, 0, getMeasuredWidth(), mColorArray, null, Shader.TileMode.MIRROR));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawArc(mRectF, 0, 360, false, mBackPaint);
canvas.drawArc(mRectF, 275, 360 * mProgress / 1000, false, mProgPaint);
}
// ---------------------------------------------------------------------------------------------
/**
* 获取当前进度
*
* @return 当前进度0-100
*/
public int getProgress() {
return mProgress;
}
/**
* 设置当前进度
*
* @param progress 当前进度0-100
*/
public void setProgress(int progress) {
this.mProgress = progress;
invalidate();
}
// /**
// * 设置当前进度并展示进度动画。如果动画时间小于等于0则不展示动画
// *
// * @param progress 当前进度0-100
// * @param animTime 动画时间(毫秒)
// */
// public void setProgress(int progress, long animTime) {
// if (animTime <= 0) setProgress(progress);
// else {
// ValueAnimator animator = ValueAnimator.ofInt(mProgress, progress);
// animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
// @Override
// public void onAnimationUpdate(ValueAnimator animation) {
// mProgress = (int) animation.getAnimatedValue();
// invalidate();
// }
// });
// animator.setInterpolator(new OvershootInterpolator());
// animator.setDuration(animTime);
// animator.start();
// }
// }
/**
* 设置背景圆环宽度
*
* @param width 背景圆环宽度
*/
public void setBackWidth(int width) {
mBackPaint.setStrokeWidth(width);
invalidate();
}
/**
* 设置背景圆环颜色
*
* @param color 背景圆环颜色
*/
public void setBackColor(@ColorRes int color) {
mBackPaint.setColor(ContextCompat.getColor(getContext(), color));
invalidate();
}
/**
* 设置进度圆环宽度
*
* @param width 进度圆环宽度
*/
public void setProgWidth(int width) {
mProgPaint.setStrokeWidth(width);
invalidate();
}
/**
* 设置进度圆环颜色
*
* @param color 景圆环颜色
*/
public void setProgColor(@ColorRes int color) {
mProgPaint.setColor(ContextCompat.getColor(getContext(), color));
mProgPaint.setShader(null);
invalidate();
}
/**
* 设置进度圆环颜色(支持渐变色)
*
* @param startColor 进度圆环开始颜色
* @param firstColor 进度圆环结束颜色
*/
public void setProgColor(@ColorRes int startColor, @ColorRes int firstColor) {
mColorArray = new int[]{ContextCompat.getColor(getContext(), startColor), ContextCompat.getColor(getContext(), firstColor)};
mProgPaint.setShader(new LinearGradient(0, 0, 0, getMeasuredWidth(), mColorArray, null, Shader.TileMode.MIRROR));
invalidate();
}
/**
* 设置进度圆环颜色(支持渐变色)
*
* @param colorArray 渐变色集合
*/
public void setProgColor(@ColorRes int[] colorArray) {
if (colorArray == null || colorArray.length < 2) return;
mColorArray = new int[colorArray.length];
for (int index = 0; index < colorArray.length; index++)
mColorArray[index] = ContextCompat.getColor(getContext(), colorArray[index]);
mProgPaint.setShader(new LinearGradient(0, 0, 0, getMeasuredWidth(), mColorArray, null, Shader.TileMode.MIRROR));
invalidate();
}
}

View File

@@ -0,0 +1,144 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.appcompat.widget.AppCompatEditText;
import com.xscm.moduleutil.R;
/**
* 带删除按钮的editText
* Created by air on 2017/1/12.
* updated by air on 2017/3/20
*/
public class ClearEditText extends AppCompatEditText implements View.OnFocusChangeListener, TextWatcher {
//EditText右侧的删除按钮
private Drawable mClearDrawable;
private boolean hasFocus;
private onFocusListener onFocusListener;
public ClearEditText(Context context) {
this(context, null);
}
public ClearEditText(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.editTextStyle);
}
public ClearEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
//获取EditText的DrawableRight,假如没有设置我们就使用默认的图片
mClearDrawable = getCompoundDrawables()[2];
if (mClearDrawable == null) {
mClearDrawable = getResources().getDrawable(R.mipmap.common_et_clear_icon);
}
mClearDrawable.setBounds(0, 0, mClearDrawable.getIntrinsicWidth(),
mClearDrawable.getIntrinsicHeight());
// 默认设置隐藏图标
setClearIconVisible(false);
// 设置焦点改变的监听
setOnFocusChangeListener(this);
// 设置输入框里面内容发生改变的监听
addTextChangedListener(this);
}
/* *
* @说明isInnerWidth, isInnerHeight为ture触摸点在删除图标之内则视为点击了删除图标
* event.getX() 获取相对应自身左上角的X坐标
* event.getY() 获取相对应自身左上角的Y坐标
* getWidth() 获取控件的宽度
* getHeight() 获取控件的高度
* getTotalPaddingRight() 获取删除图标左边缘到控件右边缘的距离
* getPaddingRight() 获取删除图标右边缘到控件右边缘的距离
* isInnerWidth:
* getWidth() - getTotalPaddingRight() 计算删除图标左边缘到控件左边缘的距离
* getWidth() - getPaddingRight() 计算删除图标右边缘到控件左边缘的距离
* isInnerHeight:
* distance 删除图标顶部边缘到控件顶部边缘的距离
* distance + height 删除图标底部边缘到控件顶部边缘的距离
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
if (getCompoundDrawables()[2] != null) {
int x = (int) event.getX();
int y = (int) event.getY();
Rect rect = getCompoundDrawables()[2].getBounds();
int height = rect.height();
int distance = (getHeight() - height) / 2;
boolean isInnerWidth = x > (getWidth() - getTotalPaddingRight()) && x < (getWidth() - getPaddingRight());
boolean isInnerHeight = y > distance && y < (distance + height);
if (isInnerWidth && isInnerHeight) {
this.setText("");
}
}
}
return super.onTouchEvent(event);
}
public void setClearIconVisible(boolean visible) {
Drawable right = visible ? mClearDrawable : null;
setCompoundDrawables(getCompoundDrawables()[0],
getCompoundDrawables()[1], right, getCompoundDrawables()[3]);
}
/**
* 当ClearEditText焦点发生变化的时候
* 输入长度为零,隐藏删除图标,否则,显示删除图标
*/
@Override
public void onFocusChange(View v, boolean hasFocus) {
this.hasFocus = hasFocus;
if (hasFocus) {
setClearIconVisible(getText().length() > 0);
if (onFocusListener != null) {
onFocusListener.onFocus();
}
} else {
setClearIconVisible(false);
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
}
@Override
public void onTextChanged(CharSequence s, int start, int count, int after) {
if (hasFocus) {
setClearIconVisible(s.length() > 0);
}
}
public interface onFocusListener {
void onFocus();
}
public void setOnFocusListener(ClearEditText.onFocusListener onFocusListener) {
this.onFocusListener = onFocusListener;
}
}

View File

@@ -0,0 +1,96 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;
import androidx.annotation.ColorInt;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
/**
*@author
*@data
*@description: listview自定义间隔
*/
public class CommItemDecoration extends RecyclerView.ItemDecoration {
private static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
private static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private int mSpace = 1; //间隔
private Rect mRect = new Rect(0,0,0,0);
private Paint mPaint = new Paint();
private int mOrientation;
private CommItemDecoration(Context context, int orientation, @ColorInt int color, int space) {
mOrientation = orientation;
if(space>0){
mSpace = space;
}
mPaint.setColor(color);
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mSpace;
mRect.set(left,top,right,bottom);
c.drawRect(mRect,mPaint);
}
}
public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final int right = left + mSpace;
mRect.set(left, top, right, bottom);
c.drawRect(mRect,mPaint);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0,mSpace);
} else {
outRect.set(0, 0, mSpace, 0);
}
}
public static CommItemDecoration createVertical(Context context, @ColorInt int color, int height){
return new CommItemDecoration(context,VERTICAL_LIST,color,height);
}
public static CommItemDecoration createHorizontal(Context context, @ColorInt int color, int width){
return new CommItemDecoration(context,HORIZONTAL_LIST,color,width);
}
}

View File

@@ -0,0 +1,745 @@
package com.xscm.moduleutil.widget;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.text.TextUtils;
import com.alibaba.fastjson.JSON;
import com.blankj.utilcode.util.LogUtils;
import com.xscm.moduleutil.base.CommonAppContext;
import com.xscm.moduleutil.bean.ConfigBean;
import com.xscm.moduleutil.interfaces.CommonCallback;
import com.xscm.moduleutil.utils.DeviceUtils;
import com.xscm.moduleutil.utils.LanguageUtil;
import com.xscm.moduleutil.utils.SpUtil;
import com.xscm.moduleutil.utils.StringUtil;
import com.xscm.moduleutil.utils.WordUtil;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by cxf on 2017/8/4 .
*/
public class CommonAppConfig {
public static final String PACKAGE_NAME = "com.xscm.liveShort";
public static final String PACKAGE_NAME1 = "com.xscm.midi";
//Http请求头 Header
public static final Map<String, String> HEADER = new HashMap<>();
//域名
public static final String HOST = getHost();
public static final String EXTERNAL_PATH = getExternalPath();
public static final String DOWNLOAD_PATH = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath();
public static final String VIDEO_PATH = EXTERNAL_PATH + "/video/";
public static final String VIDEO_RECORD_PATH = VIDEO_PATH + "/record/";
public static final String VIDEO_RECORD_PARTS_PATH = VIDEO_RECORD_PATH + "/parts/";
//下载视频保存路径
public static final String VIDEO_DOWNLOAD_PATH = DOWNLOAD_PATH + "/video/";
//下载音乐保存路径
public static final String MUSIC_PATH = EXTERNAL_PATH + "/music/";
public static final String IMAGE_PATH = EXTERNAL_PATH + "/image/";
//下载图片保存路径
public static final String IMAGE_DOWNLOAD_PATH = DOWNLOAD_PATH + "/image/";
//log保存路径
public static final String LOG_PATH = EXTERNAL_PATH + "/log/";
public static final String GIF_PATH = EXTERNAL_PATH + "/gif/";
public static final String WATER_MARK_PATH = EXTERNAL_PATH + "/water/";
public static final String IM_SOUND = EXTERNAL_PATH + "/im_sound/";
public static final String IM_IMAGE = EXTERNAL_PATH + "/im_image/";
//腾讯IM appId
public static final int TX_IM_APP_ID = getMetaDataInt("TxIMAppId");
//QQ登录是否与PC端互通
public static final boolean QQ_LOGIN_WITH_PC = false;
//是否使用游戏
public static final boolean GAME_ENABLE = true;
//是否上下滑动切换直播间
public static final boolean LIVE_ROOM_SCROLL = true;
private static String getExternalPath() {
String outPath = null;
try {
File externalFilesDir = CommonAppContext.getInstance().getExternalFilesDir("qixing");
if (externalFilesDir != null) {
if (!externalFilesDir.exists()) {
externalFilesDir.mkdirs();
}
outPath = externalFilesDir.getAbsolutePath();
}
} catch (Exception e) {
outPath = null;
}
if (TextUtils.isEmpty(outPath)) {
outPath = CommonAppContext.getInstance().getFilesDir().getAbsolutePath();
}
return outPath;
}
private static CommonAppConfig sInstance;
private CommonAppConfig() {
}
public static CommonAppConfig getInstance() {
if (sInstance == null) {
synchronized (CommonAppConfig.class) {
if (sInstance == null) {
sInstance = new CommonAppConfig();
}
}
}
return sInstance;
}
private String mUid;
private String mToken;
private ConfigBean mConfig;
private double mLng;
private double mLat;
private String mProvince;//省
private String mCity;//市
private String mDistrict;//区
// private UserBean mUserBean;
// private UserBean mEmptyUserBean;//未登录游客
private String mVersion;
private boolean mLaunched;//App是否启动了
// private SparseArray<LevelBean> mLevelMap;
// private SparseArray<LevelBean> mAnchorLevelMap;
private String mGiftListJson;
private String mGiftDaoListJson;
private String mTxMapAppKey;//腾讯定位地图的AppKey
private String mTxMapAppSecret;//腾讯地图的AppSecret
private boolean mFrontGround;
private int mAppIconRes;
private String mAppName;
private Boolean mMhBeautyEnable;//是否使用美狐 true使用美狐 false 使用基础美颜
private String mDeviceId;
private Boolean mTeenagerType;//是否是青少年模式
private int mTopActivityType;//最上面的Activity的类型 1直播间 2消息
private boolean mShowLiveFloatWindow;//退出直播后是否显示直播悬浮窗
public String getUid() {
if (TextUtils.isEmpty(mUid)) {
String[] uidAndToken = SpUtil.getInstance()
.getMultiStringValue(new String[]{SpUtil.UID, SpUtil.TOKEN});
if (uidAndToken != null) {
if (!TextUtils.isEmpty(uidAndToken[0]) && !TextUtils.isEmpty(uidAndToken[1])) {
mUid = uidAndToken[0];
mToken = uidAndToken[1];
} else {
mUid = Constants.NOT_LOGIN_UID;
mToken = Constants.NOT_LOGIN_TOKEN;
}
} else {
mUid = Constants.NOT_LOGIN_UID;
mToken = Constants.NOT_LOGIN_TOKEN;
}
}
return mUid;
}
public String getToken() {
return mToken;
}
public boolean isLogin() {
return !Constants.NOT_LOGIN_UID.equals(getUid());
}
public String getCoinName() {
ConfigBean configBean = getConfig();
if (configBean != null) {
return configBean.getCoinName();
}
return Constants.DIAMONDS;
}
public String getVotesName() {
ConfigBean configBean = getConfig();
if (configBean != null) {
return configBean.getVotesName();
}
return Constants.VOTES;
}
public String getScoreName() {
ConfigBean configBean = getConfig();
if (configBean != null) {
return configBean.getScoreName();
}
return Constants.SCORE;
}
public ConfigBean getConfig() {
if (mConfig == null) {
String configString = SpUtil.getInstance().getStringValue(SpUtil.CONFIG);
if (!TextUtils.isEmpty(configString)) {
mConfig = JSON.parseObject(configString, ConfigBean.class);
}
}
return mConfig;
}
public void getConfig(CommonCallback<ConfigBean> callback) {
if (callback == null) {
return;
}
ConfigBean configBean = getConfig();
if (configBean != null) {
callback.callback(configBean);
} else {
// CommonHttpUtil.getConfig(callback);
}
}
public void setConfig(ConfigBean config) {
mConfig = config;
}
/**
* 经度
*/
public double getLng() {
if (mLng == 0) {
String lng = SpUtil.getInstance().getStringValue(SpUtil.LOCATION_LNG);
if (!TextUtils.isEmpty(lng)) {
try {
mLng = Double.parseDouble(lng);
} catch (Exception e) {
e.printStackTrace();
}
}
}
return mLng;
}
/**
* 纬度
*/
public double getLat() {
if (mLat == 0) {
String lat = SpUtil.getInstance().getStringValue(SpUtil.LOCATION_LAT);
if (!TextUtils.isEmpty(lat)) {
try {
mLat = Double.parseDouble(lat);
} catch (Exception e) {
e.printStackTrace();
}
}
}
return mLat;
}
/**
* 省
*/
public String getProvince() {
if (TextUtils.isEmpty(mProvince)) {
mProvince = SpUtil.getInstance().getStringValue(SpUtil.LOCATION_PROVINCE);
}
return mProvince == null ? "" : mProvince;
}
/**
* 市
*/
public String getCity() {
if (TextUtils.isEmpty(mCity)) {
mCity = SpUtil.getInstance().getStringValue(SpUtil.LOCATION_CITY);
}
return mCity == null ? "" : mCity;
}
/**
* 区
*/
public String getDistrict() {
if (TextUtils.isEmpty(mDistrict)) {
mDistrict = SpUtil.getInstance().getStringValue(SpUtil.LOCATION_DISTRICT);
}
return mDistrict == null ? "" : mDistrict;
}
// public void setUserBean(UserBean bean) {
// mUserBean = bean;
// }
// public UserBean getUserBean() {
// if (mUserBean == null) {
// String userBeanJson = SpUtil.getInstance().getStringValue(SpUtil.USER_INFO);
// if (!TextUtils.isEmpty(userBeanJson)) {
//// mUserBean = JSON.parseObject(userBeanJson, UserBean.class);
// }
// }
// if (mUserBean == null) {
//// mUserBean = getEmptyUserBean();
// }
// return mUserBean;
// }
/**
* 设置美狐是否可用
*/
public void setMhBeautyEnable(boolean mhBeautyEnable) {
mMhBeautyEnable = mhBeautyEnable;
SpUtil.getInstance().setBooleanValue(SpUtil.MH_BEAUTY_ENABLE, mhBeautyEnable);
}
/**
* 美狐是否可用
*/
public boolean isMhBeautyEnable() {
if (mMhBeautyEnable == null) {
mMhBeautyEnable = SpUtil.getInstance().getBooleanValue(SpUtil.MH_BEAUTY_ENABLE);
}
return mMhBeautyEnable;
}
public void setTeenagerType(boolean teenagerType) {
mTeenagerType = teenagerType;
SpUtil.getInstance().setBooleanValue(SpUtil.TEENAGER, teenagerType);
}
public boolean isTeenagerType() {
if (mTeenagerType == null) {
mTeenagerType = SpUtil.getInstance().getBooleanValue(SpUtil.TEENAGER);
}
return mTeenagerType;
}
public void setShowLiveFloatWindow(boolean showLiveFloatWindow) {
mShowLiveFloatWindow = showLiveFloatWindow;
}
public boolean isShowLiveFloatWindow() {
return mShowLiveFloatWindow;
}
/**
* 设置登录信息
*/
public void setLoginInfo(String uid, String token, boolean save) {
LogUtils.e("登录成功", "uid------>" + uid);
LogUtils.e("登录成功", "token------>" + token);
mUid = uid;
mToken = token;
if (save) {
SpUtil.getInstance().setBooleanValue(SpUtil.TEENAGER_SHOW, false);
}
Map<String, String> map = new HashMap<>();
map.put(SpUtil.UID, uid);
map.put(SpUtil.TOKEN, token);
SpUtil.getInstance().setMultiStringValue(map);
}
/**
* 清除登录信息
*/
public void clearLoginInfo() {
mUid = null;
mToken = null;
SpUtil.getInstance().removeValue(
SpUtil.UID, SpUtil.TOKEN, SpUtil.USER_INFO, SpUtil.TX_IM_USER_SIGN,
Constants.CASH_ACCOUNT_ID, Constants.CASH_ACCOUNT, Constants.CASH_ACCOUNT_TYPE,
SpUtil.TEENAGER, SpUtil.TEENAGER_SHOW
);
// CommonAppConfig.getInstance().setUserBean(getEmptyUserBean());
mShowLiveFloatWindow = false;
// EventBus.getDefault().post(new CloseFloatWindowEvent());
}
/**
* 未登录,游客
*/
// private UserBean getEmptyUserBean() {
// if (mEmptyUserBean == null) {
// UserBean bean = new UserBean();
// bean.setId(Constants.NOT_LOGIN_UID);
// bean.setUserNiceName("游客");
// bean.setLevel(1);
// bean.setLevelAnchor(1);
// String defaultAvatar = CommonAppConfig.HOST + "/default.jpg";
// bean.setAvatar(defaultAvatar);
// bean.setAvatarThumb(defaultAvatar);
// bean.setSignature(Constants.EMPTY_STRING);
// bean.setCoin(Constants.EMPTY_STRING);
// bean.setVotes(Constants.EMPTY_STRING);
// bean.setConsumption(Constants.EMPTY_STRING);
// bean.setVotestotal(Constants.EMPTY_STRING);
// bean.setProvince(Constants.EMPTY_STRING);
// bean.setCity(Constants.EMPTY_STRING);
// bean.setLocation(Constants.EMPTY_STRING);
// bean.setBirthday(Constants.EMPTY_STRING);
// bean.setVip(new UserBean.Vip());
// bean.setLiang(new UserBean.Liang());
// bean.setCar(new UserBean.Car());
// mEmptyUserBean = bean;
// }
// return mEmptyUserBean;
// }
/**
* 设置位置信息
*
* @param lng 经度
* @param lat 纬度
*/
public void setLngLat(double lng, double lat) {
mLng = lng;
mLat = lat;
Map<String, String> map = new HashMap<>();
map.put(SpUtil.LOCATION_LNG, String.valueOf(lng));
map.put(SpUtil.LOCATION_LAT, String.valueOf(lat));
SpUtil.getInstance().setMultiStringValue(map);
}
/**
* 设置位置信息
*
* @param lng 经度
* @param lat 纬度
* @param province 省
* @param city 市
*/
public void setLocationInfo(double lng, double lat, String province, String city, String district) {
mLng = lng;
mLat = lat;
mProvince = province;
mCity = city;
mDistrict = district;
Map<String, String> map = new HashMap<>();
map.put(SpUtil.LOCATION_LNG, String.valueOf(lng));
map.put(SpUtil.LOCATION_LAT, String.valueOf(lat));
map.put(SpUtil.LOCATION_PROVINCE, province);
map.put(SpUtil.LOCATION_CITY, city);
map.put(SpUtil.LOCATION_DISTRICT, district);
SpUtil.getInstance().setMultiStringValue(map);
}
/**
* 清除定位信息
*/
public void clearLocationInfo() {
mLng = 0;
mLat = 0;
mProvince = null;
mCity = null;
mDistrict = null;
SpUtil.getInstance().removeValue(
SpUtil.LOCATION_LNG,
SpUtil.LOCATION_LAT,
SpUtil.LOCATION_PROVINCE,
SpUtil.LOCATION_CITY,
SpUtil.LOCATION_DISTRICT);
}
/**
* 获取版本号
*/
public String getVersion() {
if (TextUtils.isEmpty(mVersion)) {
try {
PackageManager manager = CommonAppContext.getInstance().getPackageManager();
PackageInfo info = manager.getPackageInfo(PACKAGE_NAME1, 0);
mVersion = info.versionName;
} catch (Exception e) {
e.printStackTrace();
}
}
return mVersion;
}
public static boolean isYunBaoApp() {
if (!TextUtils.isEmpty(PACKAGE_NAME1)) {
return PACKAGE_NAME1.contains("com.xscm.qxlive");
}
return false;
}
/**
* 获取App名称
*/
public String getAppName() {
if (TextUtils.isEmpty(mAppName)) {
int res = CommonAppContext.getInstance().getResources().getIdentifier("app_name", "string", "com.xscm.qxlive");
mAppName = WordUtil.getString(res);
}
return mAppName;
}
/**
* 获取App图标的资源id
*/
public int getAppIconRes() {
if (mAppIconRes == 0) {
mAppIconRes = CommonAppContext.getInstance().getResources().getIdentifier("icon_app", "mipmap", "com.xscm.qxlive");
}
return mAppIconRes;
}
/**
* 获取MetaData中的腾讯定位地图的AppKey
*
* @return
*/
public String getTxMapAppKey() {
if (mTxMapAppKey == null) {
mTxMapAppKey = getMetaDataString("TencentMapSDK");
}
return mTxMapAppKey;
}
/**
* 获取MetaData中的腾讯定位地图的AppSecret
*
* @return
*/
public String getTxMapAppSecret() {
if (mTxMapAppSecret == null) {
mTxMapAppSecret = getMetaDataString("TencentMapAppSecret");
}
return mTxMapAppSecret;
}
public static String getMetaDataString(String key) {
String res = null;
try {
ApplicationInfo appInfo = CommonAppContext.getInstance().getPackageManager().getApplicationInfo(PACKAGE_NAME1, PackageManager.GET_META_DATA);
res = appInfo.metaData.getString(key);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return res;
}
public static boolean getMetaDataBoolean(String key) {
boolean res = false;
try {
ApplicationInfo appInfo = CommonAppContext.getInstance().getPackageManager().getApplicationInfo(PACKAGE_NAME1, PackageManager.GET_META_DATA);
res = appInfo.metaData.getBoolean(key);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return res;
}
public static int getMetaDataInt(String key) {
int res = 0;
try {
ApplicationInfo appInfo = CommonAppContext.getInstance().getPackageManager().getApplicationInfo(PACKAGE_NAME1, PackageManager.GET_META_DATA);
res = appInfo.metaData.getInt(key, 0);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return res;
}
private static String getHost() {
String host = getMetaDataString("SERVER_HOST");
HEADER.put("referer", host);
return host;
}
/**
* 保存用户等级信息
*/
// public void setLevel(String levelJson) {
// if (TextUtils.isEmpty(levelJson)) {
// return;
// }
// List<LevelBean> list = JSON.parseArray(levelJson, LevelBean.class);
// if (list == null || list.size() == 0) {
// return;
// }
// if (mLevelMap == null) {
// mLevelMap = new SparseArray<>();
// }
// mLevelMap.clear();
// for (LevelBean bean : list) {
// mLevelMap.put(bean.getLevel(), bean);
// }
// }
/**
* 保存主播等级信息
*/
// public void setAnchorLevel(String anchorLevelJson) {
// if (TextUtils.isEmpty(anchorLevelJson)) {
// return;
// }
// List<LevelBean> list = JSON.parseArray(anchorLevelJson, LevelBean.class);
// if (list == null || list.size() == 0) {
// return;
// }
// if (mAnchorLevelMap == null) {
// mAnchorLevelMap = new SparseArray<>();
// }
// mAnchorLevelMap.clear();
// for (LevelBean bean : list) {
// mAnchorLevelMap.put(bean.getLevel(), bean);
// }
// }
/**
* 获取用户等级
*/
// public LevelBean getLevel(int level) {
// if (mLevelMap == null) {
// String configString = SpUtil.getInstance().getStringValue(SpUtil.CONFIG);
// if (!TextUtils.isEmpty(configString)) {
// JSONObject obj = JSON.parseObject(configString);
// setLevel(obj.getString("level"));
// }
// }
// if (mLevelMap == null || mLevelMap.size() == 0) {
// return null;
// }
// return mLevelMap.get(level);
// }
/**
* 获取主播等级
*/
// public LevelBean getAnchorLevel(int level) {
// if (mAnchorLevelMap == null) {
// String configString = SpUtil.getInstance().getStringValue(SpUtil.CONFIG);
// if (!TextUtils.isEmpty(configString)) {
// JSONObject obj = JSON.parseObject(configString);
// setAnchorLevel(obj.getString("levelanchor"));
// }
// }
// if (mAnchorLevelMap == null || mAnchorLevelMap.size() == 0) {
// return null;
// }
// return mAnchorLevelMap.get(level);
// }
public String getGiftListJson() {
return mGiftListJson;
}
public void setGiftListJson(String getGiftListJson) {
mGiftListJson = getGiftListJson;
}
public String getGiftDaoListJson() {
return mGiftDaoListJson;
}
public void setGiftDaoListJson(String getGiftDaoListJson) {
mGiftDaoListJson = getGiftDaoListJson;
}
/**
* 判断某APP是否安装
*/
public static boolean isAppExist(String packageName) {
if (!TextUtils.isEmpty(packageName)) {
PackageManager manager = CommonAppContext.getInstance().getPackageManager();
List<PackageInfo> list = manager.getInstalledPackages(0);
for (PackageInfo info : list) {
if (packageName.equalsIgnoreCase(info.packageName)) {
return true;
}
}
}
return false;
}
public boolean isLaunched() {
return mLaunched;
}
public void setLaunched(boolean launched) {
mLaunched = launched;
}
//app是否在前台
public boolean isFrontGround() {
return mFrontGround;
}
//app是否在前台
public void setFrontGround(boolean frontGround) {
mFrontGround = frontGround;
}
public String getDeviceId() {
if (TextUtils.isEmpty(mDeviceId)) {
String deviceId = SpUtil.getInstance().getStringValue(SpUtil.DEVICE_ID);
if (TextUtils.isEmpty(deviceId)) {
deviceId = DeviceUtils.getDeviceId();
SpUtil.getInstance().setStringValue(SpUtil.DEVICE_ID, deviceId);
}
mDeviceId = deviceId;
}
LogUtils.e("getDeviceId---mDeviceId-----> " + mDeviceId);
return mDeviceId;
}
public int getTopActivityType() {
return mTopActivityType;
}
public void setTopActivityType(int topActivityType) {
mTopActivityType = topActivityType;
}
/**
* 是否是基本功能模式
*/
public boolean isBaseFunctionMode() {
return SpUtil.getInstance().getBooleanValue(SpUtil.BASE_FUNCTION_MODE, true);
}
/**
* 设置基本功能模式
*/
public void setBaseFunctionMode(boolean baseFunctionMode) {
SpUtil.getInstance().setBooleanValue(SpUtil.BASE_FUNCTION_MODE, baseFunctionMode);
}
public static String getHtmlUrl(String url) {
if (!TextUtils.isEmpty(url) && url.startsWith(CommonAppConfig.HOST)) {
if (!url.contains("?")) {
url = StringUtil.contact(url, "?");
}
url = StringUtil.contact(url,
"&uid=", CommonAppConfig.getInstance().getUid(),
"&token=", CommonAppConfig.getInstance().getToken(),
"&", Constants.LANGUAGE, "=", LanguageUtil.getInstance().getLanguage()
);
}
return url;
}
public boolean isPrivateMsgSwitchOpen() {
ConfigBean configBean = getConfig();
if (configBean != null) {
return configBean.getPriMsgSwitch() == 1;
}
return false;
}
}

View File

@@ -0,0 +1,100 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.xscm.moduleutil.R;
/**
*@author
*@data 2025/5/15
*@description: 当没有数据时展示的view
*/
public class CommonEmptyView extends FrameLayout {
private ImageView ivEmpty;
private TextView tvEmptyText;
private TextView tvOtherOperation;
private LinearLayout layoutEmpty;
private OtherOpListener otherOpListener;
public CommonEmptyView(@NonNull Context context) {
super(context);
initViews();
}
public CommonEmptyView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initViews();
}
public CommonEmptyView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initViews();
}
private void initViews() {
LayoutInflater.from(getContext()).inflate(R.layout.layout_empty, this);
ivEmpty = findViewById(R.id.iv_empty);
tvEmptyText = findViewById(R.id.tv_empty_text);
tvOtherOperation = findViewById(R.id.tv_other_operation);
layoutEmpty = findViewById(R.id.layout_empty);
tvOtherOperation.setOnClickListener(v -> otherOpListener.otherClick());
}
/**
* 设置提示文字
*
* @param text
*/
public void setEmptyText(String text) {
if (null != tvEmptyText) {
tvEmptyText.setText(text);
}
}
/**
* 设置提示文字的颜色
*/
public void setTextColor(int color) {
if (null != tvEmptyText) {
tvEmptyText.setTextColor(color);
}
}
/**
* 设置按钮文字
*
* @param txt
*/
public void setBtnText(String txt) {
if (null != tvOtherOperation) {
tvOtherOperation.setText(txt);
tvOtherOperation.setVisibility(VISIBLE);
}
}
/**
* 设置占位图
*/
public void setImg(int res) {
ivEmpty.setImageResource(res);
}
public void addOnOtherListener(OtherOpListener otherOpListener) {
this.otherOpListener = otherOpListener;
}
public interface OtherOpListener {
void otherClick();
}
}

View File

@@ -0,0 +1,386 @@
package com.xscm.moduleutil.widget;
/**
* Created by cxf on 2018/6/7.
*/
public class Constants {
public static final String URL = "url";
public static final String PAYLOAD = "payload";
public static final String SEX = "sex";
public static final String NICK_NAME = "nickname";
public static final String AVATAR = "avatar";
public static final String SIGN = "sign";
public static final String TO_UID = "toUid";
public static final String FROM_LIVE_ROOM = "fromLiveRoom";
public static final String FROM_LOGIN = "fromLogin";
public static final String TO_NAME = "toName";
public static final String STREAM = "stream";
public static final String LIMIT = "limit";
public static final String UID = "uid";
public static final String TIP = "tip";
public static final String EXIT = "exit";
public static final String FIRST_LOGIN = "firstLogin";
public static final String USER_BEAN = "userBean";
public static final String CLASS_ID = "classID";
public static final String CLASS_NAME = "className";
public static final String CHECKED_ID = "checkedId";
public static final String CHECKED_COIN = "checkedCoin";
public static final String LIVE_DANMU_PRICE = "danmuPrice";
public static final String COIN_NAME = "coinName";
public static final String AT_NAME = "atName";
public static final String LIVE_BEAN = "liveBean";
public static final String LIVE_TYPE = "liveType";
public static final String LIVE_KEY = "liveKey";
public static final String LIVE_POSITION = "livePosition";
public static final String LIVE_TYPE_VAL = "liveTypeVal";
public static final String LIVE_UID = "liveUid";
public static final String LIVE_PLAY_URL = "livePlayUrl";
public static final String SHARE_UID = "shareUid";
public static final String LIVE_STREAM = "liveStream";
public static final String OPEN_PACK = "openPack";
public static final String LIVE_HOME = "liveHome";
public static final String LIVE_FOLLOW = "liveFollow";
public static final String LIVE_NEAR = "liveNear";
public static final String LIVE_CLASS_PREFIX = "liveClass_";
public static final String LIVE_CLASS_RECOMMEND = "liveRecommend";
public static final String LIVE_ADMIN_ROOM = "liveAdminRoom";
public static final String HAS_GAME = "hasGame";
public static final String OPEN_FLASH = "openFlash";
public static final String LINK_MIC = "linkMic";
public static final String SHARE_QR_CODE_FILE = "shareQrCodeFile.png";
public static final String ANCHOR = "anchor";
public static final String FOLLOW = "follow";
public static final String DIAMONDS = "钻石";
public static final String VOTES = "映票";
public static final String SCORE = "积分";
public static final String PAY_TYPE_ALI = "ali";
public static final String PAY_TYPE_WX = "wx";
public static final String PAY_TYPE_PAYPAL = "paypal";
public static final String PAY_TYPE_BALANCE = "balance";
public static final String PAY_BUY_COIN_ALI = "Charge.getAliOrder";
public static final String PAY_BUY_COIN_WX = "Charge.getWxOrder";
public static final String PAY_BUY_COIN_PAYPAL = "Charge.getBraintreePaypalOrder";
public static final String PACKAGE_NAME_ALI = "com.eg.android.AlipayGphone";//支付宝的包名
public static final String PACKAGE_NAME_WX = "com.tencent.mm";//微信的包名
public static final String PACKAGE_NAME_QQ = "com.tencent.mobileqq";//QQ的包名
public static final String PACKAGE_NAME_GAODE_MAP = "com.autonavi.minimap";//高德地图包名
public static final String PACKAGE_NAME_BAIDU_MAP = "com.baidu.BaiduMap";//百度地图包名
public static final String PACKAGE_NAME_TX_MAP = "com.tencent.map";//腾讯地图包名
public static final String LAT = "lat";
public static final String LNG = "lng";
public static final String ADDRESS = "address";
public static final String SELECT_IMAGE_PATH = "selectedImagePath";
public static final String COPY_PREFIX = "copy://";
public static final String TEL_PREFIX = "tel://";
public static final String AUTH_PREFIX = "auth://";
public static final int GUARD_TYPE_NONE = 0;
public static final int GUARD_TYPE_MONTH = 1;
public static final int GUARD_TYPE_YEAR = 2;
public static final String DOWNLOAD_MUSIC = "downloadMusic";
public static final String LINK = "link";
public static final String REPORT = "report";
public static final String SAVE = "save";
public static final String DELETE = "delete";
public static final String GOODS = "goods";
public static final String MAX_COUNT = "maxCount";
public static final String USE_CAMERA = "useCamera";
public static final String USE_PREVIEW = "usePreview";
public static final int SETTING_MODIFY_PWD = 15;
public static final int SETTING_UPDATE_ID = 16;
public static final int SETTING_CLEAR_CACHE = 18;
public static final int SEX_MALE = 1;
public static final int SEX_FEMALE = 2;
public static final int FOLLOW_FROM_FOLLOW = 1002;
public static final int FOLLOW_FROM_FANS = 1003;
public static final int FOLLOW_FROM_SEARCH = 1004;
public static final String IM_FROM_HOME = "imFromHome";
//直播房间类型
public static final int LIVE_TYPE_NORMAL = 0;//普通房间
public static final int LIVE_TYPE_PWD = 1;//密码房间
public static final int LIVE_TYPE_PAY = 2;//收费房间
public static final int LIVE_TYPE_TIME = 3;//计时房间
//主播直播间功能
public static final int LIVE_FUNC_BEAUTY = 2001;//美颜
public static final int LIVE_FUNC_CAMERA = 2002;//切换摄像头
public static final int LIVE_FUNC_FLASH = 2003;//闪光灯
public static final int LIVE_FUNC_MUSIC = 2004;//伴奏
public static final int LIVE_FUNC_SHARE = 2005;//分享
public static final int LIVE_FUNC_GAME = 2006;//游戏
public static final int LIVE_FUNC_RED_PACK = 2007;//红包
public static final int LIVE_FUNC_LINK_MIC = 2008;//连麦
public static final int LIVE_FUNC_MIRROR = 2009;//镜像
public static final int LIVE_FUNC_TASK = 2010;//每日任务
public static final int LIVE_FUNC_LUCK = 2011;//幸运奖池
public static final int LIVE_FUNC_PAN = 2012;//转盘
public static final int LIVE_FUNC_MSG = 2013;//私信
public static final int LIVE_FUNC_FACE = 2014;//表情
public static final int LIVE_FUNC_LINK_MIC_AUD = 2015;//用户连麦
public static final int LIVE_FUNC_REPORT = 2016;//举报
//socket
public static final String SOCKET_CONN = "conn";
public static final String SOCKET_BROADCAST = "broadcastingListen";
public static final String SOCKET_SEND = "broadcast";
public static final String SOCKET_STOP_PLAY = "stopplay";//超管关闭直播间
public static final String SOCKET_STOP_LIVE = "stopLive";//超管关闭直播间
public static final String SOCKET_SEND_MSG = "SendMsg";//发送文字消息,点亮,用户进房间
public static final String SOCKET_LIGHT = "light";//飘心
public static final String SOCKET_SEND_GIFT = "SendGift";//送礼物
public static final String SOCKET_SEND_BARRAGE = "SendBarrage";//发弹幕
public static final String SOCKET_LEAVE_ROOM = "disconnect";//用户离开房间
public static final String SOCKET_LIVE_END = "StartEndLive";//主播关闭直播
public static final String SOCKET_SYSTEM = "SystemNot";//系统消息
public static final String SOCKET_KICK = "KickUser";//踢人
public static final String SOCKET_SHUT_UP = "ShutUpUser";//禁言
public static final String SOCKET_SET_ADMIN = "setAdmin";//设置或取消管理员
public static final String SOCKET_CHANGE_LIVE = "changeLive";//切换计时收费类型
public static final String SOCKET_UPDATE_VOTES = "updateVotes";//门票或计时收费时候更新主播的映票数
public static final String SOCKET_FAKE_FANS = "requestFans";//僵尸粉
public static final String SOCKET_LINK_MIC = "ConnectVideo";//连麦
public static final String SOCKET_LINK_MIC_ANCHOR = "LiveConnect";//主播连麦
public static final String SOCKET_LINK_MIC_PK = "LivePK";//主播PK
public static final String SOCKET_BUY_GUARD = "BuyGuard";//购买守护
public static final String SOCKET_RED_PACK = "SendRed";//红包
public static final String SOCKET_LUCK_WIN = "luckWin";//幸运礼物中奖
public static final String SOCKET_PRIZE_POOL_WIN = "jackpotWin";//奖池中奖
public static final String SOCKET_PRIZE_POOL_UP = "jackpotUp";//奖池升级
public static final String SOCKET_GIFT_GLOBAL = "Sendplatgift";//全站礼物
public static final String SOCKET_LIVE_GOODS_SHOW = "goodsLiveShow";//直播间展示商品
public static final String SOCKET_LIVE_GOODS_SALE_NUM = "liveGoodsSaleNums";//直播间商品销量
public static final String SOCKET_LIVE_GOODS_FLOAT = "shopGoodsLiveFloat";//直播间商品飘屏
public static final String SOCKET_VOICE_ROOM = "voiceRoom";//语音聊天室
public static final String SOCKET_LIVE_WARNING = "warning";//直播间警告
public static final String SOCKET_XQTB_WIN = "xqtbWin";//星球探宝中奖后发送
public static final String SOCKET_LUCKPAN_WIN = "xydzpWin";//幸运大转盘中奖后发送
//游戏socket
public static final String SOCKET_GAME_ZJH = "startGame";//炸金花
public static final String SOCKET_GAME_HD = "startLodumaniGame";//海盗船长
public static final String SOCKET_GAME_NZ = "startCattleGame";//开心牛仔
public static final String SOCKET_GAME_ZP = "startRotationGame";//幸运转盘
public static final String SOCKET_GAME_EBB = "startShellGame";//二八贝
public static final int SOCKET_WHAT_CONN = 0;
public static final int SOCKET_WHAT_DISCONN = 2;
public static final int SOCKET_WHAT_BROADCAST = 1;
//socket 用户类型
public static final int SOCKET_USER_TYPE_NORMAL = 30;//普通用户
public static final int SOCKET_USER_TYPE_ADMIN = 40;//房间管理员
public static final int SOCKET_USER_TYPE_ANCHOR = 50;//主播
public static final int SOCKET_USER_TYPE_SUPER = 60;//超管
//提现账号类型1表示支付宝2表示微信3表示银行卡
public static final int CASH_ACCOUNT_ALI = 1;
public static final int CASH_ACCOUNT_WX = 2;
public static final int CASH_ACCOUNT_BANK = 3;
public static final String CASH_ACCOUNT_ID = "cashAccountID";
public static final String CASH_ACCOUNT = "cashAccount";
public static final String CASH_ACCOUNT_NAME = "cashAccountName";
public static final String CASH_ACCOUNT_TYPE = "cashAccountType";
public static final String NOT_LOGIN_UID = "-9999";//未登录的uid
public static final String NOT_LOGIN_TOKEN = "-9999";//未登录的token
public static final String LANGUAGE = "language";
public static final String LANG_EN = "en";//英文
public static final String LANG_ZH = "zh-cn";//中文
public static final int PAYSUCESS = 0x0000;
public static final String FILE_PATH = "/YuTang";
//接口地址
public static final String SEND_CODE = "/api/Sms/send";
public static final String LOGIN = "/api/Login/phone_code";
public static final String USER_LOGIN = "/api/Login/user_login";
//随机昵称
public static final String UPLOAD_NICK_NAME = "/api/UserData/random_nickname";
public static final String UPLOAD_USER_PIC = "/api/UserData/modify_user_sex_pic";
public static final String SWITCH_ACCOUNTS = "/api/Login/multi_account_login";
public static final String USER_UPDATE = "/api/UserData/modify_fist_user_info";
public static final String CHANGE_PASSWORD = "/api/UserData/modify_password";//设置密码
public static final String URL_LOGIN = "/api/Login/one_click_login";//一键登录
public static final String URL_AUTH_CODE = "/api/Login/aliLogin";//支付宝登录
public static final String URL_WX_CODE = "/api/Login/wechatLogin";//微信登录
public static final String AUTHORIZATION = "/api/Login/AlipayUserInfo";
public static final String REAL_NAME = "/api/UserData/real_name";//调用实名认证接口
public static final String REAL_NAME_RESULT = "/api/UserData/real_name_result";//调用实名认证接口
public static final String GET_EXPAND_COLUMN = "/api/UserZone/expand_zone";//扩列获取数据接口
public static final String GET_OFFICIAL_NOTICE = "/api/UserMessage/get_user_message_cover_info";//系统封面消息
public static final String GET_ALBUM_LIST = "/api/User/get_album_list";//相册列表
public static final String CREATE_ALBUM = "/api/User/create_album";//创建相册
public static final String EDIT_ALBUM = "/api/User/edit_album";//编辑相册
public static final String GET_REWARD_LIST = "/api/UserZone/reward_list";//动态打赏列表
public static final String GET_GIFT_LABEL = "/api/Gift/get_gift_label";//礼物标签
public static final String GIFT_LIST = "/api/Gift/get_gift_list";//礼物列表
public static final String TOPIC_LIST = "/api/UserZone/topic_list";//获取话题列表
public static final String PUBLISH_ZONE = "/api/UserZone/publish_zone";//发布动态
public static final String GET_CATEGORIES = "/api/UserZone/get_zone_topic";//动态引用的四条话题
public static final String GET_CIRCLE_LIST = "/api/UserZone/zone_list";//语圈列表
public static final String LIKE_ZONE = "/api/UserZone/like_zone";//语圈列表
public static final String GET_COMMENT_LIST = "/api/UserZone/get_comment_list";//评论列表
public static final String COMMENT_ZONE = "/api/UserZone/comment_zone";//评论列表
public static final String TOPIC_ID = "/api/UserZone/get_zone_topic_list";//热门话题列表
public static final String ZONE_DETAIL = "/api/UserZone/zone_detail";//语圈详情
public static final String DELETE_COMMENT = "/api/UserZone/delete_zone_comment";//删除子评论
public static final String DELETE_ZONE = "/api/UserZone/delete_zone";//删除动态
public static final String FORGOT_PASSWORD = "/api/Login/forgot_password";//忘记密码
public static final String CLEAR_LOGIN_INFO = "/api/Login/logout";//忘记密码
public static final String CANCEL = "/api/Login/cancel";//注销账号
public static final String GET_MY_INFO = "/api/User/get_user_info";//点击我的获取个人数据
public static final String GET_USER_HOME = "/api/User/get_user_home";//点击获取个人数据
public static final String ED_USER_INFO = "/api/User/edit_user_info";//编辑信息
public static final String ED_USER_BG = "/api/User/edit_user_bg";//编辑背景图片
public static final String GET_USER_TAG_LIST = "/api/User/get_user_tag_list";//获取用户标签
public static final String GET_USER_HOME_ZONE = "/api/User/user_home_zone";//获取用户动态
public static final String GET_LIKE_LIST = "/api/UserZone/like_list";//获取点赞列表
public static final String POST_GZ = "/api/User/follow";//关注、回关、取关
public static final String GET_ALBUM_DETAIL = "/api/User/get_album_detail";//相册详情
public static final String UP_ALBUM = "/api/User/add_album_content";//添加相册图片
public static final String MOVE_ALBUM = "/api/User/move_album_images";//移动相册图片
public static final String DELETE_ALBUM_IMAGE = "/api/User/delete_album_images";//删除图片
public static final String DELETE_ALBUM = "/api/User/delete_album";//删除相册
public static final String LIKE_ALBUM = "/api/User/like_album";//相册点赞
public static final String GET_PERSONALTY = "/api/Decorate/get_type_list";//装扮类型列表
public static final String GET_DECORATE = "/api/Decorate/user_decorate";//装扮详情
public static final String SET_USER_DECORATE = "/api/Decorate/set_user_decorate";//用户装扮
public static final String JOIN_ROOM = "/api/Room/join_room";//加入房间
public static final String UPDATEPASSWORD = "/api/room/setRoomPassword";//更新房间秘密啊
public static final String GET_ROOM_ONLINE = "/api/Room/room_online_list";//房间在线列表
public static final String GET_SJ_ROOM_NAME = "/api/Room/room_random_name";//随机房间名称
public static final String CHECK_TXT = "/api/Room/create_room";//创建房间
public static final String GET_MY_ROOM = "/api/Room/my_associated_room";//我的房间
public static final String GET_MY_FOOT = "/api/Room/user_room_history_list";//我的足迹
public static final String GET_TOP_ROOM = "/api/Index/room_list";//首页房间列表置顶不传lable_id房间列表is_top传1
public static final String GET_ROOM_TYPE = "/api/Index/room_type_list";//房间分类列表
public static final String GET_GIVE_GIFT = "/api/Gift/chat_gift_send";//聊天送礼物
public static final String GET_WALLET = "/api/UserWallet/wallet";//钱包
public static final String GET_ROOM_GIFT = "/api/Room/room_give_gift";//直播间送礼
public static final String GET_ROOM_USER = "/api/Room/room_user_home";//房间内点击头像
public static final String APPLY_PIT = "/api/RoomPit/apply_pit";//申请上麦
public static final String DOWN_PIT = "/api/RoomPit/down_pit";//下麦
public static final String ADDRESS_IP = "/api/User/update_user_ip";//修改ip地址
public static final String REWARD_ZONE = "/api/UserZone/reward_zone";//动态打赏礼物
public static final String POST_CHANGE_ROOM = "/api/RoomPit/change_room_up_pit_type";//修改上麦模式
public static final String POST_APPLY_LIST = "/api/RoomPit/apply_pit_list";//申请上麦列表
public static final String SET_ROOM_APPLY = "/api/RoomPit/set_room_pit_apply_help_gift";//设置房间插麦礼物
public static final String CLEAR_APPLY = "/api/RoomPit/clear_apply_pit_list";//清空申请列表
public static final String AGREE_PIT = "/api/RoomPit/agree_pit";//同意上麦
public static final String REFUSE_PIT = "/api/RoomPit/refuse_pit";//拒绝上麦
public static final String HELP_APPLY = "/api/RoomPit/help_apply_pit";//上麦助力
public static final String POST_APPLY_SONG = "/api/RoomSong/apply_song";//申请点歌
public static final String POST_AGREE_SONG = "/api/RoomSong/agree_song";//同意、拒绝点歌
public static final String POST_SONG_LIST = "/api/RoomSong/song_list";//获取已点歌曲
public static final String POST_SONG = "/api/RoomSong/song";//点歌
public static final String POST_UP_SONG = "/api/RoomSong/up_song";//移动歌曲
public static final String POST_END_SONG = "/api/RoomSong/end_song";//结束本场歌
public static final String POST_ROOM_INFO = "/api/Room/room_info";//房间信息
public static final String GET_CHARM_RANK = "/api/RoomSong/get_charm_rank";//房间魅力排行榜
public static final String CHANGE_SONG = "/api/RoomSong/change_song";//切歌
public static final String POST_HOST_LIST = "/api/Room/host_list";//主持人、管理员列表
public static final String POST_SEARCH = "/api/Search/search";//搜索, type1用户2房间3公会
public static final String SET_PRESIDED_RATIO = "/api/Room/set_host_profit";//设置主持人收益
public static final String GET_PRESIDED_RATIO = "/api/Room/set_host";//操作主持、管理,type1主持。2管理 is_add1添加2删除
public static final String POST_ROOM_HOST_PIT = "/api/RoomPit/host_user_pit";//抱麦/踢麦 type1抱麦2踢麦 当在在线列表中,点击抱麦的时候,是没有
public static final String POST_SET_MUTE_PIT = "api/Room/set_mute";// 禁言 禁麦 \解禁 is_mute 1-不让打字2-不让说话3-可打字4-可说话
public static final String POST_SET_LOCK_PIT = "/api/Room/set_lock_pit";//锁麦、解禁 is_lock0未禁麦1已禁
public static final String GET_ROOM_BJ = "/api/Room/room_bg_list";//获取房间背景
public static final String POST_SET_UPLOAD_BG_IMG = "/api/Room/upload_bg_img";//上传、删除背景图片
public static final String POST_EDIT_ROOM = "/api/Room/edit_room";//编辑房间信息
public static final String POST_KICK_OUT_ROOM = "/api/Room/kick_out_room";//踢出房间
public static final String POST_QUIT_ROOM = "/api/Room/quit_room";//退出房间
public static final String ADD_BLACK_LIST = "/api/User/add_blacklist";//添加黑名单
public static final String POST_FOLLOW_LIST = "/api/User/get_user_follow_list";//关注列表
public static final String POST_FANS_LIST = "/api/User/get_user_fans_list";//粉丝列表
public static final String POST_BLACK_LIST = "/api/User/get_blacklist";//黑名单
public static final String POST_LOCK_MI_LIST = "/api/User/get_look_me_list";//看过我的,访客
public static final String REMOVE_BLACK_LIST = "/api/User/remove_blacklist";//移除黑名单
public static final String POST_CHANGE_ROOM_TYPE = "/api/Room/change_room_type";//修改房间类型
public static final String POST_ROOM_RELATION_LIST = "/api/RoomAuction/room_relation_list";//房间关系列表
public static final String POST_ROOM_AUCTION = "/api/RoomAuction/room_auction";//房间竞拍开始
public static final String POST_AUCTION_DELAY = "/api/RoomAuction/room_auction_delay";//延时
public static final String POST_AUCTION_END = "/api/RoomAuction/room_auction_end";//结束
public static final String POST_ROOM_AUCTION_TIME = "/api/RoomAuction/room_auction_time";//真爱拍选礼物后计算天数
public static final String POST_ROOM_AUCTION_JOIN = "/api/RoomAuction/room_auction_join";//参与竞拍
public static final String POST_AUCTION_MODE = "/api/RoomAuction/room_auction_mode";//修改房间竞拍模式
public static final String POST_ROOM_AUCTION_LIST = "/api/RoomAuction/room_auction_list";//房间竞拍列表
public static final String GET_MY_CP_ROOM_LIST = "/api/Room/my_cp_room";//我的Cp房
public static final String POST_SEARCH_PK_ROOM = "/api/RoomPk/search_pk_room";//搜索 或推荐的pk房间
public static final String POST_SEND_PK = "/api/RoomPk/send_pk";//发起PK邀请
public static final String ACCEPT_PK = "/api/RoomPk/accept_pk";//接受/拒绝 PK邀请
public static final String POST_REFUSE_PK = "/api/RoomPk/refuse_pk";//不再接受 PK is_pk 1-接受2-拒绝
public static final String POST_END_PK = "/api/RoomPk/end_pk";//pk结束、断开链接,type:1:时间到后系统调用2没有开始前点击结束 3开始后中途结束
public static final String POST_START_PK = "/api/RoomPk/start_pk";//开始PK
public static final String POST_CHARM_LIST = "/api/Room/room_turnover_detail";//房间流水明细
public static final String GET_SUBSIDY = "/api/Room/room_ubsidy";//房间补贴
public static final String POST_MY_ROOM_BEAN = "/api/Index/index_banner";//首页轮播图
public static final String GET_RECHARGE = "/api/UserWallet/can_recharge_list";//可选充值金额列表
public static final String POST_APPLY_Pay = "/api/Payment/app_pay";//APP支付
public static final String POST_LOG_LIST = "/api/UserWallet/log_list";//金币(钻石)明细
public static final String GET_REAL_NAME = "/api/UserData/real_name_info";//实名认证后的信息
public static final String SEND_HEADLINE = "/api/Room/send_headline";//发(抢)头条
public static final String CURRENT_HEADLINE = "/api/Room/current_headline";//当前发(抢)头条需要的数据
public static final String POST_BIND_TYPE = "/api/Bind/bindType";//绑定方式及状态
public static final String EARNINGS_NUM = "/api/UserWallet/exchange_coin";//收益(钻石)兑换金币
public static final String GET_WALLET_CONFIG = "/api/UserWallet/get_wallet_config";//钱包相关配置接口
public static final String POST_BIND = "/api/Bind/bind";//绑定(支付宝、银行卡、微信)
public static final String POST_WITHDRAWAL = "/api/UserWithdrawal/withdrawal";//用户提现
public static final String POST_WEALTH_RANKING = "/api/Ranking/wealth_ranking";//财富、魅力榜
public static final String POST_ROOM_RANKING = "/api/Ranking/room_ranking";//房间榜
public static final String GUILD_RANKING = "/api/Ranking/guild_ranking";//公会榜
public static final String POST_LOVE_RANKING = "/api/Ranking/love_ranking";//真爱榜
public static final String GET_TASKS_LIHEN = "/api/Dailytasks/dailyTasksList";//每日任务列表
public static final String GET_DAILY_TASK_BOX = "/api/Dailytasks/dailyTasksBoxRecord";//礼盒记录
public static final String dailyTasksOpenBox = "/api/Dailytasks/dailyTasksOpenBox";//开启礼盒
public static final String GET_MY_BAG_DATA = "/api/UserGiftPack/get_gift_pack_income";//背包收入
public static final String GET_MY_BAG_OUTCOME = "/api/UserGiftPack/get_gift_pack_outcome";//背包礼物支出列表
public static final String GET_GIFT_PACK = "/api/UserGiftPack/get_gift_pack_list";//获取背包礼物列表
public static final String GET_WITHDRAWAL_LIST = "/api/UserWithdrawal/withdrawal_list";//提现记录
public static final String POST_ROOM_RANK = "/api/Room/room_rank";//房间排行榜(财富、魅力)
public static final String GET_APP_UPDATE = "/api/Version/get_app_version";//版本更新
public static final String POST_CLEAR_USER_CHARM = "/api/Room/clear_user_charm";//清除魅力值
public static final String POST_MESSAGE_LIST = "/api/UserMessage/get_user_message_list";//消息列表
public static final String POST_USER_WALL = "/api/User/user_gift_wall";//礼物墙
public static final String POST_USER_OLINE_STATUS = "/api/Room/user_online_status";//用户在线状态
public static final String DELETE_ROOM_HISTORY = "/api/Room/delete_room_history";//删除历史足迹
public static final String POST_GIVE_COIN = "/api/User/give_coin";//转币功能
public static final String POST_RELATION_CARD = "/api/Room/relation_card";//获取关系列表
public static final String POST_TOP_RELATION_CARD = "/api/Room/top_relation_card";//置顶关系卡片
public static final String POST_DELETE_RELATION_CARD = "/api/Room/delete_relation_card";//删除关系
public static final String GET_FIRST_CHARGE = "/api/Activities/first_charge_gift_permission";//首充好礼弹框权限
public static final String GET_FIRST_CHARGE_GIFT = "/api/Activities/first_charge_gift";//首充好礼列表接口
public static final String dailyTasksReceive = "/api/Dailytasks/dailyTasksReceive";//领取每日任务奖励
public static final String postRoomSwToken = "/api/Room/update_user_sw_token";//获取用户声网token
public static final String dailyTasksComplete = "/api/Dailytasks/dailyTasksComplete";//领取每日任务奖励
public static final String POST_CANCEL_USER_DECORATE = "/api/Decorate/cancel_user_decorate";//取消装扮
public static final String GET_THEME_DATA = "/api/Theme/get_theme_data";//主题接口
public static final String START_FRIEND = "/api/Friend/start_friend";//点击开始按钮 交友房
public static final String DELAY = "/api/Friend/delay";//点击延时 交友房
public static final String END_FRIEND = "/api/Friend/end_friend";//点击结束 交友房
public static final String CREATE_RELATION = "/api/Friend/create_relation";//卡关系 (创建关系) 交友房
public static final String MODIFY_MOBILE = "/api/UserData/modify_mobile";//手机换绑
public static final String BIND_MOBILE = "/api/UserData/bind_mobile";//手机绑定
}

View File

@@ -0,0 +1,22 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.os.Bundle;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import com.google.android.material.bottomsheet.BottomSheetDialog;
public class CustomBottomSheetDialog extends BottomSheetDialog {
public CustomBottomSheetDialog(@NonNull Context context) {
super(context);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
}
}

View File

@@ -0,0 +1,104 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.text.Editable;
import android.text.InputFilter;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.xscm.moduleutil.R;
/**
*@author
*@data 2025/5/15
*@description: 输入框添加字数文字提示
*/
public class CustomEditText extends LinearLayout {
private EditText editText;
private TextView lengthTextView;
private int maxLength = 100; // 默认最大长度
public CustomEditText(Context context) {
super(context);
init(context);
}
public CustomEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public CustomEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
// 加载布局文件
LayoutInflater.from(context).inflate(R.layout.custom_edit_text_layout, this, true);
// 找到 EditText 和 TextView 的引用
editText = findViewById(R.id.edit_text);
lengthTextView = findViewById(R.id.length_text_view);
// 初始化监听器
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
updateLengthDisplay();
}
@Override
public void afterTextChanged(Editable s) {}
});
// 设置默认的最大长度
setMaxLength(maxLength);
}
/**
* 设置最大长度
*/
public void setMaxLength(int maxLength) {
this.maxLength = maxLength;
editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)});
updateLengthDisplay(); // 初始化显示
}
/**
* 更新显示的字数
*/
private void updateLengthDisplay() {
int currentLength = editText.getText().length();
lengthTextView.setText(currentLength + "/" + maxLength);
}
/**
* 设置提示文本
*/
public void setHint(String hint) {
editText.setHint(hint);
}
/**
* 获取当前文本
*/
public String getText() {
return editText.getText().toString();
}
/**
* 设置文本
*/
public void setText(String text) {
editText.setText(text);
}
}

View File

@@ -0,0 +1,120 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.view.View;
import com.petterp.floatingx.listener.control.IFxControl;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.databinding.RoomDialogMusicWindowOpenBinding;
import com.xscm.moduleutil.interfaces.OnMusicItemClickListener;
import com.xscm.moduleutil.utils.ImageUtils;
import io.agora.musiccontentcenter.Music;
public class CustomMusicFloatingView {
private final IFxControl fxControl;
private final Context context;
private RoomDialogMusicWindowOpenBinding binding;
private OnMusicItemClickListener onItemClickListener;
private boolean isInitialized = false;
public CustomMusicFloatingView(Context context, IFxControl fxControl) {
this.context = context;
this.fxControl = fxControl;
// this.binding = DataBindingUtil.inflate(LayoutInflater.from(context),
// R.layout.room_dialog_music_window_open, null, false);
}
public void initView() {
View floatingView = fxControl.getView();
if (floatingView == null || isInitialized) return;
// 替换为绑定方式获取控件
binding = RoomDialogMusicWindowOpenBinding.bind(floatingView);
// 设置点击事件
binding.ivMinx.setOnClickListener(v -> {
if (onItemClickListener != null) {
onItemClickListener.onMinimize();
}
});
binding.ivMusicPlayState.setOnClickListener(v -> {
if (onItemClickListener != null) {
if (binding.ivMusicPlayState.getTag() instanceof Boolean) {
boolean isPlaying = (boolean) binding.ivMusicPlayState.getTag();
if (isPlaying) {
onItemClickListener.onPause();
} else {
onItemClickListener.onResume();
}
binding.ivMusicPlayState.setImageResource(isPlaying ?
R.mipmap.room_music_win_puase : R.mipmap.room_music_win_start);
binding.ivMusicPlayState.setTag(!isPlaying);
}
}
});
binding.ivList.setOnClickListener(v -> {
if (onItemClickListener != null) {
onItemClickListener.onOpenList();
}
});
binding.ivNext.setOnClickListener(v -> {
if (onItemClickListener != null) {
onItemClickListener.onNext();
}
});
isInitialized = true; // 只绑定一次
}
public void show() {
fxControl.show();
}
public void hide() {
fxControl.hide();
}
public void destroy() {
fxControl.cancel();
}
public void setOnItemClickListener(OnMusicItemClickListener listener) {
this.onItemClickListener = listener;
}
public void updatePlayState(boolean isPlaying) {
// if (fxControl.getView() == null) return;
// binding = RoomDialogMusicWindowOpenBinding.bind(fxControl.getView());
binding.ivMusicPlayState.setImageResource(isPlaying ?
R.mipmap.room_music_win_start : R.mipmap.room_music_win_puase);
binding.ivMusicPlayState.setTag(isPlaying);
}
public void updateTitle(String title, String singer) {
if (fxControl.getView() == null) return;
binding = RoomDialogMusicWindowOpenBinding.bind(fxControl.getView());
binding.tvMusicTitle.setText(title);
binding.tvSinger.setText(singer);
}
public void onMusicEvent(Music event) {
// updatePlayState(true);
ImageUtils.loadHeadCC(event.getPoster(), binding.musicImg);
binding.tvMusicTitle.setText(event.getName());
binding.tvSinger.setText(event.getSinger());
binding.musicM.setText("/" + formatSecondsToMinutes(event.getDurationS()));
}
public void updateProgress(int progress) {
updatePlayState(true);
binding.musicT.setText(formatSecondsToMinutes(progress/1000));
}
public static String formatSecondsToMinutes(int totalSeconds) {
int minutes = totalSeconds / 60;
int seconds = totalSeconds % 60;
return String.format("%02d:%02d", minutes, seconds);
}
}

View File

@@ -0,0 +1,118 @@
package com.xscm.moduleutil.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.drawable.AnimationDrawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.utils.logger.Logger;
import com.scwang.smartrefresh.layout.api.RefreshHeader;
import com.scwang.smartrefresh.layout.api.RefreshKernel;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.constant.RefreshState;
import com.scwang.smartrefresh.layout.constant.SpinnerStyle;
/**
* 项目名称 qipao-android
* 包名com.qpyy.module.index.widget
* 创建人 王欧
* 创建时间 2020/6/30 3:55 PM
* 描述 describe
*/
public class CustomRefreshHeader extends ConstraintLayout implements RefreshHeader {
ImageView mImageView;
AnimationDrawable mAnimationDrawable;
public CustomRefreshHeader(Context context) {
this(context, null);
}
public CustomRefreshHeader(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.index_header_custom_refresh, this);
mImageView = findViewById(R.id.image);
mAnimationDrawable = (AnimationDrawable) mImageView.getBackground();
}
@NonNull
@Override
public View getView() {
return this;
}
@NonNull
@Override
public SpinnerStyle getSpinnerStyle() {
return SpinnerStyle.Translate;
}
@SuppressLint("RestrictedApi")
@Override
public void setPrimaryColors(int... colors) {
}
@SuppressLint("RestrictedApi")
@Override
public void onInitialized(@NonNull RefreshKernel kernel, int height, int maxDragHeight) {
}
@SuppressLint("RestrictedApi")
@Override
public void onMoving(boolean isDragging, float percent, int offset, int height, int maxDragHeight) {
}
@SuppressLint("RestrictedApi")
@Override
public void onReleased(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) {
Logger.e("onReleased");
}
@SuppressLint("RestrictedApi")
@Override
public void onStartAnimator(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) {
Logger.e("onStartAnimator");
//判断是否在运行
if (!mAnimationDrawable.isRunning()) {
//开启帧动画
mAnimationDrawable.start();
}
}
@SuppressLint("RestrictedApi")
@Override
public int onFinish(@NonNull RefreshLayout refreshLayout, boolean success) {
if (mAnimationDrawable.isRunning()) {
//开启帧动画
mAnimationDrawable.stop();
}
return 0;
}
@SuppressLint("RestrictedApi")
@Override
public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) {
}
@Override
public boolean isSupportHorizontalDrag() {
return false;
}
@SuppressLint("RestrictedApi")
@Override
public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {
}
}

View File

@@ -0,0 +1,122 @@
package com.xscm.moduleutil.widget;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.databinding.DataBindingUtil;
import com.blankj.utilcode.util.BarUtils;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.databinding.ViewCustomTopBarBinding;
public class CustomTopBar extends ConstraintLayout {
private OnCallBackRightIcon onCallBackRightIcon;
private OnCallBackRightIcon2 onCallBackRightIcon2;
private ViewCustomTopBarBinding mBinding;
public CustomTopBar(Context context) {
this(context, null, 0);
}
public CustomTopBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomTopBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mBinding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.view_custom_top_bar, this, true);
mBinding.ivBack.setOnClickListener(v -> {
if (getContext() instanceof Activity) {
((Activity) getContext()).finish();
}
});
mBinding.ivIntent.setOnClickListener(v -> onCallBackRightIcon.onIntent());
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomTopBar);
String title = typedArray.getString(R.styleable.CustomTopBar_TopBarTitle);
typedArray.recycle();
setTitle(title);
setPadding(0, BarUtils.getStatusBarHeight(), 0, 0);
mBinding.tvRight.setOnClickListener(v -> onCallBackRightIcon2.onIntent());
}
public ImageView getIvBack() {
return mBinding.ivBack;
}
public TextView getTvRight() {
return mBinding.tvRight;
}
public TextView getTvTitle() {
return mBinding.tvTitle;
}
public void setTitle(String title) {
mBinding.tvTitle.setText(title);
}
public void setRightText(String txt) {
mBinding.tvRight.setText(txt);
}
public void setRightColor(int color) {
mBinding.tvRight.setTextColor(color);
}
public void setRightSize(int size) {
mBinding.tvRight.setTextSize(size);
}
/**
* 设置右图片
*
* @param
*/
public void setRightIcon(int res) {
mBinding.ivIntent.setImageResource(res);
}
/**
* 设置图片右边距
*
* @param i
*/
public void setImgPaddingRight(int i) {
mBinding.ivIntent.setPadding(0, 0, i, 0);
}
public void setRightTxtVisible(boolean b) {
mBinding.tvRight.setVisibility(b ? VISIBLE : GONE);
}
public void setRightImgVIsible(boolean b) {
mBinding.ivIntent.setVisibility(b ? VISIBLE : GONE);
}
public void setColor(int color) {
mBinding.tvTitle.setTextColor(color);
mBinding.ivBack.setColorFilter(color);
}
public void addIntentListener(OnCallBackRightIcon onCallBackRightIcon) {
this.onCallBackRightIcon = onCallBackRightIcon;
}
public interface OnCallBackRightIcon {
void onIntent();
}
public void addIntentListener2(OnCallBackRightIcon2 onCallBackRightIcon) {
this.onCallBackRightIcon2 = onCallBackRightIcon;
}
public interface OnCallBackRightIcon2 {
void onIntent();
}
}

View File

@@ -0,0 +1,74 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.ImageView;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.utils.ImageUtils;
import com.xscm.moduleutil.utils.logger.Logger;
/**
* 项目名称 qipao-android
* 包名com.yutang.xqipao.utils.view
* 创建人 王欧
* 创建时间 2020/4/9 12:46 PM
* 描述 describe
*/
public class DecorationHeadView extends ConstraintLayout {
private ImageView mRiv;
private ImageView mIvFrame;
private ImageView mIvSex;
private ImageView mIvOnline;
public DecorationHeadView(Context context) {
this(context, null, 0);
}
public DecorationHeadView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DecorationHeadView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context, attrs, defStyleAttr);
}
private void initView(Context context, AttributeSet attrs, int defStyleAttr) {
LayoutInflater.from(context).inflate(R.layout.common_view_decoration_head, this, true);
mRiv = findViewById(R.id.riv);
mIvFrame = findViewById(R.id.iv_frame);
mIvSex = findViewById(R.id.iv_sex);
mIvOnline = findViewById(R.id.iv_online);
}
public void setData(String headPicture, String framePicture, String sex) {
Logger.e(headPicture, framePicture, sex);
if (!TextUtils.isEmpty(headPicture)) {
ImageUtils.loadHeadCC(headPicture, mRiv);
}
if (TextUtils.isEmpty(framePicture)) {
mIvSex.setVisibility(VISIBLE);
if (!TextUtils.isEmpty(sex)) {
if (sex.equals("1")) {
mIvSex.setBackgroundResource(R.mipmap.nan);
} else {
mIvSex.setBackgroundResource(R.mipmap.nv);
}
}
} else {
mIvSex.setVisibility(GONE);
}
ImageUtils.loadImageView(framePicture, mIvFrame);
}
public void setOnline(boolean isOnline) {
mIvOnline.setVisibility(VISIBLE);
mIvOnline.setImageResource(isOnline ? R.mipmap.me_online_icon : R.mipmap.me_icon_unchecked);
}
}

View File

@@ -0,0 +1,94 @@
package com.xscm.moduleutil.widget;
import android.app.Dialog;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TabHost;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.xscm.moduleutil.R;
import java.util.Calendar;
import java.util.Date;
public class DoubleTimePickerBottomSheet extends BottomSheetDialogFragment {
private OnTimeRangeSelectedListener listener;
public interface OnTimeRangeSelectedListener {
void onTimeRangeSelected(Date startDate, Date endDate);
}
public void setOnTimeRangeSelectedListener(OnTimeRangeSelectedListener listener) {
this.listener = listener;
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
return new CustomBottomSheetDialog(requireContext());
}
@NonNull
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.dialog_double_time_picker, container, false);
// 初始化 TabHost
TabHost tabHost = view.findViewById(R.id.tabHost);
tabHost.setup();
tabHost.addTab(tabHost.newTabSpec("start").setIndicator("开始时间").setContent(R.id.start_time_tab));
tabHost.addTab(tabHost.newTabSpec("end").setIndicator("结束时间").setContent(R.id.end_time_tab));
// 初始化 Wheel Views
WheelDatePicker wheelStartDate = view.findViewById(R.id.wheel_start_date);
WheelTimePicker wheelStartTime = view.findViewById(R.id.wheel_start_time);
WheelDatePicker wheelEndDate = view.findViewById(R.id.wheel_end_date);
WheelTimePicker wheelEndTime = view.findViewById(R.id.wheel_end_time);
// 默认设置当前时间
Calendar startCal = Calendar.getInstance();
wheelStartDate.init(startCal.get(Calendar.YEAR), startCal.get(Calendar.MONTH), startCal.get(Calendar.DAY_OF_MONTH));
wheelStartTime.init(startCal.get(Calendar.HOUR_OF_DAY), startCal.get(Calendar.MINUTE),startCal.get(Calendar.SECOND));
// 设置默认结束时间略晚于开始时间
Calendar endCal = (Calendar) startCal.clone();
endCal.add(Calendar.HOUR, 1);
wheelEndDate.init(endCal.get(Calendar.YEAR), endCal.get(Calendar.MONTH), endCal.get(Calendar.DAY_OF_MONTH));
wheelEndTime.init(endCal.get(Calendar.HOUR_OF_DAY), endCal.get(Calendar.MINUTE),endCal.get(Calendar.SECOND));
// 取消按钮
view.findViewById(R.id.btn_cancel).setOnClickListener(v -> dismiss());
// 确定按钮
view.findViewById(R.id.btn_sure).setOnClickListener(v -> {
Calendar start = Calendar.getInstance();
start.set(wheelStartDate.getYear(), wheelStartDate.getMonth(), wheelStartDate.getDay(),
wheelStartTime.getHour(), wheelStartTime.getMinute(), wheelStartTime.getSecond());
Calendar end = Calendar.getInstance();
end.set(wheelEndDate.getYear(), wheelEndDate.getMonth(), wheelEndDate.getDay(),
wheelEndTime.getHour(), wheelEndTime.getMinute(), wheelEndTime.getSecond());
if (end.getTime().before(start.getTime())) {
Toast.makeText(requireContext(), "结束时间不能早于开始时间", Toast.LENGTH_SHORT).show();
return;
}
if (listener != null) {
listener.onTimeRangeSelected(start.getTime(), end.getTime());
}
dismiss();
});
return view;
}
}

View File

@@ -0,0 +1,180 @@
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;
import com.xscm.moduleutil.R;
public class DropView extends LinearLayout {
public DropView(Context context) {
super(context);
init();
}
public DropView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DropView(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();
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() : 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;
}
}
}
}

View File

@@ -0,0 +1,121 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
import androidx.appcompat.widget.AppCompatImageView;
import com.bumptech.glide.integration.webp.decoder.WebpDrawable;
import com.xscm.moduleutil.bean.FaceBean;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ExpressionImgView extends AppCompatImageView {
private Context mContext;
private Runnable goneCmd = new Runnable() {
@Override
public void run() {
setVisibility(GONE);
}
};
public ExpressionImgView(Context context) {
this(context, null);
}
public ExpressionImgView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ExpressionImgView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
}
public void addData(FaceBean faceBean) {
setVisibility(VISIBLE);
removeCallbacks(goneCmd);
if (faceBean.getType() == 1) {
String url = faceBean.getFace_spectial();
if (TextUtils.isEmpty(url)) {
return;
}
// GlideApp.with(mContext).load(url).listener(new RequestListener<Drawable>() {
// @Override
// public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
// return false;
// }
//
// @Override
// public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
// postDelayed(goneCmd, faceBean.getMillTime() == 0 ? 3000 : Math.max(faceBean.getMillTime(), 1000));
// return false;
// }
// }).skipMemoryCache(true).into(this);
} else {
// GlideApp.with(mContext).load(String.format("http://soundriver.oss-cn-hangzhou.aliyuncs.com/custom/random%ss.webp", faceBean.getNumber())).into(new SimpleTarget<Drawable>() {
// @Override
// public void onResourceReady(@NonNull Drawable drawable, @Nullable Transition<? super Drawable> transition) {
// if (drawable instanceof WebpDrawable) {
// ExpressionImgView.this.setImageDrawable(drawable);
// ((WebpDrawable) drawable).start();
// ((WebpDrawable) drawable).setLoopCount(1);
// ((WebpDrawable) drawable).registerAnimationCallback(new Animatable2Compat.AnimationCallback() {
// @Override
// public void onAnimationEnd(Drawable drawable) {
// super.onAnimationEnd(drawable);
// postDelayed(goneCmd, 500);
// }
// });
// }
// }
// });
}
}
private int getWebpPlayTime(Drawable resource) {
WebpDrawable webpDrawable = (WebpDrawable) resource;
try {
Field gifStateField = WebpDrawable.class.getDeclaredField("state");
gifStateField.setAccessible(true);
Class gifStateClass = Class.forName("com.bumptech.glide.integration.webp.decoder.WebpDrawable$WebpState");
Field gifFrameLoaderField = gifStateClass.getDeclaredField("frameLoader");
gifFrameLoaderField.setAccessible(true);
Class gifFrameLoaderClass = Class.forName("com.bumptech.glide.integration.webp.decoder.WebpFrameLoader");
Field gifDecoderField = gifFrameLoaderClass.getDeclaredField("webpDecoder");
gifDecoderField.setAccessible(true);
Class gifDecoderClass = Class.forName("com.bumptech.glide.integration.webp.decoder.WebpDecoder");
Object gifDecoder = gifDecoderField.get(gifFrameLoaderField.get(gifStateField.get(resource)));
Method getDelayMethod = gifDecoderClass.getDeclaredMethod("getDelay", int.class);
getDelayMethod.setAccessible(true);
// 设置只播放一次
// 获得总帧数
int count = webpDrawable.getFrameCount();
int delay = 0;
for (int i = 0; i < count; i++) {
// 计算每一帧所需要的时间进行累加
delay += (int) getDelayMethod.invoke(gifDecoder, i);
}
return delay;
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
public void remove() {
setVisibility(GONE);
}
}

View File

@@ -0,0 +1,124 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
import androidx.annotation.ColorInt;
import androidx.annotation.DimenRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;
import com.xscm.moduleutil.R;
public class GifAvatarOvalView extends AppCompatImageView {
public GifAvatarOvalView(@NonNull Context context) {
super(context);
init(null);
}
public GifAvatarOvalView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public GifAvatarOvalView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
protected int mWidth;
protected int mHeight;
protected float mRadius;
public static final float DEFAULT_BORDER_WIDTH = 0f;
public static final int DEFAULT_BORDER_COLOR = Color.WHITE;
protected float mBorderWidth = DEFAULT_BORDER_WIDTH;
protected ColorStateList mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR);
protected RectF mBorderRect = new RectF();
protected Paint mBorderPaint;
private void init(@Nullable AttributeSet attrs) {
if (attrs != null) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.GifAvatarOvalView);
mBorderWidth = a.getDimensionPixelSize(R.styleable.GifAvatarOvalView_gav_border_width, -1);
if (mBorderWidth < 0) {
mBorderWidth = DEFAULT_BORDER_WIDTH;
}
mBorderColor = a.getColorStateList(R.styleable.GifAvatarOvalView_gav_border_color);
if (mBorderColor == null) {
mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR);
}
a.recycle();
}
mBorderPaint = new Paint();
mBorderPaint.setStyle(Paint.Style.STROKE);
mBorderPaint.setAntiAlias(true);
mBorderPaint.setColor(mBorderColor.getDefaultColor());
mBorderPaint.setStrokeWidth(mBorderWidth);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mWidth = mHeight = Math.min(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
mRadius = mWidth / 2.0f;
mBorderRect.set(0, 0, mWidth, mHeight);
setMeasuredDimension(mWidth, mHeight);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
setClipToOutline(true);
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
Rect selfRect = new Rect(0, 0, mWidth, mHeight);
outline.setRoundRect(selfRect, mRadius);
}
});
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mBorderWidth > 0) {
canvas.drawOval(mBorderRect, mBorderPaint);
}
}
public void setBorderWidth(@DimenRes int resId) {
setBorderWidth(getResources().getDimension(resId));
}
public void setBorderWidth(float width) {
if (mBorderWidth == width) { return; }
mBorderWidth = width;
mBorderPaint.setStrokeWidth(mBorderWidth);
invalidate();
}
public void setBorderColor(@ColorInt int color) {
setBorderColor(ColorStateList.valueOf(color));
}
public void setBorderColor(ColorStateList colors) {
if (mBorderColor.equals(colors)) { return; }
mBorderColor = (colors != null) ? colors : ColorStateList.valueOf(DEFAULT_BORDER_COLOR);
mBorderPaint.setColor(mBorderColor.getDefaultColor());
if (mBorderWidth > 0) {
invalidate();
}
}
}

View File

@@ -0,0 +1,43 @@
package com.xscm.moduleutil.widget;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.widget.Toast;
import java.io.IOException;
import java.io.OutputStream;
public class ImageSaveUtils {
public static void saveImg(Context context, Bitmap bitmap) {
String fileName = "image_" + System.currentTimeMillis() + ".jpg";
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
values.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
ContentResolver resolver = context.getContentResolver();
Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
try (OutputStream os = resolver.openOutputStream(uri)) {
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
Toast.makeText(context, "图片已保存到相册", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(context, "保存失败", Toast.LENGTH_SHORT).show();
}
// 发送广播通知系统刷新图库
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
mediaScanIntent.setData(uri);
context.sendBroadcast(mediaScanIntent);
}
}

View File

@@ -0,0 +1,65 @@
package com.xscm.moduleutil.widget;
import android.view.View;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public class InfintLinearLayoutManager extends LinearLayoutManager {
public InfintLinearLayoutManager(RecyclerView recyclerView) {
super(recyclerView.getContext(), HORIZONTAL, false);
}
@Override
public boolean canScrollHorizontally() {
return true; // 允许水平滑动
}
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
return super.scrollVerticallyBy(dy, recycler, state);
}
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
// 获取最后一个和第一个可见 item 的位置
int first = findFirstVisibleItemPosition();
int last = findLastVisibleItemPosition();
if (first == 0 && dx > 0) {
// 滑动到了最左边,现在往右滑,把最后一个 item 移动到前面复用
View lastView = getChildAt(last);
if (lastView != null) {
layoutScrapView(lastView, last, -1);
offsetChildrenHorizontal(-getDecoratedMeasuredWidth(lastView));
return dx;
}
} else if (last == getItemCount() - 1 && dx < 0) {
// 滑动到了最右边,现在往左滑,把第一个 item 移动到最后面复用
View firstView = getChildAt(first);
if (firstView != null) {
layoutScrapView(firstView, first, +1);
offsetChildrenHorizontal(+getDecoratedMeasuredWidth(firstView));
return dx;
}
}
return super.scrollHorizontallyBy(dx, recycler, state);
}
private void layoutScrapView(View view, int position, int direction) {
removeView(view);
// 复用的 item 设置为新的位置
int newPosition = direction > 0 ? getItemCount() - 1 : 0;
addView(view, direction > 0 ? getChildCount() : 0);
// 更新 adapter 的 position
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
layoutDecoratedWithMargins(view,
0,
0,
getDecoratedMeasuredWidth(view),
getDecoratedMeasuredHeight(view));
}
}

View File

@@ -0,0 +1,63 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.AttributeSet;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatTextView;
public class MarqueeTextView extends AppCompatTextView {
public MarqueeTextView(Context context) {
this(context, null);
}
public MarqueeTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
// 设置单行
setSingleLine();
// setMaxLines(1);
//设置 EllipsizesetMaxLines(1) 和 setEllipsize 冲突
setEllipsize(TextUtils.TruncateAt.MARQUEE);
//获取焦距
setFocusable(true);
//走马灯的重复次数,-1代表无限重复
setMarqueeRepeatLimit(-1);
//强制获得焦点
setFocusableInTouchMode(true);
}
/**
* 用于 EditText 存在时抢占焦点
*/
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (focused) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
}
/**
* Window与Window间焦点发生改变时的回调
* 解决 Dialog 抢占焦点问题
*
* @param hasWindowFocus
*/
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
if (hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
}
}
@Override
public boolean isFocused() {//必须重写且返回值是true表示始终获取焦点
return true;
}
}

View File

@@ -0,0 +1,80 @@
package com.xscm.moduleutil.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Xfermode;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
/**
* 圆形头像
*/
@SuppressLint("AppCompatCustomView")
public abstract class MaskedImage extends ImageView {
private static final Xfermode MASK_XFERMODE;
private Bitmap mask;
private Paint paint;
static {
PorterDuff.Mode localMode = PorterDuff.Mode.DST_IN;
MASK_XFERMODE = new PorterDuffXfermode(localMode);
}
public MaskedImage(Context paramContext) {
super(paramContext);
}
public MaskedImage(Context paramContext, AttributeSet paramAttributeSet) {
super(paramContext, paramAttributeSet);
}
public MaskedImage(Context paramContext, AttributeSet paramAttributeSet, int paramInt) {
super(paramContext, paramAttributeSet, paramInt);
}
public abstract Bitmap createMask();
protected void onDraw(Canvas paramCanvas) {
Drawable localDrawable = getDrawable();
if (localDrawable == null)
return;
try {
if (this.paint == null) {
Paint localPaint1 = new Paint();
this.paint = localPaint1;
this.paint.setFilterBitmap(false);
Paint localPaint2 = this.paint;
Xfermode localXfermode1 = MASK_XFERMODE;
@SuppressWarnings("unused")
Xfermode localXfermode2 = localPaint2.setXfermode(localXfermode1);
}
float f1 = getWidth();
float f2 = getHeight();
int i = paramCanvas.saveLayer(0.0F, 0.0F, f1, f2, null, Canvas.ALL_SAVE_FLAG);
int j = getWidth();
int k = getHeight();
localDrawable.setBounds(0, 0, j, k);
localDrawable.draw(paramCanvas);
if ((this.mask == null) || (this.mask.isRecycled())) {
Bitmap localBitmap1 = createMask();
this.mask = localBitmap1;
}
Bitmap localBitmap2 = this.mask;
Paint localPaint3 = this.paint;
paramCanvas.drawBitmap(localBitmap2, 0.0F, 0.0F, localPaint3);
paramCanvas.restoreToCount(i);
return;
} catch (Exception localException) {
StringBuilder localStringBuilder = new StringBuilder()
.append("Attempting to draw with recycled bitmap. View ID = ");
System.out.println("localStringBuilder==" + localStringBuilder);
}
}
}

View File

@@ -0,0 +1,66 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.LinearInterpolator;
import androidx.databinding.DataBindingUtil;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.databinding.RoomViewMusicRatationBinding;
import com.xscm.moduleutil.widget.floatingView.FloatingMagnetView;
public class MusicRotationView extends FloatingMagnetView {
private RoomViewMusicRatationBinding mBinding;
public MusicRotationView(Context context) {
this(context, null, 0);
}
public MusicRotationView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MusicRotationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mBinding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.room_view_music_ratation,this,true);
}
/**
* 开启图片旋转动画
*/
public void startRotateAnimation() {
Animation rotateAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.image_rotate);
LinearInterpolator lin = new LinearInterpolator();
rotateAnimation.setInterpolator(lin);
mBinding.rivAvatar.setAnimation(rotateAnimation);
mBinding.rivAvatar.setVisibility(View.VISIBLE);
}
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
if (visibility == GONE) {
// RtcManager.getInstance().setAudioUrl(null);
}
}
/**
* 关闭图片旋转动画
*/
public void endRotateAnimation() {
mBinding.rivAvatar.clearAnimation();
mBinding.rivAvatar.setAnimation(null);
mBinding.rivAvatar.setVisibility(View.VISIBLE);
}
@Override
protected boolean isNearestLeft() {
return true;
}
}

View File

@@ -0,0 +1,294 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.SeekBar;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import com.petterp.floatingx.assist.helper.FxScopeHelper;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.databinding.RoomDialogMusicWindowOpenBinding;
import com.xscm.moduleutil.widget.floatingView.FloatingMagnetView;
public class MusicView extends FloatingMagnetView {
private Context mContext;
private int playPattern = 1; //1循环播放 2单曲循环 3随机播放
private boolean isPaly = false;
// private MusicTable mMusicTable;
private OnItemClickListener mOnItemClickListener;
// private List<MusicTable> musicTables = new ArrayList<>();
private RoomDialogMusicWindowOpenBinding mBinding;
public MusicView(@NonNull Context context) {
super(context);
initView(context);
}
public MusicView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public MusicView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
@Override
protected boolean isNearestLeft() {
return true;
}
private void initView(Context context) {
this.mContext = context;
mBinding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.room_dialog_music_window_open,this,true);
FxScopeHelper.builder().setLayout(R.layout.room_dialog_music_window_open).build().toControl( this);
// playPattern = SpUtils.getPlayPattern();
initListener();
switch (playPattern) {
case 1:
// mBinding.ivPattern.setImageResource(R.mipmap.room_music_win_circulation);
break;
case 2:
// mBinding.ivPattern.setImageResource(R.mipmap.room_music_win_singlecircle);
break;
case 3:
// mBinding.ivPattern.setImageResource(R.mipmap.room_music_win_random);
break;
}
// mBinding.seekBar.setProgress(SpUtils.getChannelVolume());
mBinding.ivMinx.setOnClickListener(this::onClick);
mBinding.ivMusicPlayState.setOnClickListener(this::onClick);
mBinding.ivList.setOnClickListener(this::onClick);
mBinding.ivNext.setOnClickListener(this::onClick);
// mBinding.ivLast.setOnClickListener(this::onClick);
// mBinding.ivPattern.setOnClickListener(this::onClick);
mBinding.flParent.setOnClickListener(this::onMusicDismiss);
mBinding.rlPlayRegion.setOnClickListener(this::onRegion);
}
private void initListener() {
mBinding.seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean b) {
if (b) {
if (progress < 0) {
progress = 0;
}
if (progress > 100) {
progress = 100;
}
if (mOnItemClickListener != null) {
mOnItemClickListener.setMusicVolume((int) (progress * 0.6));
}
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
//icon_music_stop
public void setPalyState(boolean b) {
this.isPaly = b;
if (isPaly) {
// mBinding.ivMusicPlayState.setImageResource(R.mipmap.room_music_win_start);
} else {
mBinding.ivMusicPlayState.setImageResource(R.mipmap.room_music_win_puase);
}
}
// public void setData(MusicTable musicTable) {
// this.mMusicTable = musicTable;
// mBinding.tvMusicTitle.setText(mMusicTable.getTitle());
// mBinding.tvSinger.setText(mMusicTable.getAuthor());
// }
public void onClick(View view) {
int id = view.getId();
if (id == R.id.iv_minx) {
if (mOnItemClickListener != null) {
mOnItemClickListener.minimize();
}
} else if (id == R.id.iv_list) {
if (mOnItemClickListener != null) {
mOnItemClickListener.openMusicList();
}
} else if (id == R.id.iv_music_play_state) {
if (mOnItemClickListener != null) {
if (isPaly) {
mOnItemClickListener.pausePlay();
} else {
mOnItemClickListener.resumePlay();
}
}
} else if (id == R.id.iv_next) {
next();
// } else if (id == R.id.iv_last) {
// last();
}
// else if (id == R.id.iv_pattern) {
// if (playPattern == 1) {
// playPattern = 2;
// } else if (playPattern == 2) {
// playPattern = 3;
// } else if (playPattern == 3) {
// playPattern = 1;
// }
// switch (playPattern) {
// case 1:
//// mBinding.ivPattern.setImageResource(R.mipmap.room_music_win_circulation);
// break;
// case 2:
// mBinding.ivPattern.setImageResource(R.mipmap.room_music_win_singlecircle);
// break;
// case 3:
//// mBinding.ivPattern.setImageResource(R.mipmap.room_music_win_random);
// break;
// }
//// SpUtils.setPlayPattern(playPattern);
// }
}
public void addOnItemClickListener(OnItemClickListener onItemClickListener) {
this.mOnItemClickListener = onItemClickListener;
}
// public void setMusicList(List<MusicTable> lovalMusicData) {
// this.musicTables = lovalMusicData;
// }
public interface OnItemClickListener {
void minimize();
void openMusicList();
// void playMusic(MusicTable musicTable);
void stopPlay();
void pausePlay();
void resumePlay();
void setMusicVolume(int progress);
}
// public void initData() {
// musicTables = DbController.getInstance(mContext).queryMusicListAll();
// }
public void next() {
// initData();
// if (musicTables.size() == 0) {
// return;
// }
// musicTables.size();
// int index = 0;
// if (mMusicTable != null) {
// for (int i = 0; i < musicTables.size(); i++) {
// if (mMusicTable.getId().equals(musicTables.get(i).getId())) {
// index = i;
// break;
// }
// }
// }
// //下一首播放逻辑
// if (playPattern == 1) {//列表循环
// index++;
// } else if (playPattern == 3) {//随机播放
// if (musicTables.size() > 1) {
// Random random = new Random();
// index = random.nextInt(musicTables.size() - 1);
// }
// }
//
// if (index == musicTables.size()) {
// mMusicTable = musicTables.get(0);
// } else {
// mMusicTable = musicTables.get(index);
// }
// if (mOnItemClickListener != null) {
// mOnItemClickListener.playMusic(mMusicTable);
// }
// setData(mMusicTable);
}
public void last() {
// initData();
// int index = 0;
// for (int i = 0; i < musicTables.size(); i++) {
// if (mMusicTable.getId().equals(musicTables.get(i).getId())) {
// index = i;
// break;
// }
// }
// //上一首播放逻辑
// if (playPattern == 1) {//列表循环
// index--;
// } else if (playPattern == 3) {//随机播放
// Random random = new Random();
// index = random.nextInt(musicTables.size() - 1);
// }
// if (index <= 0) {
// mMusicTable = musicTables.get(musicTables.size() - 1);
// } else {
// mMusicTable = musicTables.get(index);
// }
// if (mOnItemClickListener != null) {
// mOnItemClickListener.playMusic(mMusicTable);
// }
// setData(mMusicTable);
}
/**
* 更新轮循方式
*/
public void updateLoopType(int type) {
playPattern = type;
// if (type == 1) {
// mBinding.ivPattern.setImageResource(R.mipmap.room_music_win_circulation);
// } else if (type == 2) {
// mBinding.ivPattern.setImageResource(R.mipmap.room_music_win_singlecircle);
// } else {
// mBinding.ivPattern.setImageResource(R.mipmap.room_music_win_random);
// }
}
/**
* 点击外部弹窗消失
*/
public void onMusicDismiss(View view) {
if (mOnItemClickListener != null) {
mOnItemClickListener.minimize();
}
}
/**
* 点击播放器内部无效
*/
public void onRegion(View view) {
}
}

View File

@@ -0,0 +1,38 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.GridView;
public class MyGridView extends GridView {
public MyGridView(Context context) {
super(context);
}
public MyGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightSpec;
if (getLayoutParams().height == LayoutParams.WRAP_CONTENT) {
// The great Android "hackatlon", the love, the magic.
// The two leftmost bits in the height measure spec have
// a special meaning, hence we can't use them to describe height.
heightSpec = MeasureSpec.makeMeasureSpec(
Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
}
else {
// Any other height should be respected as is.
heightSpec = heightMeasureSpec;
}
super.onMeasure(widthMeasureSpec, heightSpec);
}
}

View File

@@ -0,0 +1,47 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.util.AttributeSet;
import androidx.appcompat.widget.AppCompatImageView;
import com.xscm.moduleutil.R;
public class NewView extends AppCompatImageView {
private Context mContext;
public NewView(Context context) {
super(context);
initView(context);
}
public NewView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public NewView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context) {
this.mContext = context;
}
public void setNew(int isNew) {
if (isNew == 0) {
this.setVisibility(GONE);
} else if (isNew == 1) {
this.setVisibility(VISIBLE);
setImageResource(R.mipmap.ic_user_new);
}
}
}

View File

@@ -0,0 +1,38 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import androidx.appcompat.widget.AppCompatImageView;
public class NobilityView extends AppCompatImageView {
private Context mContext;
public NobilityView(Context context) {
super(context);
initView(context);
}
public NobilityView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public NobilityView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context) {
this.mContext = context;
}
public void setNobility(String url) {
setVisibility(TextUtils.isEmpty(url) ? GONE : VISIBLE);
com.xscm.moduleutil.utils.ImageUtils.loadImageView(url, this);
}
}

View File

@@ -0,0 +1,609 @@
package com.xscm.moduleutil.widget;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import com.xscm.moduleutil.R;
public class PKProgressBar extends View {
// 文字背景颜色
private int textBgColor = 0;
// 绘制跟随进度条移动的数字的画笔
private Paint paintNumber;
private Paint bitmapPaint;
private Paint paintBar;
private Paint paintOtherBar;
private Paint paintLight;
private Paint leftTextPaintBg;
private Paint rightTextPaintBg;
private int backGroundColor = Color.GRAY;
private int barColor = Color.RED;
private Drawable leftDrawable;
private Drawable rightDrawable;
private Drawable rightDrawableOne;
private Drawable rightDrawableTwo;
private Drawable rightDrawableThree;
private OnProgressChangeListener onProgressChangeListener;
private int halfDrawableWidth = 0;
private int halfDrawableHeight = 0;
private int barPadding = 0;
private boolean isRound = true;
private float progress = 50f;
private float max = 100f;
private double progressPercentage = 0.5;
private float viewWidth = 0f;
private int viewHeight = 0;
private int progressWidth = 100;
private RectF rectFBG = new RectF();
private RectF rectFPB = new RectF();
private RectF rectFPBO = new RectF();
private Path barRoundPath = new Path();
private Path barRoundPathOther = new Path();
private Rect rectLeftText = new Rect();
private Rect rectRightText = new Rect();
private Rect rectLeftFollowText = new Rect();
private Rect rectRightFollowText = new Rect();
private Paint paintRightText;
private Paint paintLeftText;
private Paint paintBackGround;
// 是否使用颜色渐变器
private boolean isGradient = false;
// 颜色渐变器
private LinearGradient linearGradient;
private LinearGradient linearGradientOther;
private int gradientStartColor = Color.RED;
private int gradientEndColor = Color.YELLOW;
private int otherGradientStartColor = Color.RED;
private int otherGradientEndColor = Color.YELLOW;
private int barStartWidth = 0;
// 进度条最大绘制位置与最小绘制位置
private int barEndWidth = 0;
// 圆角大小
private float barRadioSize = 0f;
private int textColor = Color.WHITE;
private int textSize = 12;
private boolean textIsBold = true;
// 蓝方
private String leftTextStr = "";
// 红方
private String rightTextStr = "";
private String leftTeamName = "";
private String leftTeamCount = "";
private String rightTeamName = "";
private String rightTeamCount = "";
private String leftFollowTextStr = "";
private String rightFollowTextStr = "";
private Bitmap lightBitmap;
private int lightDrawableId = 0;
private float leftTextStrWidth = 0f;
private float leftTextStrHeight = 0f;
private float leftTextStrMargin = 21;
private float commonMargin = 6;
private float leftTextStrBg = 57;
private float rightTextStrWidth = 0f;
private float rightTextStrHeight = 0f;
private float rightTextStrMargin = 21;
private float leftFollowWidth = 0f;
private float leftFollowHeight = 0f;
private float rightFollowWidth = 0f;
private float rightFollowHeight = 0f;
private float followMargin = 16;
/**
* 左侧队名显示状态
*/
private boolean leftTeamNameVisible = true;
/**
* 左侧团队人数的显示状态
*/
private boolean leftTeamCountVisible = true;
/**
* 右侧队名显示状态
*/
private boolean rightTeamNameVisible = true;
/**
* 右侧团队人数的显示状态
*/
private boolean rightTeamCountVisible = true;
/**
* 左侧的队名宽度
*/
private float leftTeamNameWidth = 0f;
/**
* 左侧的团队人数宽度
*/
private float leftTeamCountWidth = 0f;
/**
* 右侧的队名的宽度
*/
private float rightTeamNameWidth = 0f;
/**
* 右侧的团队人数宽度
*/
private float rightTeamCountWidth = 0f;
/**
* 右侧用户头像边距
*/
private final float rightAvatarMargin = 10;
/**
* 右侧用户头像宽度
*/
private final float rightAvatarwidth = 58;
public PKProgressBar(Context context) {
super(context);
init();
}
public PKProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.PKProgressBar_pk, 0, 0);
backGroundColor = typedArray.getColor(R.styleable.PKProgressBar_pk_backGroundColor, Color.GRAY);
barColor = typedArray.getColor(R.styleable.PKProgressBar_pk_barColor, Color.RED);
leftDrawable = typedArray.getDrawable(R.styleable.PKProgressBar_pk_leftDrawableBg);
rightDrawable = typedArray.getDrawable(R.styleable.PKProgressBar_pk_rightDrawableBg);
halfDrawableWidth = typedArray.getDimensionPixelSize(R.styleable.PKProgressBar_pk_halfDrawableWidth, 1);
halfDrawableHeight = typedArray.getDimensionPixelSize(R.styleable.PKProgressBar_pk_halfDrawableHeight, 1);
barPadding = typedArray.getDimensionPixelSize(R.styleable.PKProgressBar_pk_barPadding, 0);
isRound = typedArray.getBoolean(R.styleable.PKProgressBar_pk_isRound, true);
max = typedArray.getInt(R.styleable.PKProgressBar_pk_max, 100);
lightDrawableId = typedArray.getResourceId(R.styleable.PKProgressBar_pk_lightDrawable, 0);
textColor = typedArray.getColor(R.styleable.PKProgressBar_pk_textColor, Color.BLACK);
textSize = typedArray.getDimensionPixelSize(R.styleable.PKProgressBar_pk_textSize, 13);
textIsBold = typedArray.getBoolean(R.styleable.PKProgressBar_pk_textIsBold, false);
isGradient = typedArray.getBoolean(R.styleable.PKProgressBar_pk_isGradient, false);
gradientStartColor = typedArray.getColor(R.styleable.PKProgressBar_pk_gradientStartColor, Color.RED);
gradientEndColor = typedArray.getColor(R.styleable.PKProgressBar_pk_gradientEndColor, Color.YELLOW);
otherGradientStartColor = typedArray.getColor(R.styleable.PKProgressBar_pk_otherGradientStartColor, Color.RED);
otherGradientEndColor = typedArray.getColor(R.styleable.PKProgressBar_pk_otherGradientEndColor, Color.YELLOW);
typedArray.recycle();
init();
}
public PKProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
// 底部边框背景
paintBackGround = new Paint();
paintBackGround.setColor(backGroundColor);
paintBackGround.setAntiAlias(true);
paintLight = new Paint();
paintLight.setAntiAlias(true);
// 左半部分进度条
paintBar = new Paint();
paintBar.setColor(barColor);
paintBar.setAntiAlias(true);
// 右半部分进度条
paintOtherBar = new Paint();
paintOtherBar.setColor(barColor);
paintOtherBar.setAntiAlias(true);
// 左半部分文字
// Typeface plain = Typeface.createFromAsset(getContext().getAssets(), "Roboto-BlackItalic.ttf");/**/
paintLeftText = new Paint();
// paintLeftText.setTypeface(plain);
paintLeftText.setStyle(Paint.Style.FILL);
paintLeftText.setColor(textColor);
paintLeftText.setTextSize(textSize);
leftTextStrWidth = paintLeftText != null ? paintLeftText.measureText(leftTextStr) : 0;
leftTextStrHeight = paintLeftText != null ? paintLeftText.descent() - paintLeftText.ascent() : 0;
// 团队数字
// Typeface plainNumber = Typeface.createFromAsset(getContext().getAssets(), "Roboto-BlackItalic.ttf");
paintNumber = new Paint();
// paintNumber.setTypeface(plainNumber);
paintNumber.setStyle(Paint.Style.FILL);
paintNumber.setColor(textColor);
paintNumber.setTextSize(24);
// 左半部文字的背景
leftTextPaintBg = new Paint();
leftTextPaintBg.setStyle(Paint.Style.FILL);
leftTextPaintBg.setColor(textBgColor);
leftTextPaintBg.setAntiAlias(true);
// 右半部分文字
paintRightText = new Paint();
paintRightText.setStyle(Paint.Style.FILL);
paintRightText.setColor(textColor);
paintRightText.setTextSize(textSize);
paintRightText.setFakeBoldText(textIsBold);
paintRightText.setAntiAlias(true);
paintRightText.setTextSkewX(-0.25f);
if (lightDrawableId != 0) {
lightBitmap = BitmapFactory.decodeResource(getResources(), lightDrawableId);
}
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
// 是否需要渐变器
if (isGradient) {
linearGradient = new LinearGradient(
0f,
viewHeight / 2f,
progressWidth,
viewHeight / 2f,
gradientStartColor,
gradientEndColor,
Shader.TileMode.CLAMP
);
linearGradientOther = new LinearGradient(
0f,
viewHeight / 2f,
progressWidth,
viewHeight / 2f,
otherGradientStartColor,
otherGradientEndColor,
Shader.TileMode.CLAMP
);
if (paintBar != null) {
paintBar.setShader(linearGradient);
}
if (paintOtherBar != null) {
paintOtherBar.setShader(linearGradientOther);
}
}
return false;
}
});
}
@Override
protected synchronized void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 光标水平位置
viewWidth = ((progressWidth - 3 * barPadding) * (float) progressPercentage + barPadding);
viewWidth = Math.max(viewWidth, leftTextStrBg + leftTextStrMargin + leftFollowWidth);
viewWidth = Math.min(
viewWidth,
progressWidth - commonMargin - barPadding - rightAvatarMargin - rightAvatarwidth - rightFollowWidth - followMargin
);
viewHeight = getHeight();
// 圆角大小,为实际进度条高度一半
if (isRound) {
barRadioSize = (viewHeight - barPadding * 2) / 2f;
}
// 绘制底部边框
drawBackground(canvas);
// 绘制左半部分进度条
drawLeftBar(canvas);
// 绘制右半部分进度条
drawRightBar(canvas);
// 绘制透明图层
drawPicture(canvas);
// 绘制横向半透明光柱
drawLight(canvas);
// 绘制跟随进度条移动的文字
drawFollowText(canvas);
// 绘制队伍信息
drawTeamInfoText(canvas);
}
private void drawFollowText(Canvas canvas) {
float leftFollowStart = getBoundaryPosition() - leftFollowWidth - halfDrawableWidth - followMargin;
float rightFollowStart = getBoundaryPosition() + rightFollowWidth + halfDrawableWidth + followMargin;
leftTeamCountVisible = leftFollowStart > leftTextStrBg + leftTeamNameWidth + leftTextStrMargin * 2 + leftTeamCountWidth;
leftTeamNameVisible = leftFollowStart > leftTextStrBg + commonMargin + leftTeamNameWidth;
rightTeamCountVisible = rightFollowStart < (progressWidth - commonMargin * 2 - barPadding - rightAvatarMargin - rightAvatarwidth - rightTeamNameWidth - rightTeamCountWidth);
rightTeamNameVisible = rightFollowStart < (progressWidth - commonMargin - rightTeamNameWidth - barPadding - rightAvatarMargin - rightAvatarwidth);
}
private void drawLight(Canvas canvas) {
if (lightBitmap == null) {
return;
}
canvas.save();
// 将画布坐标系移动到画布中央
canvas.translate(0f, barPadding);
Rect src = new Rect(barStartWidth, 0, barEndWidth, (viewHeight - barPadding) / 2);
canvas.drawBitmap(lightBitmap, src, src, paintLight);
canvas.restore();
}
private void drawBackground(Canvas canvas) {
canvas.save();
rectFBG.set(0f, 0f, progressWidth, viewHeight);
if (isRound) {
Path path = new Path();
path.addRoundRect(new RectF(0f, 0f, getMeasuredWidth(), getMeasuredHeight()), viewHeight / 2f, viewHeight / 2f, Path.Direction.CW);
// 先对canvas进行裁剪
canvas.clipPath(path, Region.Op.INTERSECT);
} else {
if (paintBackGround != null) {
canvas.drawRect(rectFBG, paintBackGround);
}
}
canvas.restore();
}
private void drawLeftBar(Canvas canvas) {
float right = getBoundaryPosition();
float[] radios = {
barRadioSize, barRadioSize, 0f, 0f, 0f, 0f, barRadioSize, barRadioSize
};
canvas.save();
rectFPB.set(barPadding, barPadding, right - halfDrawableWidth, viewHeight - barPadding);
barRoundPath.reset();
barRoundPath.addRoundRect(rectFPB, radios, Path.Direction.CCW);
barRoundPath.close();
if (isRound) {
if (paintBar != null) {
canvas.drawPath(barRoundPath, paintBar);
}
} else {
if (paintBar != null) {
canvas.drawRect(rectFPB, paintBar);
}
}
canvas.restore();
}
private void drawRightBar(Canvas canvas) {
float left = getBoundaryPosition();
float[] radios = {
0f, 0f,
barRadioSize, barRadioSize,
barRadioSize, barRadioSize, 0f, 0f
};
canvas.save();
rectFPBO.set(left + halfDrawableWidth, barPadding, barEndWidth, viewHeight - barPadding);
barRoundPathOther.reset();
barRoundPathOther.addRoundRect(rectFPBO, radios, Path.Direction.CCW);
barRoundPathOther.close();
if (isRound) {
if (paintOtherBar != null) {
canvas.drawPath(barRoundPathOther, paintOtherBar);
}
} else {
if (paintOtherBar != null) {
canvas.drawRect(rectFPBO, paintOtherBar);
}
}
canvas.restore();
}
private void drawTeamInfoText(Canvas canvas) {
// 左侧
// 绘制文字的背景
if (leftTextPaintBg != null) {
leftTextPaintBg.setColor(textBgColor);
canvas.drawRoundRect(13, 13, 57, viewHeight - 13, viewHeight / 2f, viewHeight / 2f, leftTextPaintBg);
}
if (paintLeftText != null) {
canvas.drawText(leftTextStr, leftTextStrMargin, viewHeight / 2f + leftTextStrHeight / 3f, paintLeftText);
}
if (leftTeamNameVisible && paintLeftText != null) {
// 队名
canvas.drawText(leftTeamName, leftTextStrBg + commonMargin, viewHeight / 2f + leftTextStrHeight / 3f, paintLeftText);
}
if (leftTeamCountVisible && paintLeftText != null) {
// 队伍人数
// canvas.drawText(leftTeamCount, leftTextStrBg + leftTeamNameWidth + commonMargin * 2, viewHeight / 2f + leftTextStrHeight / 3f, paintLeftText);
}
// 右侧
if (rightTeamNameVisible && paintRightText != null) {
// 右侧队名
canvas.drawText(rightTeamName, progressWidth - commonMargin - rightTeamNameWidth - barPadding - rightAvatarMargin - rightAvatarwidth, viewHeight / 2f + leftTextStrHeight / 3f, paintRightText);
}
if (rightTeamCountVisible && paintRightText != null) {
// 右侧团队人数
// canvas.drawText(rightTeamCount, progressWidth - commonMargin * 2 - barPadding - rightAvatarMargin - rightAvatarwidth - rightTeamNameWidth - rightTeamCountWidth, viewHeight / 2f + leftTextStrHeight / 3f, paintRightText);
}
}
/**
* 获取极限位置进度条的绘制位置
* 1. 最左边
* 2. 最左边到圆角区段位置
* 3. 中间正常位置
* 4. 最右边圆角区段位置
* 5. 最右边
*
* @return 极限位置进度条的绘制位置
*/
private float getBoundaryPosition() {
// 默认为计算的比例位置
float boundaryPos = viewWidth;
if (progressPercentage == 0.0 || viewWidth == barStartWidth) {
// 光标位于最左边
boundaryPos = barPadding + leftTextStrBg + leftTextStrMargin + leftFollowWidth;
} else if (progressPercentage == 1.0 || viewWidth == barEndWidth) {
// 光标位于最右边
boundaryPos = barEndWidth - rightAvatarwidth - rightAvatarMargin;
} else if ((viewWidth - barStartWidth < barRadioSize || viewWidth - barStartWidth == barRadioSize)
&& viewWidth > barStartWidth) {
boundaryPos = Math.max(viewWidth, barRadioSize + barStartWidth);
} else if ((viewWidth > barEndWidth - barRadioSize || viewWidth == barEndWidth - barRadioSize)
&& viewWidth < barEndWidth) {
// 光标位于进度条右侧弧形区域
boundaryPos = Math.min(viewWidth, barEndWidth - barRadioSize);
}
return boundaryPos;
}
private void drawPicture(Canvas canvas) {
if (leftDrawable != null) {
leftDrawable.setBounds(0, 0, 180, viewHeight);
leftDrawable.draw(canvas);
}
if (rightDrawable != null) {
rightDrawable.setBounds(
(int) rectFPBO.right - 180,
(int) rectFPBO.top,
(int) rectFPBO.right,
(int) rectFPBO.bottom
);
rightDrawable.draw(canvas);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = MeasureSpec.getSize(heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
width = halfDrawableWidth * 2;
}
if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
height = halfDrawableHeight * 2;
}
// 记录进度条总长度
progressWidth = width;
// 记录进度条起始位置
barStartWidth = barPadding;
// 记录进度条结束位置
barEndWidth = progressWidth - barPadding;
setMeasuredDimension(width, height);
leftTextStrWidth = paintRightText != null ? paintRightText.measureText(leftTextStr) : 0;
leftTextStrHeight = paintLeftText != null ? paintLeftText.descent() - paintLeftText.ascent() : 0;
rightTextStrWidth = paintRightText != null ? paintRightText.measureText(rightTextStr) : 0;
rightTextStrHeight = paintRightText != null ? paintRightText.descent() - paintRightText.ascent() : 0;
leftFollowWidth = paintNumber != null ? paintNumber.measureText(leftFollowTextStr) : 0;
leftFollowHeight = paintNumber != null ? paintNumber.descent() - paintNumber.ascent() : 0;
rightFollowWidth = paintNumber != null ? paintNumber.measureText(rightFollowTextStr) : 0;
rightFollowHeight = paintNumber != null ? paintNumber.descent() - paintNumber.ascent() : 0;
rightTeamNameWidth = paintLeftText != null ? paintLeftText.measureText(rightTeamName) : 0;
rightTeamCountWidth = paintLeftText != null ? paintLeftText.measureText(rightTeamCount) : 0;
leftTeamNameWidth = paintLeftText != null ? paintLeftText.measureText(leftTeamName) : 0;
leftTeamCountWidth = paintLeftText != null ? paintLeftText.measureText(leftTeamCount) : 0;
}
/**
* 进度变化监听器接口
*/
public interface OnProgressChangeListener {
/**
* 当进度发生变化时触发
*
* @param progress 当前进度值
*/
void onOnProgressChange(int progress);
/**
* 当进度完成时触发
*/
void onOnProgressFinish();
}
/**
* 根据左右值设置动画进度
*
* @param leftValue 左值
* @param rightValue 右值
*/
public synchronized void setAnimProgress(long leftValue, long rightValue) {
leftFollowTextStr = String.valueOf(leftValue);
rightFollowTextStr = String.valueOf(rightValue);
if (leftValue + rightValue == 0L) {
setAnimProgress(50f);
} else {
setAnimProgress(100f * leftValue / (leftValue + rightValue));
}
}
/**
* 传入百分比值设置动画进度
*
* @param progressValue 百分比值
*/
public void setAnimProgress(float progressValue) {
if (progressValue < 0 || progressValue > 100) {
return;
}
ValueAnimator animator = ValueAnimator.ofFloat(progress, progressValue);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
setProgress(Float.parseFloat(valueAnimator.getAnimatedValue().toString()));
}
});
Interpolator interpolator = new AccelerateDecelerateInterpolator();
animator.setInterpolator(interpolator);
animator.setDuration((long) (Math.abs(progress - progressValue) * 20));
animator.start();
}
public void setProgress(float progressValue) {
if (progress <= max) {
progress = progressValue;
} else if (progress < 0) {
progress = 0f;
} else {
progress = max;
}
progressPercentage = progress / max;
doProgressRefresh();
invalidate();
}
private synchronized void doProgressRefresh() {
if (onProgressChangeListener != null) {
onProgressChangeListener.onOnProgressChange((int) progress);
if (progress >= max) {
onProgressChangeListener.onOnProgressFinish();
}
}
}
// 设置颜色渐变器
public void setLinearGradient(LinearGradient linearGradient) {
this.linearGradient = linearGradient;
}
// 设置进度监听器
public void setOnProgressChangeListener(OnProgressChangeListener onProgressChangeListener) {
this.onProgressChangeListener = onProgressChangeListener;
}
}

View File

@@ -0,0 +1,54 @@
package com.xscm.moduleutil.widget;
import android.text.TextUtils;
import java.util.Map;
public class PayResult {
private String resultStatus;
private String result;
private String memo;
public PayResult(Map<String, String> rawResult) {
if (rawResult == null) {
return;
}
for (String key : rawResult.keySet()) {
if (TextUtils.equals(key, "resultStatus")) {
resultStatus = rawResult.get(key);
} else if (TextUtils.equals(key, "result")) {
result = rawResult.get(key);
} else if (TextUtils.equals(key, "memo")) {
memo = rawResult.get(key);
}
}
}
@Override
public String toString() {
return "resultStatus={" + resultStatus + "};memo={" + memo
+ "};result={" + result + "}";
}
/**
* @return the resultStatus
*/
public String getResultStatus() {
return resultStatus;
}
/**
* @return the memo
*/
public String getMemo() {
return memo;
}
/**
* @return the result
*/
public String getResult() {
return result;
}
}

View File

@@ -0,0 +1,115 @@
package com.xscm.moduleutil.widget;
import static com.xscm.moduleutil.utils.permission.PermissionUtils.TAG;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import com.alipay.sdk.app.PayTask;
import com.blankj.utilcode.util.ToastUtils;
import com.orhanobut.logger.Logger;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.AppPay;
import com.xscm.moduleutil.event.PayEvent;
import com.tencent.liteav.base.Log;
import com.tencent.mm.opensdk.constants.ConstantsAPI;
import com.tencent.mm.opensdk.modelbase.BaseResp;
import com.tencent.mm.opensdk.modelpay.PayReq;
import com.tencent.mm.opensdk.openapi.IWXAPI;
import org.greenrobot.eventbus.EventBus;
import java.util.Map;
public class PaymentUtil {
private static final int SDK_PAY_FLAG = 1;
public static void payAlipay(final Context mContext, String orderNo) {
// 订单信息
final String orderInfo = orderNo;
Runnable payRunnable = new Runnable() {
@Override
public void run() {
PayTask alipay = new PayTask((Activity) mContext);
Map<String, String> result = alipay.payV2(orderInfo, true);
Message msg = new Message();
msg.what = SDK_PAY_FLAG;
msg.obj = result;
mHandler.sendMessage(msg);
}
};
// 必须异步调用
Thread payThread = new Thread(payRunnable);
payThread.start();
}
public static void payWxMiniProgram2(IWXAPI api, AppPay appPay) {
if (!api.isWXAppInstalled()) {
ToastUtils.showShort("请安装微信");
return;
}
PayReq request = new PayReq();
request.appId = appPay.getWx().getAppid();
request.partnerId = appPay.getWx().getPartnerid();
request.prepayId= appPay.getWx().getPrepayid();
request.packageValue = "Sign=WXPay";
request.nonceStr= appPay.getWx().getNoncestr();
request.timeStamp= appPay.getWx().getTimestamp();
request.sign= appPay.getWx().getSign();
api.sendReq(request);
Logger.e("@@@@@","payWxMiniProgram2:"+request);
}
public void onResp(BaseResp resp){
if(resp.getType()== ConstantsAPI.COMMAND_PAY_BY_WX){
Log.d(TAG,"onPayFinish,errCode="+resp.errCode);
// AlertDialog.Builder builder=new AlertDialog.Builder(this);
// builder.setTitle("");
}
}
@SuppressLint("HandlerLeak")
public static Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SDK_PAY_FLAG: {
PayResult payResult = new PayResult((Map<String, String>) msg.obj);
/**
对于支付结果,请商户依赖服务端的异步通知结果。同步通知结果,仅作为支付结束的通知。
*/
// 同步返回需要验证的信息
String resultInfo = payResult.getResult();
String resultStatus = payResult.getResultStatus();
// 判断resultStatus 为9000则代表支付成功
if (TextUtils.equals(resultStatus, "9000")) {
// 该笔订单是否真实支付成功,需要依赖服务端的异步通知。
ToastUtils.showShort("支付成功");
PayEvent messageEvent = new PayEvent(Constants.PAYSUCESS, "支付成功");
EventBus.getDefault().post(messageEvent);
} else if (TextUtils.equals(resultStatus, "6001")) {
// 该笔订单是否真实支付成功,需要依赖服务端的异步通知。
ToastUtils.showShort("支付取消");
PayEvent messageEvent = new PayEvent(-2, "支付取消");
EventBus.getDefault().post(messageEvent);
} else {
// 该笔订单真实的支付结果,需要依赖服务端的异步通知。
ToastUtils.showShort("支付失败");
// StatisticsUtils.addEvent(MyApplication.getInstance(), Constant.OpenInstall.ALIRECHARGEFAIL);
// StatisticsUtils.addEvent(MyApplication.getInstance(), Constant.OpenInstall.RECHARGEFAIL);
}
break;
}
default:
break;
}
}
};
}

View File

@@ -0,0 +1,168 @@
package com.xscm.moduleutil.widget;
import static com.liulishuo.okdownload.OkDownloadProvider.context;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.TextView;
import com.blankj.utilcode.util.LogUtils;
import com.petterp.floatingx.FloatingX;
import com.petterp.floatingx.assist.FxGravity;
import com.petterp.floatingx.assist.helper.FxAppHelper;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.event.MqttBean;
import com.xscm.moduleutil.utils.ImageUtils;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* @Author
* @Time 2025/7/18 21:52
* @Description 飘屏管理器
*/
public class PiaoPingManager {
private static PiaoPingManager instance;
private WindowManager windowManager;
private View piaoPingView;
private boolean isPiaoPingShown = false;
private Queue<MqttBean> messageQueue = new ConcurrentLinkedQueue<>(); // 消息队列
private boolean isAnimating = false; // 动画状态标记
private PiaoPingManager(Context context) {
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
if (windowManager == null) return;
// 创建飘屏消息的布局
LayoutInflater inflater = LayoutInflater.from(context);
piaoPingView = inflater.inflate(R.layout.item_piaoping, null);
}
public static synchronized PiaoPingManager getInstance(Context context) {
if (instance == null) {
instance = new PiaoPingManager(context.getApplicationContext());
}
return instance;
}
public void showPiaoPingMessage(MqttBean mqttBean) {
// 创建 FloatingX 配置
// 添加到队列
messageQueue.offer(mqttBean);
// 如果当前没有动画正在进行,则开始处理
if (!isAnimating) {
processNextMessage();
}
}
private void processNextMessage() {
if (messageQueue.isEmpty()) {
isAnimating = false;
return;
}
isAnimating = true;
MqttBean mqttBean = messageQueue.poll();
displayMessage(mqttBean);
}
private void displayMessage(MqttBean mqttBean) {
// 获取悬浮窗视图并设置内容
View floatingView = LayoutInflater.from(context).inflate(R.layout.item_piaoping, new FrameLayout(context), false);
TextView textView = floatingView.findViewById(R.id.tv_name);
TextView textView2 = floatingView.findViewById(R.id.tv_to_name);
textView2.setText("送给" + mqttBean.getList().getToUserName());
textView.setText(mqttBean.getList().getFromUserName());
ImageUtils.loadHeadCC(mqttBean.getList().getGift_picture(), floatingView.findViewById(R.id.iv_piaoping));
TextView tv_time = floatingView.findViewById(R.id.tv_num);
tv_time.setText("x" + mqttBean.getList().getNumber());
// 先将视图放置在屏幕右侧外部,避免闪现问题
floatingView.setTranslationX(10000); // 先放到屏幕外
FxAppHelper fxAppHelper = FxAppHelper.builder()
.setContext(context)
.setLayoutView(floatingView)
.setGravity(FxGravity.RIGHT_OR_TOP)
.setX(0)
.setY(100)
.build();
FloatingX.install(fxAppHelper).show();
// 首先从右侧滑入到屏幕中央
floatingView.post(() -> {
// 确保初始位置在屏幕右侧外部
floatingView.setTranslationX(floatingView.getWidth());
// 第一阶段:从右到屏幕右侧边缘(缓慢进入)
ObjectAnimator animator1 = ObjectAnimator.ofFloat(floatingView, "translationX",
floatingView.getWidth(), 0f);
animator1.setDuration(1500); // 延长动画时间到1.5秒
animator1.setInterpolator(new DecelerateInterpolator(2.0f)); // 更平缓的减速效果
animator1.start();
// 第二阶段延迟1秒后从当前位置向左滑出
floatingView.postDelayed(() -> {
ObjectAnimator animator2 = ObjectAnimator.ofFloat(floatingView, "translationX",
0f, -floatingView.getWidth());
animator2.setDuration(1500); // 延长动画时间到1.5秒
animator2.setInterpolator(new DecelerateInterpolator(2.0f)); // 更平缓的减速效果
animator2.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// 动画结束后移除悬浮窗
FloatingX.uninstallAll();
// 处理下一个消息
processNextMessage();
}
});
animator2.start();
}, 1000); // 停留1秒
});
}
public void subscribe() {
try {
if (!EventBus.getDefault().isRegistered(this)) {
EventBus.getDefault().register(this);
}
} catch (Exception e) {
// 处理重复注册或其他异常
LogUtils.e("PiaoPingManager subscribe error: " + e.getMessage());
}
}
public void unsubscribe() {
try {
if (EventBus.getDefault().isRegistered(this)) {
EventBus.getDefault().unregister(this);
}
} catch (Exception e) {
// 处理取消注册异常
LogUtils.e("PiaoPingManager unsubscribe error: " + e.getMessage());
}
messageQueue.clear();
isAnimating = false; // 重置动画状态
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageReceived(MqttBean mqttBean) {
showPiaoPingMessage(mqttBean);
// FxAppHelper fxAppHelper = FxAppHelper.builder().setContext( context).setLayout(R.layout.item_piaoping).build();
// FloatingX.install(fxAppHelper).show();
}
}

View File

@@ -0,0 +1,79 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
public class RankRecycleView extends RecyclerView {
private float mDownPosX = 0;
private float mDownPosY = 0;
public RankRecycleView(@NonNull Context context) {
super(context);
}
public RankRecycleView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public RankRecycleView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
requestDisallowInterceptTouchEvent(false);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
final float x = ev.getX();
final float y = ev.getY();
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mDownPosX = x;
mDownPosY = y;
break;
case MotionEvent.ACTION_MOVE:
final float deltaX = Math.abs(x - mDownPosX);
final float deltaY = Math.abs(y - mDownPosY);
//拦截左右滑动
if (deltaX > deltaY) {
return false;
}
}
return super.onTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final float x = ev.getX();
final float y = ev.getY();
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mDownPosX = x;
mDownPosY = y;
break;
case MotionEvent.ACTION_MOVE:
final float deltaX = Math.abs(x - mDownPosX);
final float deltaY = Math.abs(y - mDownPosY);
//拦截左右滑动
if (deltaX > deltaY) {
return false;
}
}
return super.onInterceptTouchEvent(ev);
}
}

View File

@@ -0,0 +1,55 @@
package com.xscm.moduleutil.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;
import androidx.annotation.Nullable;
import com.xscm.moduleutil.R;
@SuppressLint("AppCompatCustomView")
public class RoleView extends ImageView {
private Context mContext;
public RoleView(Context context) {
super(context);
initView(context);
}
public RoleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public RoleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context) {
this.mContext = context;
// setVisibility(GONE);
// setImageResource(R.mipmap.img_host);
}
public void setRole(int role) {
this.setVisibility(VISIBLE);
if (role == 1) {
setImageResource(R.mipmap.img_host);
} else if (role == 2) {
setImageResource(R.mipmap.img_admin);
} else if (role == 5) {
setImageResource(R.mipmap.img_official);
} else {
this.setVisibility(GONE);
}
}
}

View File

@@ -0,0 +1,250 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.UserInfo;
import com.xscm.moduleutil.bean.room.RoomPitBean;
import com.xscm.moduleutil.utils.ImageUtils;
import com.xscm.moduleutil.utils.SpUtil;
/**
* 项目名称 qipao-android
* 包名com.yutang.xqipao.utils.view
* 创建人 王欧
* 创建时间 2020/4/9 12:46 PM
* 描述 describe
*/
public class RoomDefaultWheatView extends BaseWheatView {
public ImageView mIvTagBoss;
public TextView mTvTime;
public TextView tv_time_pk;
private boolean showBoss;//显示老板标识
public RoomDefaultWheatView(Context context) {
this(context, null, 0);
}
public RoomDefaultWheatView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoomDefaultWheatView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void initPit(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoomDefaultWheatView);
pitNumber = typedArray.getString(R.styleable.RoomDefaultWheatView_room_wheat_number);
typedArray.recycle();
mIvTagBoss = findViewById(R.id.iv_tag_boos);
mTvTime = findViewById(R.id.tv_time);
tv_time_pk = findViewById(R.id.tv_time_pk);
}
@Override
protected int getLayoutId() {
return R.layout.room_view_default_wheat;
}
@Override
protected void setPitData(RoomPitBean bean) {
sex = bean.getSex();
if (isOn()) {
//开启声浪
mIvRipple.stopAnimation();
mIvRipple.setVisibility(VISIBLE);
mTvName.setText(bean.getNickname());
ImageUtils.loadHeadCC(bean.getAvatar(), mRiv);
if (TextUtils.isEmpty(pitBean.getDress())) {
mIvFrame.setVisibility(INVISIBLE);
} else {
mIvFrame.setVisibility(VISIBLE);
mIvFrame.setSource(pitBean.getDress(), 1);
// ImageUtils.loadDecorationAvatar(pitBean.getDress_picture(), mIvFrame);
}
if (showBoss && WHEAT_BOSS.equals(pitNumber)) {
mIvTagBoss.setVisibility(GONE);
}
} else {
mTvName.setText(
"-1".equals(pitNumber) ? "" :
"9".equals(pitNumber) ? "主持位" :
"10".equals(pitNumber) ? "嘉宾位" :
pitNumber + "号麦位"
);
//麦位上锁
if (showBoss && WHEAT_BOSS.equals(pitNumber)) {
mIvTagBoss.setVisibility(VISIBLE);
ImageUtils.loadRes(isLocked() ? R.mipmap.room_ic_wheat_default_suo : R.mipmap.room_ic_wheat_default, mRiv);
} else {
// mIvTagBoss.setVisibility(GONE);
// @DrawableRes int origin = getOriginImage();
// ImageUtils.loadRes(isLocked() ? R.mipmap.room_ic_wheat_default_suo :
// (origin == 0 ? R.mipmap.room_ic_wheat_default : origin), mRiv);
mRiv.setImageResource(bean.getIs_lock() == 1 ? R.mipmap.room_ic_wheat_default_suo : R.mipmap.room_ic_wheat_default);
// ImageUtils.loadRes(isLocked() ? R.mipmap.room_ic_wheat_default_suo : R.mipmap.room_ic_wheat_default, mRiv);
}
if (isMute()) {
ImageUtils.loadRes(R.mipmap.room_microphone_off, mIvSex);
}
mIvFrame.setVisibility(INVISIBLE);
mIvFace.remove();
//停止声浪
mIvRipple.stopAnimation();
mIvRipple.setVisibility(GONE);
}
if (showSexIcon) {
checkSex();
}
if (pitBean.getNickname() == null || pitBean.getNickname().isEmpty()) {
mCharmView.setVisibility(GONE);
} else {
mCharmView.setVisibility(VISIBLE);
}
if (pitBean.is_pk() ){
if (pitBean.getUser_id()!=null && !pitBean.getUser_id().equals("0") && !pitBean.getUser_id().isEmpty()) {
tv_time_pk.setVisibility(VISIBLE);
setSex(pitBean.getCharm(),false);
mCharmView.setVisibility(GONE);
}else {
tv_time_pk.setVisibility(GONE);
}
// ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mCharmView.getLayoutParams();
// params.width = 35;
// mCharmView.setLayoutParams(params);
}else {
tv_time_pk.setVisibility(GONE);
mCharmView.setVisibility(VISIBLE);
// ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mCharmView.getLayoutParams();
// params.width = 52;
// mCharmView.setLayoutParams(params);
}
// setCardiac(pitBean.getPit_number(), 0.0f);
}
public void setSex( String value, boolean format) {
if (format) {
tv_time_pk.setText(value);
} else {
try {
long xd = Long.parseLong(value);
if (xd > 9999 || xd < -9999) {
tv_time_pk.setText(String.format("%.2fw", xd / 10000.0f));
// mBinding.tvValue.setText(String.valueOf(xd));
} else {
tv_time_pk.setText(value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private boolean showSexIcon = false;
private String sex;
public boolean isMale() {
return "1".equals(sex);
}
public boolean isFemale() {
return "2".equals(sex);
}
public void setShowSexIcon(boolean show) {
showSexIcon = show;
}
public void checkSex() {
if (isOn()) {
mIvSex.setVisibility(VISIBLE);
if (!TextUtils.isEmpty(sex)) {
if (UserInfo.MALE.equals(sex)) {
mIvSex.setBackgroundResource(R.drawable.room_xq_wheat_male_mask);
if (mTvNo != null) mTvNo.setBackgroundResource(R.mipmap.ic_room_xq_wno_male);
} else {
mIvSex.setBackgroundResource(R.drawable.room_xq_wheat_female_mask);
if (mTvNo != null) mTvNo.setBackgroundResource(R.mipmap.ic_room_xq_wno_female);
}
} else {
mIvSex.setVisibility(GONE);
if (mTvNo != null) mTvNo.setBackgroundResource(getOriginNoImage());
}
} else {
mIvSex.setVisibility(GONE);
if (mTvNo != null) mTvNo.setBackgroundResource(getOriginNoImage());
}
}
/**
* 是否显示老板标识
*/
public void setIsBossShow(String is_boss_pit) {
showBoss = "1".equals(is_boss_pit);
}
/**
* 开启计时
*/
public void setTime(int time) {
if (time == 0) {
mTvTime.setText("");
mTvTime.setVisibility(INVISIBLE);
} else {
mTvTime.setText(String.format("%s'%s", time / 60, time % 60));
mTvTime.setVisibility(VISIBLE);
}
}
public void hideMaoziIcon() {
View maozi = findViewById(R.id.iv_maozi);
if (maozi != null) maozi.setVisibility(GONE);
}
@Override
public void onRemoteSoundLevelUpdate(String userId, int soundLevel) {
}
@Override
public void onLocalSoundLevelUpdate(int volume) {
if (volume==0){
mIvRipple.stopAnimation();
} else {
if (pitBean.getUser_id().equals(SpUtil.getUserId()) && closePhone) {
mIvRipple.stopAnimation();
}else {
mIvRipple.post(() -> {
if (!mIvRipple.isAnimating()) {
mIvRipple.startAnimation();
}
mIvRipple.setVisibility(VISIBLE);
});
}
}
}
@Override
public void userJoined(int userId, int elapsd) {
}
@Override
public void userOffline(int userId, int reason) {
}
}

View File

@@ -0,0 +1,307 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.UserOnlineStatusBean;
import com.xscm.moduleutil.bean.room.RoomPitBean;
import com.xscm.moduleutil.color.ThemeableDrawableUtils;
import com.xscm.moduleutil.utils.ImageUtils;
public class RoomFriendshipWheatView extends BaseWheatView {
public ImageView mIvTagBoss;
public TextView mTvTime;
public ImageView iv_on_line;
private ImageView iv_tag_type;
public WheatCharmView mCharmView;
public TextView tv_zhul;
private boolean showBoss;//显示老板标识
private boolean showSexIcon = false;
private String sex;
public RoomFriendshipWheatView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RoomFriendshipWheatView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public RoomFriendshipWheatView(Context context) {
super(context);
}
@Override
protected void initPit(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoomMakeWheatView);
pitNumber = typedArray.getString(R.styleable.RoomMakeWheatView_room_make_wheat_number);
pitImageVId = typedArray.getResourceId(R.styleable.RoomMakeWheatView_room_make_pic, 0);
typedArray.recycle();
mIvTagBoss = findViewById(R.id.iv_tag_boos);
mTvTime = findViewById(R.id.tv_time);
iv_on_line = findViewById(R.id.iv_online);
iv_tag_type = findViewById(R.id.iv_tag_type);
mCharmView = findViewById(R.id.charm_view);
mRiv.setImageResource(pitImageVId);
tv_zhul = findViewById(R.id.tv_zhul);
// 为 tv_zhul 设置点击监听
if (tv_zhul != null) {
tv_zhul.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mOnZhulClickListener != null && pitBean != null) {
mOnZhulClickListener.onZhulClick(RoomFriendshipWheatView.this, pitBean);
}
}
});
}
}
@Override
protected int getLayoutId() {
return R.layout.room_view_friendship_wheat;
}
@Override
protected void setPitData(RoomPitBean bean) {
sex = bean.getSex();
if (isOn()) {
//开启声浪
mIvRipple.stopAnimation();
mIvRipple.setVisibility(VISIBLE);
mTvName.setText(bean.getNickname());
ImageUtils.loadCenterCrop(bean.getAvatar(), mRiv);
if (TextUtils.isEmpty(pitBean.getDress_picture())) {
mIvFrame.setVisibility(INVISIBLE);
} else {
mIvFrame.setVisibility(VISIBLE);
}
if (showBoss && WHEAT_BOSS.equals(pitNumber)) {
mIvTagBoss.setVisibility(GONE);
}
} else {
mTvName.setText((!"10".equals(pitBean.getPit_number()) && !"9".equals(pitBean.getPit_number())) ? pitBean.getPit_number() : "");
//麦位上锁
//麦位上锁
// if (showBoss && WHEAT_BOSS.equals(pitNumber)) {
// mIvTagBoss.setVisibility(VISIBLE);
// ImageUtils.loadRes(isLocked() ? R.mipmap.room_ic_wheat_default_suo : R.mipmap.jiaoy, mRiv);
// } else {
// mRiv.setImageResource(bean.getIs_lock() == 1 ? R.mipmap.room_ic_wheat_default_suo : R.mipmap.jiaoy);
// }
if (pitBean.getPit_number().equals("4") || pitBean.getPit_number().equals("5") || pitBean.getPit_number().equals("6")) {
ImageUtils.loadRes(R.mipmap.jiaoy_n, mRiv);
} else {
ImageUtils.loadRes(R.mipmap.jiaoy, mRiv);
}
if (isMute()) {
ImageUtils.loadRes(R.mipmap.room_microphone_off, mIvSex);
}
mIvFrame.setVisibility(INVISIBLE);
mIvFace.remove();
//停止声浪
mIvRipple.stopAnimation();
mIvRipple.setVisibility(GONE);
}
if (showSexIcon) {
checkSex();
}
if (pitBean.getPit_number().equals("9")) {
iv_tag_type.setVisibility(VISIBLE);
iv_tag_type.setImageResource(R.mipmap.zc);
switchCharmViewPosition(false);
tv_zhul.setVisibility(GONE);
} else if (pitBean.getPit_number().equals("10")) {
iv_tag_type.setVisibility(VISIBLE);
iv_tag_type.setImageResource(R.mipmap.jb);
switchCharmViewPosition(false);
tv_zhul.setVisibility(GONE);
} else {
iv_tag_type.setVisibility(GONE);
switchCharmViewPosition(true);
// tv_zhul.setVisibility(VISIBLE);
}
if (pitBean.getPit_number().equals("1") || pitBean.getPit_number().equals("2") || pitBean.getPit_number().equals("3")) {
ThemeableDrawableUtils.setThemeableRoundedBackground(tv_zhul, Color.parseColor("#D449E4"), 53);
tv_zhul.setTextColor(Color.parseColor("#FFFFFF"));
} else if (pitBean.getPit_number().equals("4") || pitBean.getPit_number().equals("5") || pitBean.getPit_number().equals("6")) {
ThemeableDrawableUtils.setThemeableRoundedBackground(tv_zhul, Color.parseColor("#4965E4"), 53);
tv_zhul.setTextColor(Color.parseColor("#FFFFFF"));
}
}
public void checkSex() {
if (isOn()) {
mIvSex.setVisibility(VISIBLE);
} else {
mIvSex.setVisibility(GONE);
if (mTvNo != null) mTvNo.setBackgroundResource(getOriginNoImage());
}
}
/**
* 切换 charm_view 的显示位置
*
* @param showAtTagTypePosition true-显示在 iv_tag_type 位置false-显示在原始位置
*/
public void switchCharmViewPosition(boolean showAtTagTypePosition) {
if (mCharmView == null) return;
// 确保视图已经加载完成
if (getWidth() == 0 || getHeight() == 0) {
post(() -> switchCharmViewPosition(showAtTagTypePosition));
return;
}
ConstraintSet constraintSet = new ConstraintSet();
ConstraintLayout constraintLayout = (ConstraintLayout) getChildAt(0);
constraintSet.clone(constraintLayout);
if (showAtTagTypePosition) {
// 显示在 tag_type 位置(替代 tag_type 的位置)
if (iv_tag_type != null) {
iv_tag_type.setVisibility(GONE);
}
// 清除原有约束
constraintSet.clear(R.id.charm_view, ConstraintSet.TOP);
// constraintSet.clear(R.id.charm_view, ConstraintSet.START);
// constraintSet.clear(R.id.charm_view, ConstraintSet.END);
// 设置新约束(与 iv_tag_type 相同的位置)
constraintSet.connect(
R.id.charm_view, ConstraintSet.TOP,
R.id.riv, ConstraintSet.BOTTOM,
-dip2px(12) // 与 iv_tag_type 相同的 margin
);
// constraintSet.connect(
// R.id.charm_view, ConstraintSet.START,
// R.id.riv, ConstraintSet.START,
// 0
// );
//
// constraintSet.connect(
// R.id.charm_view, ConstraintSet.END,
// R.id.riv, ConstraintSet.END,
// 0
// );
mCharmView.setBg(R.drawable.room_bg_wheat_jy_charm);
} else {
// 恢复原始位置(在 tv_name 下面)
// 显示 iv_tag_type如果需要
if (iv_tag_type != null) {
iv_tag_type.setVisibility(VISIBLE);
}
// 清除临时约束
constraintSet.clear(R.id.charm_view, ConstraintSet.TOP);
constraintSet.clear(R.id.charm_view, ConstraintSet.START);
constraintSet.clear(R.id.charm_view, ConstraintSet.END);
// 恢复原始约束
constraintSet.connect(
R.id.charm_view, ConstraintSet.TOP,
R.id.tv_name, ConstraintSet.BOTTOM,
0
);
constraintSet.connect(
R.id.charm_view, ConstraintSet.START,
R.id.riv, ConstraintSet.START,
0
);
constraintSet.connect(
R.id.charm_view, ConstraintSet.END,
R.id.riv, ConstraintSet.END,
0
);
}
constraintSet.applyTo(constraintLayout);
}
// 添加dp转px的工具方法
private int dip2px(float dpValue) {
final float scale = getContext().getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* 开启计时
*/
public void setTime(int time) {
if (time == 0) {
mTvTime.setText("");
mTvTime.setVisibility(INVISIBLE);
} else {
mTvTime.setText(String.format("%s'%s", time / 60, time % 60));
mTvTime.setVisibility(VISIBLE);
}
}
@Override
public void onRemoteSoundLevelUpdate(String userId, int soundLevel) {
}
@Override
public void onLocalSoundLevelUpdate(int volume) {
}
@Override
public void userJoined(int userId, int elapsd) {
}
@Override
public void userOffline(int userId, int reason) {
}
public void setOnlineStatus(UserOnlineStatusBean isOnline) {
if (pitBean.getUser_id() != null && !pitBean.getUser_id().equals("0") && !pitBean.getUser_id().isEmpty()) {
if (pitBean.getUser_id().equals(isOnline.getUser_id())) {
if (isOnline.getIs_online() == 1) {
iv_on_line.setVisibility(GONE);
} else {
iv_on_line.setVisibility(VISIBLE);
}
}
}
}
/// 设置是否显示助力按钮
public void setLockZl(boolean lock) {
tv_zhul.setVisibility(lock ? VISIBLE : INVISIBLE);
}
private OnZhulClickListener mOnZhulClickListener;
// 设置监听器的方法
public void setOnZhulClickListener(OnZhulClickListener listener) {
this.mOnZhulClickListener = listener;
}
public interface OnZhulClickListener {
void onZhulClick(RoomFriendshipWheatView view, RoomPitBean pitBean);
}
}

View File

@@ -0,0 +1,254 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.UserInfo;
import com.xscm.moduleutil.bean.room.RoomPitBean;
import com.xscm.moduleutil.utils.ImageUtils;
import com.xscm.moduleutil.utils.SpUtil;
/**
*@author qx
*@data 2025/8/13
*@description: K歌模式下的视图
*/
public class RoomKtvWheatView extends BaseWheatView {
public ImageView mIvTagBoss;
public TextView mTvTime;
public TextView tv_time_pk;
private boolean showBoss;//显示老板标识
public RoomKtvWheatView(Context context) {
this(context, null, 0);
}
public RoomKtvWheatView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoomKtvWheatView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void initPit(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoomDefaultWheatView);
pitNumber = typedArray.getString(R.styleable.RoomDefaultWheatView_room_wheat_number);
typedArray.recycle();
mIvTagBoss = findViewById(R.id.iv_tag_boos);
mTvTime = findViewById(R.id.tv_time);
tv_time_pk = findViewById(R.id.tv_time_pk);
}
@Override
protected int getLayoutId() {
return R.layout.room_view_ktv_wheat;
}
@Override
protected void setPitData(RoomPitBean bean) {
sex = bean.getSex();
if (isOn()) {
//开启声浪
mIvRipple.stopAnimation();
mIvRipple.setVisibility(VISIBLE);
mTvName.setText(bean.getNickname());
ImageUtils.loadHeadCC(bean.getAvatar(), mRiv);
if (TextUtils.isEmpty(pitBean.getDress())) {
mIvFrame.setVisibility(INVISIBLE);
} else {
mIvFrame.setVisibility(VISIBLE);
mIvFrame.setSource(pitBean.getDress(), 1);
// ImageUtils.loadDecorationAvatar(pitBean.getDress_picture(), mIvFrame);
}
if (showBoss && WHEAT_BOSS.equals(pitNumber)) {
mIvTagBoss.setVisibility(GONE);
}
} else {
mTvName.setText(
"-1".equals(pitNumber) ? "" :
"9".equals(pitNumber) ? "主持位" :
"10".equals(pitNumber) ? "嘉宾位" :
pitNumber + "号麦位"
);
//麦位上锁
if (showBoss && WHEAT_BOSS.equals(pitNumber)) {
mIvTagBoss.setVisibility(VISIBLE);
ImageUtils.loadRes(isLocked() ? R.mipmap.room_ic_wheat_default_suo : R.mipmap.room_ic_wheat_default, mRiv);
} else {
// mIvTagBoss.setVisibility(GONE);
// @DrawableRes int origin = getOriginImage();
// ImageUtils.loadRes(isLocked() ? R.mipmap.room_ic_wheat_default_suo :
// (origin == 0 ? R.mipmap.room_ic_wheat_default : origin), mRiv);
mRiv.setImageResource(bean.getIs_lock() == 1 ? R.mipmap.room_ic_wheat_default_suo : R.mipmap.room_ic_wheat_default);
// ImageUtils.loadRes(isLocked() ? R.mipmap.room_ic_wheat_default_suo : R.mipmap.room_ic_wheat_default, mRiv);
}
if (isMute()) {
ImageUtils.loadRes(R.mipmap.room_microphone_off, mIvSex);
}
mIvFrame.setVisibility(INVISIBLE);
mIvFace.remove();
//停止声浪
mIvRipple.stopAnimation();
mIvRipple.setVisibility(GONE);
}
if (showSexIcon) {
checkSex();
}
if (pitBean.getNickname() == null || pitBean.getNickname().isEmpty()) {
mCharmView.setVisibility(GONE);
} else {
mCharmView.setVisibility(VISIBLE);
}
if (pitBean.is_pk() ){
if (pitBean.getUser_id()!=null && !pitBean.getUser_id().equals("0") && !pitBean.getUser_id().isEmpty()) {
tv_time_pk.setVisibility(VISIBLE);
setSex(pitBean.getCharm(),false);
mCharmView.setVisibility(GONE);
}else {
tv_time_pk.setVisibility(GONE);
}
// ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mCharmView.getLayoutParams();
// params.width = 35;
// mCharmView.setLayoutParams(params);
}else {
tv_time_pk.setVisibility(GONE);
mCharmView.setVisibility(VISIBLE);
// ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mCharmView.getLayoutParams();
// params.width = 52;
// mCharmView.setLayoutParams(params);
}
// setCardiac(pitBean.getPit_number(), 0.0f);
}
public void setSex( String value, boolean format) {
if (format) {
tv_time_pk.setText(value);
} else {
try {
long xd = Long.parseLong(value);
if (xd > 9999 || xd < -9999) {
tv_time_pk.setText(String.format("%.2fw", xd / 10000.0f));
// mBinding.tvValue.setText(String.valueOf(xd));
} else {
tv_time_pk.setText(value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private boolean showSexIcon = false;
private String sex;
public boolean isMale() {
return "1".equals(sex);
}
public boolean isFemale() {
return "2".equals(sex);
}
public void setShowSexIcon(boolean show) {
showSexIcon = show;
}
public void checkSex() {
if (isOn()) {
mIvSex.setVisibility(VISIBLE);
if (!TextUtils.isEmpty(sex)) {
if (UserInfo.MALE.equals(sex)) {
mIvSex.setBackgroundResource(R.drawable.room_xq_wheat_male_mask);
if (mTvNo != null) mTvNo.setBackgroundResource(R.mipmap.ic_room_xq_wno_male);
} else {
mIvSex.setBackgroundResource(R.drawable.room_xq_wheat_female_mask);
if (mTvNo != null) mTvNo.setBackgroundResource(R.mipmap.ic_room_xq_wno_female);
}
} else {
mIvSex.setVisibility(GONE);
if (mTvNo != null) mTvNo.setBackgroundResource(getOriginNoImage());
}
} else {
mIvSex.setVisibility(GONE);
if (mTvNo != null) mTvNo.setBackgroundResource(getOriginNoImage());
}
}
/**
* 是否显示老板标识
*/
public void setIsBossShow(String is_boss_pit) {
showBoss = "1".equals(is_boss_pit);
}
/**
* 开启计时
*/
public void setTime(int time) {
if (time == 0) {
mTvTime.setText("");
mTvTime.setVisibility(INVISIBLE);
} else {
mTvTime.setText(String.format("%s'%s", time / 60, time % 60));
mTvTime.setVisibility(VISIBLE);
}
}
public void hideMaoziIcon() {
View maozi = findViewById(R.id.iv_maozi);
if (maozi != null) maozi.setVisibility(GONE);
}
@Override
public void onRemoteSoundLevelUpdate(String userId, int soundLevel) {
}
@Override
public void onLocalSoundLevelUpdate(int volume) {
if (volume==0){
mIvRipple.stopAnimation();
} else {
if (pitBean.getUser_id().equals(SpUtil.getUserId()) && closePhone) {
mIvRipple.stopAnimation();
}else {
mIvRipple.post(() -> {
if (!mIvRipple.isAnimating()) {
mIvRipple.startAnimation();
}
mIvRipple.setVisibility(VISIBLE);
});
}
}
}
@Override
public void userJoined(int userId, int elapsd) {
if (pitBean != null && pitBean.getUser_id() != null && !pitBean.getUser_id().equals("0")) {
if (pitBean.getUser_id().equals(userId + "")) {
iv_on_line.setVisibility(GONE);
}
}
}
@Override
public void userOffline(int userId, int reason) {
if (pitBean != null && pitBean.getUser_id() != null && !pitBean.getUser_id().equals("0")) {
if (pitBean.getUser_id().equals(userId + "")) {
iv_on_line.setVisibility(VISIBLE);
}
}
}
}

View File

@@ -0,0 +1,214 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.UserOnlineStatusBean;
import com.xscm.moduleutil.bean.room.RoomPitBean;
import com.xscm.moduleutil.utils.ImageUtils;
public class RoomMakeWheatView extends BaseWheatView {
public ImageView mIvTagBoss;
public TextView mTvTime;
public ImageView iv_zhul;
public ImageView iv_on_line;
private ImageView iv_tag_type;
public WheatCharmView mCharmView;
private boolean showBoss;//显示老板标识
private boolean showSexIcon = false;
private String sex;
public RoomMakeWheatView(Context context) {
this(context, null, 0);
}
public RoomMakeWheatView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoomMakeWheatView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void initPit(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoomMakeWheatView);
pitNumber = typedArray.getString(R.styleable.RoomMakeWheatView_room_make_wheat_number);
pitImageVId=typedArray.getResourceId(R.styleable.RoomMakeWheatView_room_make_pic, 0);
typedArray.recycle();
mIvTagBoss = findViewById(R.id.iv_tag_boos);
mTvTime = findViewById(R.id.tv_time);
iv_on_line=findViewById(R.id.iv_online);
iv_tag_type=findViewById(R.id.iv_tag_type);
mCharmView = findViewById(R.id.charm_view);
mRiv.setImageResource(pitImageVId);
}
@Override
protected int getLayoutId() {
return R.layout.room_view_make_wheat;
}
public void setShowZl(boolean show) {
if (iv_zhul != null) {
iv_zhul.setVisibility(show ? VISIBLE : GONE);
}
}
@Override
protected void setPitData(RoomPitBean bean) {
sex = bean.getSex();
if (isOn()) {
//开启声浪
mIvRipple.stopAnimation();
mIvRipple.setVisibility(VISIBLE);
mTvName.setText(bean.getNickname());
ImageUtils.loadCenterCrop(bean.getAvatar(), mRiv);
if (TextUtils.isEmpty(pitBean.getDress_picture())) {
mIvFrame.setVisibility(INVISIBLE);
} else {
mIvFrame.setVisibility(VISIBLE);
}
if (showBoss && WHEAT_BOSS.equals(pitNumber)) {
mIvTagBoss.setVisibility(GONE);
}
} else {
mTvName.setText((!"8".equals(pitBean.getPit_number()) && !"9".equals(pitBean.getPit_number()))? pitBean.getPit_number() : "");
//麦位上锁
//麦位上锁
if (showBoss && WHEAT_BOSS.equals(pitNumber)) {
mIvTagBoss.setVisibility(VISIBLE);
ImageUtils.loadRes(isLocked() ? R.mipmap.room_ic_wheat_default_suo : R.mipmap.room_ic_wheat_default, mRiv);
} else {
mRiv.setImageResource(bean.getIs_lock()==1 ? R.mipmap.room_ic_wheat_default_suo : R.mipmap.room_ic_wheat_default);
}
if (isMute()){
ImageUtils.loadRes(R.mipmap.room_microphone_off, mIvSex);
}
mIvFrame.setVisibility(INVISIBLE);
mIvFace.remove();
//停止声浪
mIvRipple.stopAnimation();
mIvRipple.setVisibility(GONE);
}
if (showSexIcon) {
checkSex();
}
// if (pitBean.getIs_online()==0 && pitBean.getUser_id()!=null && !pitBean.getUser_id().equals("0")){
// iv_on_line.setVisibility(VISIBLE);
// }else {
// iv_on_line.setVisibility(GONE);
// }
if (pitBean.getPit_number().equals("888")){
iv_tag_type.setVisibility(GONE);
mCharmView.setVisibility(GONE);
mTvName.setText(pitBean.getNickname()!=null && !pitBean.getNickname().equals("")?pitBean.getNickname():"拍卖者");
}else if (pitBean.getPit_number().equals("9")){
iv_tag_type.setVisibility(VISIBLE);
mCharmView.setVisibility(GONE);
}else if (pitBean.getPit_number().equals("111") || pitBean.getPit_number().equals("222")|| pitBean.getPit_number().equals("333")){
iv_tag_type.setVisibility(GONE);
mTvName.setVisibility(GONE);
mCharmView.setVisibility(GONE);
if (pitBean.getUser_id()==null || pitBean.getUser_id().equals("0") ||pitBean.getUser_id().isEmpty()){
mTvTime.setVisibility(GONE);
}else {
mTvTime.setVisibility(VISIBLE);
}
mTvTime.setText(pitBean.getCharm());
}else if (pitBean.getPit_number().equals("000")){
iv_tag_type.setVisibility(GONE);
mCharmView.setVisibility(GONE);
}
}
public void setChe(String che){
}
public boolean isMale() {
return "1".equals(sex);
}
public boolean isFemale() {
return "2".equals(sex);
}
public void setShowSexIcon(boolean show) {
showSexIcon = show;
}
public void checkSex() {
if (isOn()) {
mIvSex.setVisibility(VISIBLE);
} else {
mIvSex.setVisibility(GONE);
if (mTvNo != null) mTvNo.setBackgroundResource(getOriginNoImage());
}
}
/**
* 是否显示老板标识
*/
public void setIsBossShow(String is_boss_pit) {
showBoss = "1".equals(is_boss_pit);
}
/**
* 开启计时
*/
public void setTime(int time) {
if (time == 0) {
mTvTime.setText("");
mTvTime.setVisibility(INVISIBLE);
} else {
mTvTime.setText(String.format("%s'%s", time / 60, time % 60));
mTvTime.setVisibility(VISIBLE);
}
}
public void hideMaoziIcon() {
View maozi = findViewById(R.id.iv_maozi);
if (maozi != null) maozi.setVisibility(GONE);
}
@Override
public void onRemoteSoundLevelUpdate(String userId, int soundLevel) {
}
@Override
public void onLocalSoundLevelUpdate(int volume) {
}
@Override
public void userJoined(int userId, int elapsd) {
}
@Override
public void userOffline(int userId, int reason) {
}
public void setOnlineStatus(UserOnlineStatusBean isOnline) {
if (pitBean.getUser_id() != null && !pitBean.getUser_id().equals("0") && !pitBean.getUser_id().isEmpty()) {
if (pitBean.getUser_id().equals(isOnline.getUser_id())) {
if (isOnline.getIs_online() == 1) {
iv_on_line.setVisibility(GONE);
} else {
iv_on_line.setVisibility(VISIBLE);
}
}
}
}
}

View File

@@ -0,0 +1,137 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.os.CountDownTimer;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.blankj.utilcode.util.KeyboardUtils;
import com.hjq.toast.ToastUtils;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.RoomInputEvent;
import com.xscm.moduleutil.event.RoomInputHideEvent;
import org.greenrobot.eventbus.EventBus;
public class RoomMessageInputMenu extends ConstraintLayout {
EditText etContent;
Button tvSend;
private View rootView;
private int screenHeight;
public RoomMessageInputMenu(Context context) {
this(context, null);
}
public RoomMessageInputMenu(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.room_message_input_menu, this);
etContent = findViewById(R.id.et_content);
tvSend = findViewById(R.id.tv_send);
tvSend.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onViewClicked(v);
}
});
setVisibility(GONE);
}
private void moveInputMenuAboveKeyboard(int keypadHeight) {
// 调整输入框的位置
ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) this.getLayoutParams();
params.bottomMargin = keypadHeight;
this.setLayoutParams(params);
}
public void setText(String text) {
etContent.setText(text);
}
public void onViewClicked(View view) {
if (!canSend) {
ToastUtils.show("消息发送较频繁~");
return;
}
String text = etContent.getText().toString();
if (TextUtils.isEmpty(text)) {
ToastUtils.show("请输入评论内容");
return;
}
// ApiClient.getInstance().sendTxtFilter(text,new BaseObserver<String>() {
// @Override
// public void onSubscribe(Disposable d) {
//
// }
//
// @Override
// public void onNext(String s) {
// EventBus.getDefault().post(new RoomInputEvent(text));
// }
//
// @Override
// public void onComplete() {
//
// }
// });
sendInput(text);
dismiss();
etContent.setText("");
countDownTimer();
}
private void sendInput(String text) {
EventBus.getDefault().post(new RoomInputEvent(text));
}
/**
* 下面的内容为发送消息逻辑
*/
private CountDownTimer mCountDownTimer;
private boolean canSend = true;
private void countDownTimer() {
releaseCountDownTimer();
mCountDownTimer = new CountDownTimer(3 * 1000L, 1000L) {
@Override
public void onTick(long millisUntilFinished) {
canSend = false;
}
@Override
public void onFinish() {
canSend = true;
}
};
mCountDownTimer.start();
}
@Override
protected void onDetachedFromWindow() {
releaseCountDownTimer();
super.onDetachedFromWindow();
}
private void releaseCountDownTimer() {
if (mCountDownTimer != null) {
mCountDownTimer.cancel();
mCountDownTimer = null;
}
}
public void show() {
setVisibility(VISIBLE);
etContent.requestFocus();
KeyboardUtils.showSoftInput(etContent);
EventBus.getDefault().post(new RoomInputHideEvent(false));
}
public void dismiss() {
setVisibility(GONE);
KeyboardUtils.hideSoftInput(etContent);
EventBus.getDefault().post(new RoomInputHideEvent(true));
}
}

View File

@@ -0,0 +1,247 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.UserInfo;
import com.xscm.moduleutil.bean.room.RoomPitBean;
import com.xscm.moduleutil.utils.ImageUtils;
import com.xscm.moduleutil.utils.SpUtil;
/**
* @Author lxj$
* @Time 2025-8-6 17:27:29$ $
* @Description 二卡八视图控件$
*/
public class RoomSingSongWheatView extends BaseWheatView {
public ImageView mIvTagBoss;
public TextView mTvTime;
public TextView tv_time_pk;
private boolean showBoss;//显示老板标识
public RoomSingSongWheatView(Context context) {
this(context, null, 0);
}
public RoomSingSongWheatView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoomSingSongWheatView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void initPit(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoomDefaultWheatView);
pitNumber = typedArray.getString(R.styleable.RoomDefaultWheatView_room_wheat_number);
typedArray.recycle();
mIvTagBoss = findViewById(R.id.iv_tag_boos);
mTvTime = findViewById(R.id.tv_time);
tv_time_pk = findViewById(R.id.tv_time_pk);
}
@Override
protected int getLayoutId() {
return R.layout.room_view_sing_wheat;
}
@Override
protected void setPitData(RoomPitBean bean) {
sex = bean.getSex();
if (isOn()) {
//开启声浪
mIvRipple.stopAnimation();
mIvRipple.setVisibility(VISIBLE);
mTvName.setText(bean.getNickname());
ImageUtils.loadHeadCC(bean.getAvatar(), mRiv);
if (TextUtils.isEmpty(pitBean.getDress())) {
mIvFrame.setVisibility(INVISIBLE);
} else {
mIvFrame.setVisibility(VISIBLE);
mIvFrame.setSource(pitBean.getDress(), 1);
// ImageUtils.loadDecorationAvatar(pitBean.getDress_picture(), mIvFrame);
}
if (showBoss && WHEAT_BOSS.equals(pitNumber)) {
mIvTagBoss.setVisibility(GONE);
}
} else {
mTvName.setText(
"-1".equals(pitNumber) ? "" :
"9".equals(pitNumber) ? "主持位" :
"10".equals(pitNumber) ? "嘉宾位" :
pitNumber + "号麦位"
);
//麦位上锁
if (showBoss && WHEAT_BOSS.equals(pitNumber)) {
mIvTagBoss.setVisibility(VISIBLE);
ImageUtils.loadRes(isLocked() ? R.mipmap.room_ic_wheat_default_suo : R.mipmap.room_ic_wheat_default, mRiv);
} else {
// mIvTagBoss.setVisibility(GONE);
// @DrawableRes int origin = getOriginImage();
// ImageUtils.loadRes(isLocked() ? R.mipmap.room_ic_wheat_default_suo :
// (origin == 0 ? R.mipmap.room_ic_wheat_default : origin), mRiv);
mRiv.setImageResource(bean.getIs_lock() == 1 ? R.mipmap.room_ic_wheat_default_suo : R.mipmap.room_ic_wheat_default);
// ImageUtils.loadRes(isLocked() ? R.mipmap.room_ic_wheat_default_suo : R.mipmap.room_ic_wheat_default, mRiv);
}
if (isMute()) {
ImageUtils.loadRes(R.mipmap.room_microphone_off, mIvSex);
}
mIvFrame.setVisibility(INVISIBLE);
mIvFace.remove();
//停止声浪
mIvRipple.stopAnimation();
mIvRipple.setVisibility(GONE);
}
if (showSexIcon) {
checkSex();
}
if (pitBean.getNickname() == null || pitBean.getNickname().isEmpty()) {
mCharmView.setVisibility(GONE);
} else {
mCharmView.setVisibility(VISIBLE);
}
if (pitBean.is_pk() ){
if (pitBean.getUser_id()!=null && !pitBean.getUser_id().equals("0") && !pitBean.getUser_id().isEmpty()) {
tv_time_pk.setVisibility(VISIBLE);
setSex(pitBean.getCharm(),false);
mCharmView.setVisibility(GONE);
}else {
tv_time_pk.setVisibility(GONE);
}
// ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mCharmView.getLayoutParams();
// params.width = 35;
// mCharmView.setLayoutParams(params);
}else {
tv_time_pk.setVisibility(GONE);
mCharmView.setVisibility(VISIBLE);
// ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mCharmView.getLayoutParams();
// params.width = 52;
// mCharmView.setLayoutParams(params);
}
// setCardiac(pitBean.getPit_number(), 0.0f);
}
public void setSex( String value, boolean format) {
if (format) {
tv_time_pk.setText(value);
} else {
try {
long xd = Long.parseLong(value);
if (xd > 9999 || xd < -9999) {
tv_time_pk.setText(String.format("%.2fw", xd / 10000.0f));
// mBinding.tvValue.setText(String.valueOf(xd));
} else {
tv_time_pk.setText(value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private boolean showSexIcon = false;
private String sex;
public boolean isMale() {
return "1".equals(sex);
}
public boolean isFemale() {
return "2".equals(sex);
}
public void setShowSexIcon(boolean show) {
showSexIcon = show;
}
public void checkSex() {
if (isOn()) {
mIvSex.setVisibility(VISIBLE);
if (!TextUtils.isEmpty(sex)) {
if (UserInfo.MALE.equals(sex)) {
mIvSex.setBackgroundResource(R.drawable.room_xq_wheat_male_mask);
if (mTvNo != null) mTvNo.setBackgroundResource(R.mipmap.ic_room_xq_wno_male);
} else {
mIvSex.setBackgroundResource(R.drawable.room_xq_wheat_female_mask);
if (mTvNo != null) mTvNo.setBackgroundResource(R.mipmap.ic_room_xq_wno_female);
}
} else {
mIvSex.setVisibility(GONE);
if (mTvNo != null) mTvNo.setBackgroundResource(getOriginNoImage());
}
} else {
mIvSex.setVisibility(GONE);
if (mTvNo != null) mTvNo.setBackgroundResource(getOriginNoImage());
}
}
/**
* 是否显示老板标识
*/
public void setIsBossShow(String is_boss_pit) {
showBoss = "1".equals(is_boss_pit);
}
/**
* 开启计时
*/
public void setTime(int time) {
if (time == 0) {
mTvTime.setText("");
mTvTime.setVisibility(INVISIBLE);
} else {
mTvTime.setText(String.format("%s'%s", time / 60, time % 60));
mTvTime.setVisibility(VISIBLE);
}
}
public void hideMaoziIcon() {
View maozi = findViewById(R.id.iv_maozi);
if (maozi != null) maozi.setVisibility(GONE);
}
@Override
public void onRemoteSoundLevelUpdate(String userId, int soundLevel) {
}
@Override
public void onLocalSoundLevelUpdate(int volume) {
if (volume==0){
mIvRipple.stopAnimation();
} else {
if (pitBean.getUser_id().equals(SpUtil.getUserId()) && closePhone) {
mIvRipple.stopAnimation();
}else {
mIvRipple.post(() -> {
if (!mIvRipple.isAnimating()) {
mIvRipple.startAnimation();
}
mIvRipple.setVisibility(VISIBLE);
});
}
}
}
@Override
public void userJoined(int userId, int elapsd) {
}
@Override
public void userOffline(int userId, int reason) {
}
}

View File

@@ -0,0 +1,91 @@
package com.xscm.moduleutil.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.widget.TextView;
/**
* ProjectName: CustomViewApp
* Package: com.yde.libview
* Description: 扫光TextView
* Author: 姚闻达
* CreateDate: 2021/3/11
* UpdateUser: 更新者
* UpdateDate: 2021/3/11 10:58
* UpdateRemark: 更新说明
* Version: 1.0
*/
public class ScanTextView extends TextView {
//渲染器
private LinearGradient mLinearGradient;
//渲染范围
private Matrix mGradientMatrix;
//渲染的起始位置
private int mViewWidth = 0;
//渲染的终止距离
private int mTranslate = 0;
//是否启动动画
private boolean mAnimating = true;
//多少毫秒刷新一次
private int speed = 50;
private int lightColor;
private Paint mPaint;
public ScanTextView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = getPaint();
mGradientMatrix = new Matrix();
}
@SuppressLint("DrawAllocation")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mViewWidth = getMeasuredWidth();
// mLinearGradient = new LinearGradient(0, 0, mViewWidth, 0, new int[] { Color.GRAY, Color.WHITE, Color.GRAY }, null, Shader.TileMode.CLAMP);
if (mAnimating) {
mLinearGradient = new LinearGradient(0, 0, mViewWidth, 0, new int[]{getCurrentTextColor(), lightColor, getCurrentTextColor()}, null, Shader.TileMode.CLAMP);
} else {
mLinearGradient = new LinearGradient(0, 0, mViewWidth, 0, new int[]{getCurrentTextColor(), getCurrentTextColor(), getCurrentTextColor()}, null, Shader.TileMode.CLAMP);
}
mPaint.setShader(mLinearGradient);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mAnimating && mGradientMatrix != null) {
mTranslate += mViewWidth / 10;//每次移动屏幕的1/10宽
if (mTranslate > 2 * mViewWidth) {
mTranslate = -mViewWidth;
}
mGradientMatrix.setTranslate(mTranslate, 0);
mLinearGradient.setLocalMatrix(mGradientMatrix);//在指定矩阵上渲染
postInvalidateDelayed(speed);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
public void setSpeed(int speed) {
this.speed = speed;
}
public void setLightColor(int lightColor) {
this.lightColor = lightColor;
}
public void setScan(boolean canScan) {
this.mAnimating = canScan;
}
}

View File

@@ -0,0 +1,60 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import androidx.viewpager.widget.ViewPager;
/**
* Created by Administrator on 2017/5/17.
*/
public class ScrollViewPager extends ViewPager {
private boolean scroll = true;
public ScrollViewPager(Context context) {
super(context);
}
public ScrollViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setScroll(boolean scroll) {
this.scroll = scroll;
}
@Override
public void scrollTo(int x, int y) {
super.scrollTo(x, y);
}
@Override
public boolean onTouchEvent(MotionEvent arg0) {
/*return false;//super.onTouchEvent(arg0);*/
if (scroll)
return false;
else
return super.onTouchEvent(arg0);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent arg0) {
if (scroll)
return false;
else
return super.onInterceptTouchEvent(arg0);
}
@Override
public void setCurrentItem(int item, boolean smoothScroll) {
super.setCurrentItem(item, smoothScroll);
}
@Override
public void setCurrentItem(int item) {
super.setCurrentItem(item);
}
}

View File

@@ -0,0 +1,40 @@
package com.xscm.moduleutil.widget;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import android.opengl.GLES20;
import android.util.Log;
public class ShaderUtils {
private static final String TAG = "ShaderUtils";
// 创建并编译着色器
public static int loadShader(int type, String shaderCode) {
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
int[] compiled = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
Log.e(TAG, "Could NOT compile shader: " + GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
return 0;
}
return shader;
}
// 创建 FloatBuffer
public static FloatBuffer createFloatBuffer(float[] array) {
ByteBuffer bb = ByteBuffer.allocateDirect(array.length * 4);
bb.order(ByteOrder.nativeOrder());
FloatBuffer buffer = bb.asFloatBuffer();
buffer.put(array);
buffer.position(0);
return buffer;
}
}

View File

@@ -0,0 +1,76 @@
package com.xscm.moduleutil.widget;
import android.os.CountDownTimer;
public class SilentCountDownTimer {
private CountDownTimer countDownTimer;
private long remainingMillis; // 剩余毫秒数
private OnCountDownFinishListener listener;
public interface OnCountDownFinishListener {
void onFinish();
}
public void setOnCountDownFinishListener(OnCountDownFinishListener listener) {
this.listener = listener;
}
/**
* 启动或更新倒计时
*
* @param targetTimestamp 目标时间戳(单位:秒)
*/
public void start(long targetTimestamp) {
// 将目标时间戳(秒)转为毫秒,并计算剩余时间
long currentTimeMillis = System.currentTimeMillis();
long targetMillis = targetTimestamp * 1000L;
remainingMillis = targetMillis - currentTimeMillis;
if (remainingMillis <= 0) {
// 时间已过,直接结束
finishCountDown();
return;
}
// 释放旧的倒计时
if (countDownTimer != null) {
countDownTimer.cancel();
}
// 创建新倒计时
countDownTimer = new CountDownTimer(remainingMillis, 1000) {
@Override
public void onTick(long millisUntilFinished) {
remainingMillis = millisUntilFinished;
// 可选:输出日志或做一些后台处理
// Log.d("SilentCountDown", "剩余时间:" + millisUntilFinished / 1000 + " 秒");
}
@Override
public void onFinish() {
finishCountDown();
}
};
countDownTimer.start();
}
private void finishCountDown() {
if (listener != null) {
listener.onFinish(); // 回调完成
}
release(); // 释放资源
}
/**
* 销毁倒计时并释放资源
*/
public void release() {
if (countDownTimer != null) {
countDownTimer.cancel();
countDownTimer = null;
}
listener = null;
}
}

View File

@@ -0,0 +1,640 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.text.InputFilter;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.InflateException;
import androidx.appcompat.widget.AppCompatEditText;
import androidx.core.content.ContextCompat;
import com.xscm.moduleutil.R;
public class SplitEditText extends AppCompatEditText {
//密码显示模式:隐藏密码,显示圆形
public static final int CONTENT_SHOW_MODE_PASSWORD = 1;
//密码显示模式:显示密码
public static final int CONTENT_SHOW_MODE_TEXT = 2;
//输入框相连的样式
public static final int INPUT_BOX_STYLE_CONNECT = 1;
//单个的输入框样式
public static final int INPUT_BOX_STYLE_SINGLE = 2;
//下划线输入框样式
public static final int INPUT_BOX_STYLE_UNDERLINE = 3;
//画笔
private RectF mRectFConnect;
private RectF mRectFSingleBox;
private Paint mPaintDivisionLine;
private Paint mPaintContent;
private Paint mPaintBorder;
private Paint mPaintUnderline;
//边框大小
private Float mBorderSize;
//边框颜色
private int mBorderColor;
//圆角大小
private float mCornerSize;
//分割线大小
private float mDivisionLineSize;
//分割线颜色
private int mDivisionColor;
//圆形密码的半径大小
private float mCircleRadius;
//密码框长度
private int mContentNumber;
//密码显示模式
private int mContentShowMode;
//单框和下划线输入样式下,每个输入框的间距
private float mSpaceSize;
//输入框样式
private int mInputBoxStyle;
//字体大小
private float mTextSize;
//字体颜色
private int mTextColor;
//每个输入框是否是正方形标识
private boolean mInputBoxSquare;
private OnInputListener inputListener;
private Paint mPaintCursor;
private CursorRunnable cursorRunnable;
private int mCursorColor;//光标颜色
private float mCursorWidth;//光标宽度
private int mCursorHeight;//光标高度
private int mCursorDuration;//光标闪烁时长
private int mUnderlineFocusColor;//下划线输入样式下,输入框获取焦点时下划线颜色
private int mUnderlineNormalColor;//下划线输入样式下,下划线颜色
public SplitEditText(Context context) {
this(context, null);
}
//这里没有写成默认的EditText属性样式android.R.attr.editTextStyle,这样会存在EditText默认的样式
public SplitEditText(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/*public SplitEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
initAttrs(context, attrs);
}*/
public SplitEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context, attrs);
}
private void initAttrs(Context c, AttributeSet attrs) {
TypedArray array = c.obtainStyledAttributes(attrs, R.styleable.SplitEditText);
mBorderSize = array.getDimension(R.styleable.SplitEditText_borderSize, dp2px(1f));
mBorderColor = array.getColor(R.styleable.SplitEditText_borderColor, Color.BLACK);
mCornerSize = array.getDimension(R.styleable.SplitEditText_corner_size, 0f);
mDivisionLineSize = array.getDimension(R.styleable.SplitEditText_divisionLineSize, dp2px(1f));
mDivisionColor = array.getColor(R.styleable.SplitEditText_divisionLineColor, Color.BLACK);
mCircleRadius = array.getDimension(R.styleable.SplitEditText_circleRadius, dp2px(5f));
mContentNumber = array.getInt(R.styleable.SplitEditText_contentNumber, 6);
mContentShowMode = array.getInteger(R.styleable.SplitEditText_contentShowMode, CONTENT_SHOW_MODE_PASSWORD);
mInputBoxStyle = array.getInteger(R.styleable.SplitEditText_inputBoxStyle, INPUT_BOX_STYLE_CONNECT);
mSpaceSize = array.getDimension(R.styleable.SplitEditText_spaceSize, dp2px(10f));
mTextSize = array.getDimension(R.styleable.SplitEditText_android_textSize, sp2px(16f));
mTextColor = array.getColor(R.styleable.SplitEditText_android_textColor, Color.BLACK);
mInputBoxSquare = array.getBoolean(R.styleable.SplitEditText_inputBoxSquare, true);
mCursorColor = array.getColor(R.styleable.SplitEditText_cursorColor, ContextCompat.getColor(c,R.color.colorAccent));
mCursorDuration = array.getInt(R.styleable.SplitEditText_cursorDuration, 500);
mCursorWidth = array.getDimension(R.styleable.SplitEditText_cursorWidth, dp2px(2f));
mCursorHeight = (int) array.getDimension(R.styleable.SplitEditText_cursorHeight, 0);
mUnderlineNormalColor = array.getInt(R.styleable.SplitEditText_underlineNormalColor, Color.BLACK);
mUnderlineFocusColor = array.getInt(R.styleable.SplitEditText_underlineFocusColor, 0);
array.recycle();
init();
}
private void init() {
mPaintBorder = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintBorder.setStyle(Paint.Style.FILL_AND_STROKE);
mPaintBorder.setStrokeWidth(mBorderSize);
mPaintBorder.setColor(mBorderColor);
mPaintDivisionLine = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintDivisionLine.setStyle(Paint.Style.STROKE);
mPaintDivisionLine.setStrokeWidth(mDivisionLineSize);
mPaintDivisionLine.setColor(mDivisionColor);
mPaintContent = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintContent.setTextSize(mTextSize);
mPaintCursor = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintCursor.setStrokeWidth(mCursorWidth);
mPaintCursor.setColor(mCursorColor);
mPaintUnderline = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintUnderline.setStrokeWidth(mBorderSize);
mPaintUnderline.setColor(mUnderlineNormalColor);
//避免onDraw里面重复创建RectF对象,先初始化RectF对象,在绘制时调用set()方法
//单个输入框样式的RectF
mRectFSingleBox = new RectF();
//绘制Connect样式的矩形框
mRectFConnect = new RectF();
//设置单行输入
setSingleLine();
//若构造方法中没有写成android.R.attr.editTextStyle的属性,应该需要设置该属性,EditText默认是获取焦点的
setFocusableInTouchMode(true);
//取消默认的光标
//这里默认不设置该属性,不然长按粘贴有问题(一开始长按不能粘贴,输入内容就可以长按粘贴)
//setCursorVisible(false);
//设置透明光标,若是直接不显示光标的话,长按粘贴会没效果
/*try {
Field f = TextView.class.getDeclaredField("mCursorDrawableRes");
f.setAccessible(true);
f.set(this, 0);
} catch (Exception e) {
e.printStackTrace();
}*/
//设置光标的TextSelectHandle
//这里判断版本,10.0以及以上直接通过方法调用,以下通过反射设置
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// setTextSelectHandle(android.R.color.transparent);
// } else {
// //通过反射改变光标TextSelectHandle的样式
// try {
// Field f = TextView.class.getDeclaredField("mTextSelectHandleRes");
// f.setAccessible(true);
// f.set(this, android.R.color.transparent);
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
//设置InputFilter,设置输入的最大字符长度为设置的长度
setFilters(new InputFilter[]{new InputFilter.LengthFilter(mContentNumber)});
}
@Override
protected void onAttachedToWindow() {
// L.e("SplitEditText","onAttachedToWindow------->");
super.onAttachedToWindow();
if(hasFocus()){
startCursor();
}
}
private void startCursor(){
if(cursorRunnable==null){
cursorRunnable = new CursorRunnable();
}
postDelayed(cursorRunnable, mCursorDuration);
}
private void stopCursor(){
if(cursorRunnable!=null){
removeCallbacks(cursorRunnable);
}
}
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
if(focused){
startCursor();
}else{
stopCursor();
}
invalidate();
}
@Override
protected void onDetachedFromWindow() {
// L.e("SplitEditText","onDetachedFromWindow------->");
stopCursor();
super.onDetachedFromWindow();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mInputBoxSquare) {
int width = MeasureSpec.getSize(widthMeasureSpec);
//计算view高度,使view高度和每个item的宽度相等,确保每个item是一个正方形
float itemWidth = getContentItemWidthOnMeasure(width);
switch (mInputBoxStyle) {
case INPUT_BOX_STYLE_UNDERLINE:
setMeasuredDimension(width, (int) (itemWidth + mBorderSize));
break;
case INPUT_BOX_STYLE_SINGLE:
case INPUT_BOX_STYLE_CONNECT:
default:
setMeasuredDimension(width, (int) (itemWidth + mBorderSize * 2));
break;
}
}
}
@Override
protected void onDraw(Canvas canvas) {
//绘制输入框
switch (mInputBoxStyle) {
case INPUT_BOX_STYLE_SINGLE:
drawSingleStyle(canvas);
break;
case INPUT_BOX_STYLE_UNDERLINE:
drawUnderlineStyle(canvas);
break;
case INPUT_BOX_STYLE_CONNECT:
default:
drawConnectStyle(canvas);
break;
}
//绘制输入框内容
drawContent(canvas);
//绘制光标
if(hasFocus()){
drawCursor(canvas);
}
}
/**
* 根据输入内容显示模式,绘制内容是圆心还是明文的text
*/
private void drawContent(Canvas canvas) {
int cy = getHeight() / 2;
String password = getText().toString().trim();
if (mContentShowMode == CONTENT_SHOW_MODE_PASSWORD) {
mPaintContent.setColor(Color.BLACK);
for (int i = 0; i < password.length(); i++) {
float startX = getDrawContentStartX(i);
canvas.drawCircle(startX, cy, mCircleRadius, mPaintContent);
}
} else {
mPaintContent.setColor(mTextColor);
//计算baseline
float baselineText = getTextBaseline(mPaintContent, cy);
for (int i = 0; i < password.length(); i++) {
float startX = getDrawContentStartX(i);
//计算文字宽度
String text = String.valueOf(password.charAt(i));
float textWidth = mPaintContent.measureText(text);
//绘制文字x应该还需要减去文字宽度的一半
canvas.drawText(text, startX - textWidth / 2, baselineText, mPaintContent);
}
}
}
/**
* 绘制光标
* 光标只有一个,所以不需要根据循环来绘制,只需绘制第N个就行
* 即:
* 当输入内容长度为0,光标在第0个位置
* 当输入内容长度为1,光标应在第1个位置
* ...
* 所以光标所在位置为输入内容的长度
* 这里光标的长度默认就是 height/2
*/
private void drawCursor(Canvas canvas) {
if (mCursorHeight > getHeight()) {
throw new InflateException("cursor height must smaller than view height");
}
String content = getText().toString().trim();
float startX = getDrawContentStartX(content.length());
//如果设置得有光标高度,那么startY = (高度-光标高度)/2+边框宽度
if (mCursorHeight == 0) {
mCursorHeight = getHeight() / 2;
}
int sy = (getHeight() - mCursorHeight) / 2;
float startY = sy + mBorderSize;
float stopY = getHeight() - sy - mBorderSize;
//此时的绘制光标竖直线,startX = stopX
canvas.drawLine(startX, startY, startX, stopY, mPaintCursor);
}
/**
* 、
* 计算三种输入框样式下绘制圆和文字的x坐标
*
* @param index 循环里面的下标 i
*/
private float getDrawContentStartX(int index) {
switch (mInputBoxStyle) {
case INPUT_BOX_STYLE_SINGLE:
//单个输入框样式下的startX
//即 itemWidth/2 + i*itemWidth + i*每一个间距宽度 + 前面所有的左右边框
// i = 0,左侧1个边框
// i = 1,左侧3个边框(一个完整的item的左右边框+ 一个左侧边框)
// i = ..., (2*i+1)*mBorderSize
return getContentItemWidth() / 2 + index * getContentItemWidth() + index * mSpaceSize + (2 * index + 1) * mBorderSize;
case INPUT_BOX_STYLE_UNDERLINE:
//下划线输入框样式下的startX
//即 itemWidth/2 + i*itemWidth + i*每一个间距宽度
return getContentItemWidth() / 2 + index * mSpaceSize + index * getContentItemWidth();
case INPUT_BOX_STYLE_CONNECT:
//矩形输入框样式下的startX
//即 itemWidth/2 + i*itemWidth + i*分割线宽度 + 左侧的一个边框宽度
default:
return getContentItemWidth() / 2 + index * getContentItemWidth() + index * mDivisionLineSize + mBorderSize;
}
}
/**
* 绘制下划线输入框样式
* 线条起点startX:每个字符所占宽度itemWidth + 每个字符item之间的间距mSpaceSize
* 线条终点stopX:stopX与startX之间就是一个itemWidth的宽度
*/
private void drawUnderlineStyle(Canvas canvas) {
String content = getText().toString().trim();
for (int i = 0; i < mContentNumber; i++) {
//计算绘制下划线的startX
float startX = i * getContentItemWidth() + i * mSpaceSize;
//stopX
float stopX = getContentItemWidth() + startX;
//对于下划线这种样式,startY = stopY
float startY = getHeight() - mBorderSize / 2;
//这里判断是否设置有输入框获取焦点时,下划线的颜色
if (mUnderlineFocusColor != 0) {
if (content.length() >= i) {
mPaintUnderline.setColor(mUnderlineFocusColor);
} else {
mPaintUnderline.setColor(mUnderlineNormalColor);
}
}
canvas.drawLine(startX, startY, stopX, startY, mPaintUnderline);
}
}
/**
* 绘制单框输入模式
* 这里计算left、right时有点饶,
* 理解、计算时最好根据图形、参照drawConnectStyle()绘制带边框的矩形
* left:绘制带边框的矩形等图形时,去掉边框的一半即 + mBorderSize / 2,同时加上每个字符item的间距 + i*mSpaceSize
* 另外,每个字符item的宽度 + i*itemWidth
* 最后,绘制时都是以整个view的宽度计算,绘制第N个时,都应该加上以前的边框宽度
* 即第一个i = 0 ,边框的宽度为0
* 第二个i = 1,边框的宽度 2*mBorderSize,左右两个的边框宽度
* 以此...最后应该 + i*2*mBorderSize
* 同理
* right去掉边框的一半 -mBorderSize/2,还应该加上前面一个item的宽度+(i+1)*itemWidth
* 同样,绘制时都是以整个view的宽度计算,绘制后面的,都应该加上前面的所有宽度
* 即 间距:+i*mSpaceSize
* 边框:(注意是计算整个view)
* 第一个i = 0,2个边框2*mBorderSize
* 第二个i = 1,4个边框,即 (1+1)*2*mBorderSize
* 所以算上边框 +(i+1)*2*mBorderSize
*/
private void drawSingleStyle(Canvas canvas) {
for (int i = 0; i < mContentNumber; i++) {
mRectFSingleBox.setEmpty();
float left = i * getContentItemWidth() + i * mSpaceSize + i * mBorderSize * 2 + mBorderSize / 2;
float right = i * mSpaceSize + (i + 1) * getContentItemWidth() + (i + 1) * 2 * mBorderSize - mBorderSize / 2;
//为避免在onDraw里面创建RectF对象,这里使用rectF.set()方法
mRectFSingleBox.set(left, mBorderSize / 2, right, getHeight() - mBorderSize / 2);
canvas.drawRoundRect(mRectFSingleBox, mCornerSize, mCornerSize, mPaintBorder);
}
}
/**
* 绘制矩形外框
* 在绘制圆角矩形的时候,应该减掉边框的宽度
* 不然会有所偏差
* <p>
* 在绘制矩形以及其它图形的时候,矩形(图形)的边界是边框的中心,不是边框的边界
* 所以在绘制带边框的图形的时候应该减去边框宽度的一半
* https://blog.csdn.net/a512337862/article/details/74161988
*/
private void drawConnectStyle(Canvas canvas) {
//每次重新绘制时,先将rectF重置下
mRectFConnect.setEmpty();
//需要减去边框的一半
mRectFConnect.set(
mBorderSize / 2,
mBorderSize / 2,
getWidth() - mBorderSize / 2,
getHeight() - mBorderSize / 2
);
canvas.drawRoundRect(mRectFConnect, mCornerSize, mCornerSize, mPaintBorder);
//绘制分割线
drawDivisionLine(canvas);
}
/**
* 分割线条数为内容框数目-1
* 这里startX应该要加上左侧边框的宽度
* 应该还需要加上分割线的一半
* 至于startY和stopY不是 mBorderSize/2 而是 mBorderSize
* startX是计算整个宽度的,需要算上左侧的边框宽度,所以不是+mBorderSize/2 而是+mBorderSize
* startY和stopY分割线是紧挨着边框内部的,所以应该是mBorderSize,而不是mBorderSize/2
*/
private void drawDivisionLine(Canvas canvas) {
float stopY = getHeight() - mBorderSize;
for (int i = 0; i < mContentNumber - 1; i++) {
//对于分割线条,startX = stopX
float startX = (i + 1) * getContentItemWidth() + i * mDivisionLineSize + mBorderSize + mDivisionLineSize / 2;
canvas.drawLine(startX, mBorderSize, startX, stopY, mPaintDivisionLine);
}
}
/**
* 计算3种样式下,相应每个字符item的宽度
*/
private float getContentItemWidth() {
//计算每个密码字符所占的宽度,每种输入框样式下,每个字符item所占宽度也不一样
float tempWidth;
switch (mInputBoxStyle) {
case INPUT_BOX_STYLE_SINGLE:
//单个输入框样式:宽度-间距宽度(字符数-1)*每个间距宽度-每个输入框的左右边框宽度
tempWidth = getWidth() - (mContentNumber - 1) * mSpaceSize - 2 * mContentNumber * mBorderSize;
break;
case INPUT_BOX_STYLE_UNDERLINE:
//下划线样式:宽度-间距宽度(字符数-1)*每个间距宽度
tempWidth = getWidth() - (mContentNumber - 1) * mSpaceSize;
break;
case INPUT_BOX_STYLE_CONNECT:
//矩形输入框样式:宽度-左右两边框宽度-分割线宽度(字符数-1)*每个分割线宽度
default:
tempWidth = getWidth() - (mDivisionLineSize * (mContentNumber - 1)) - 2 * mBorderSize;
break;
}
return tempWidth / mContentNumber;
}
/**
* 根据view的测量宽度,计算每个item的宽度
*
* @param measureWidth view的measure
* @return onMeasure时的每个item宽度
*/
private float getContentItemWidthOnMeasure(int measureWidth) {
//计算每个密码字符所占的宽度,每种输入框样式下,每个字符item所占宽度也不一样
float tempWidth;
switch (mInputBoxStyle) {
case INPUT_BOX_STYLE_SINGLE:
//单个输入框样式:宽度-间距宽度(字符数-1)*每个间距宽度-每个输入框的左右边框宽度
tempWidth = measureWidth - (mContentNumber - 1) * mSpaceSize - 2 * mContentNumber * mBorderSize;
break;
case INPUT_BOX_STYLE_UNDERLINE:
//下划线样式:宽度-间距宽度(字符数-1)*每个间距宽度
tempWidth = measureWidth - (mContentNumber - 1) * mSpaceSize;
break;
case INPUT_BOX_STYLE_CONNECT:
//矩形输入框样式:宽度-左右两边框宽度-分割线宽度(字符数-1)*每个分割线宽度
default:
tempWidth = measureWidth - (mDivisionLineSize * (mContentNumber - 1)) - 2 * mBorderSize;
break;
}
return tempWidth / mContentNumber;
}
/**
* 计算绘制文本的基线
*
* @param paint 绘制文字的画笔
* @param halfHeight 高度的一半
*/
private float getTextBaseline(Paint paint, float halfHeight) {
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
float dy = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
return halfHeight + dy;
}
/**
* 设置密码是否可见
*/
public void setContentShowMode(int mode) {
if (mode != CONTENT_SHOW_MODE_PASSWORD && mode != CONTENT_SHOW_MODE_TEXT) {
throw new IllegalArgumentException(
"the value of the parameter must be one of" +
"{1:EDIT_SHOW_MODE_PASSWORD} or " +
"{2:EDIT_SHOW_MODE_TEXT}"
);
}
mContentShowMode = mode;
invalidate();
}
/**
* 获取密码显示模式
*/
public int getContentShowMode() {
return mContentShowMode;
}
/**
* 设置输入框样式
*/
public void setInputBoxStyle(int inputBoxStyle) {
if (inputBoxStyle != INPUT_BOX_STYLE_CONNECT
&& inputBoxStyle != INPUT_BOX_STYLE_SINGLE
&& inputBoxStyle != INPUT_BOX_STYLE_UNDERLINE
) {
throw new IllegalArgumentException(
"the value of the parameter must be one of" +
"{1:INPUT_BOX_STYLE_CONNECT}, " +
"{2:INPUT_BOX_STYLE_SINGLE} or " +
"{3:INPUT_BOX_STYLE_UNDERLINE}"
);
}
mInputBoxStyle = inputBoxStyle;
// 这里没有调用invalidate因为会存在问题
// invalidate会重绘,但是不会去重新测量,当输入框样式切换的之后,item的宽度其实是有变化的,所以此时需要重新测量
// requestLayout,调用onMeasure和onLayout,不一定会调用onDraw,当view的l,t,r,b发生改变时会调用onDraw
requestLayout();
//invalidate();
}
public void setOnInputListener(OnInputListener listener) {
this.inputListener = listener;
}
/**
* 通过复写onTextChanged来实现对输入的监听
* 如果在onDraw里面监听text的输入长度来实现,会重复的调用该方法,就不妥当
*/
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
String content = text.toString().trim();
if (inputListener != null) {
inputListener.onInputChanged(content);
if (content.length() == mContentNumber) {
inputListener.onInputFinished(content);
}
}
}
/**
* 获取输入框样式
*/
public int getInputBoxStyle() {
return mInputBoxStyle;
}
private float dp2px(float dpValue) {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dpValue,
Resources.getSystem().getDisplayMetrics()
);
}
private float sp2px(float spValue) {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
spValue,
Resources.getSystem().getDisplayMetrics()
);
}
/**
* 光标Runnable
* 通过Runnable每500ms执行重绘,每次runnable通过改变画笔的alpha值来使光标产生闪烁的效果
*/
private class CursorRunnable implements Runnable {
@Override
public void run() {
//获取光标画笔的alpha值
int alpha = mPaintCursor.getAlpha();
//设置光标画笔的alpha值
mPaintCursor.setAlpha(alpha == 0 ? 255 : 0);
invalidate();
postDelayed(this, mCursorDuration);
// L.e("SplitEditText","CursorRunnable-----run-->");
}
}
/**
* 输入的监听抽象类
* 没定义接口的原因是可以在抽象类里面定义空实现的方法,可以让用户根据需求选择性的复写某些方法
*/
public interface OnInputListener {
/**
* 输入完成的抽象方法
*
* @param content 输入内容
*/
public abstract void onInputFinished(String content);
/**
* 输入的内容
*/
public void onInputChanged(String text) ;
}
}

View File

@@ -0,0 +1,65 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.TypedValue;
public class Utils {
public static <T> T checkNotNull(T object, String message) {
if (object == null) {
throw new NullPointerException(message);
}
return object;
}
public static int pixelOfScaled(Context c, int sp) {
Resources r = c.getResources();
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, r.getDisplayMetrics());
}
public static int pixelOfDp(Context c, int dp) {
Resources r = c.getResources();
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics());
}
private static final Object sLock = new Object();
private static TypedValue sTempValue;
/**
* Returns a drawable object associated with a particular resource ID.
* <p>
* Starting in {@link Build.VERSION_CODES#LOLLIPOP}, the
* returned drawable will be styled for the specified Context's theme.
*
* @param id The desired resource identifier, as generated by the aapt tool.
* This integer encodes the package, type, and resource entry.
* The value 0 is an invalid identifier.
* @return Drawable An object that can be used to draw this resource.
*/
public static Drawable getDrawable(Context context, int id) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return context.getDrawable(id);
} else if (Build.VERSION.SDK_INT >= 16) {
//noinspection deprecation
return context.getResources().getDrawable(id);
} else {
// Prior to JELLY_BEAN, Resources.getDrawable() would not correctly
// retrieve the final configuration density when the resource ID
// is a reference another Drawable resource. As a workaround, try
// to resolve the drawable reference manually.
final int resolvedId;
synchronized (sLock) {
if (sTempValue == null) {
sTempValue = new TypedValue();
}
context.getResources().getValue(id, sTempValue, true);
resolvedId = sTempValue.resourceId;
}
//noinspection deprecation
return context.getResources().getDrawable(resolvedId);
}
}
}

View File

@@ -0,0 +1,36 @@
package com.xscm.moduleutil.widget;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import androidx.annotation.NonNull;
import com.petterp.floatingx.listener.control.IFxControl;
import com.tencent.liteav.base.Log;
public class ViewUtils {
public static void waitUntilViewReady(@NonNull IFxControl control, @NonNull OnViewCreatedListener listener) {
final int[] retryCount = {0};
final Handler handler = new Handler(Looper.getMainLooper());
final Runnable checkRunnable = new Runnable() {
@Override
public void run() {
View view = control.getView();
if (view != null) {
listener.onViewCreated(view);
} else if (retryCount[0]++ < 10) { // 最多尝试 10 次
handler.postDelayed(this, 300); // 每隔 300ms 再试一次
} else {
Log.e("ViewUtils", "等待 View 创建超时");
}
}
};
handler.post(checkRunnable);
}
public interface OnViewCreatedListener {
void onViewCreated(@NonNull View view);
}
}

View File

@@ -0,0 +1,143 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.graphics.Color;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.databinding.DataBindingUtil;
import com.blankj.utilcode.util.ConvertUtils;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.room.RoomUserJoinModel;
import com.xscm.moduleutil.databinding.RoomViewWelcomeAnimViewBinding;
import com.xscm.moduleutil.utils.ImageUtils;
import com.xscm.moduleutil.utils.SpUtil;
import java.util.LinkedList;
import java.util.Queue;
/**
*@author qx
*@data 2025/6/9
*@description: 进场公屏
*/
public class WelcomeAnimView extends ConstraintLayout implements Animation.AnimationListener {
private Animation mAnimation;
public boolean animEnded = true;
private RoomViewWelcomeAnimViewBinding mBinding;
public WelcomeAnimView(Context context) {
this(context, null);
}
public WelcomeAnimView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mBinding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.room_view_welcome_anim_view, this, true);
setVisibility(GONE);
mAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.room_anim_set_welcome);
mAnimation.setFillAfter(true);
mAnimation.setAnimationListener(this);
}
public void showAnim() {
if (SpUtil.getOpenEffect() == 0) {
return;
}
if (queue == null || queue.size() == 0) {
return;
}
if (animEnded) {
RoomUserJoinModel animBean = queue.poll();
if (animBean != null) {
// mIvRole.setRole(animBean.getRole());
// mIvNew.setVisibility(animBean.getUser_is_new() == 1 ? VISIBLE : GONE);
mBinding.tvName.setText(animBean.getNickname());
// mIvNobility.setVisibility(TextUtils.isEmpty(animBean.getNobility_icon()) ? GONE : VISIBLE);
// ImageUtils.loadImageView(animBean.getNobility_icon(), mIvNobility);
mBinding.ivRank.setVisibility(TextUtils.isEmpty(animBean.getRank_icon()) ? GONE : VISIBLE);
ImageUtils.loadImageView(animBean.getRank_icon(), mBinding.ivRank);
}
setVisibility(VISIBLE);
//加入房间背景 和 字体颜色
LayoutParams infoParams = (LayoutParams) mBinding.llInfo.getLayoutParams();
if (!TextUtils.isEmpty(animBean.getBackground())) {
mBinding.ivUserInto.setVisibility(VISIBLE);
mBinding.llInfo.setBackgroundResource(R.drawable.bg_r10_transparent);
infoParams.leftMargin = ConvertUtils.dp2px(74);
ImageUtils.loadImageView(animBean.getBackground(), mBinding.ivUserInto);
} else {
infoParams.leftMargin = 0;
mBinding.llInfo.setBackgroundResource(R.drawable.room_bg_welcome_anim);
mBinding.ivUserInto.setVisibility(INVISIBLE);
}
mBinding.llInfo.setLayoutParams(infoParams);
if (!TextUtils.isEmpty(animBean.getColor())) {
try {
mBinding.tvEndTxt.setTextColor(Color.parseColor(animBean.getColor()));
mBinding.tvName.setTextColor(Color.parseColor(animBean.getColor()));
} catch (Exception e) {
mBinding.tvEndTxt.setTextColor(Color.WHITE);
mBinding.tvName.setTextColor(Color.WHITE);
e.printStackTrace();
}
} else {
mBinding.tvEndTxt.setTextColor(Color.WHITE);
mBinding.tvName.setTextColor(Color.WHITE);
}
this.startAnimation(mAnimation);
}
}
public void addAnim(RoomUserJoinModel bean) {
if (SpUtil.getOpenEffect() == 0) {
return;
}
if (queue.size() == 0) {
queue.add(bean);
showAnim();
} else {
queue.add(bean);
}
}
private Queue<RoomUserJoinModel> queue = new LinkedList<>();
@Override
public void onAnimationStart(Animation animation) {
animEnded = false;
}
@Override
public void onAnimationEnd(Animation animation) {
animEnded = true;
showAnim();
}
@Override
public void onAnimationRepeat(Animation animation) {
}
/**
* 关闭特效
*/
public void closeEffect() {
//清空队列
queue.clear();
//关闭动画
if (mAnimation != null && !animEnded) {
animEnded = true;
mAnimation.cancel();
}
}
}

View File

@@ -0,0 +1,52 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.RelativeLayout;
import androidx.databinding.DataBindingUtil;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.databinding.RoomViewWheatCharmBinding;
public class WheatCharmView extends RelativeLayout {
private RoomViewWheatCharmBinding mBinding;
public WheatCharmView(Context context) {
this(context, null);
}
public WheatCharmView(Context context, AttributeSet attrs) {
super(context, attrs);
mBinding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.room_view_wheat_charm, this, true);
setClipChildren(false);
setClipToPadding(false);
}
public void setSex(String sex, String userId, String value, boolean format) {
if (format) {
mBinding.tvValue.setText(value);
} else {
try {
long xd = Long.parseLong(value);
if (xd > 9999 || xd < -9999) {
mBinding.tvValue.setText(String.format("%.2fw", xd / 10000.0f));
// mBinding.tvValue.setText(String.valueOf(xd));
} else {
mBinding.tvValue.setText(value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void setBg(int bg){
mBinding.bg.setBackgroundResource(bg);
}
}

View File

@@ -0,0 +1,75 @@
package com.xscm.moduleutil.widget;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.app.Activity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.ImageView;
import com.blankj.utilcode.util.ScreenUtils;
import com.xscm.moduleutil.utils.GiftAnimatorUtil;
import com.xscm.moduleutil.utils.ImageUtils;
public class WheatGiftAnim {
public static void addGift(View targetView, String imageUrl) {
final int[] location = new int[2];
targetView.getLocationOnScreen(location);
ImageView imageView = new ImageView(targetView.getContext());
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(targetView.getWidth(), targetView.getHeight());
imageView.setLayoutParams(params);
ImageUtils.loadImageView(imageUrl, imageView);
attach2Activity((Activity) targetView.getContext(), imageView);
ObjectAnimator tada = GiftAnimatorUtil.tada(imageView);
PropertyValuesHolder objectAnimatorY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, ScreenUtils.getScreenHeight(), location[1]);
PropertyValuesHolder objectAnimatorX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X, ScreenUtils.getScreenWidth() / 2f, location[0]);
ObjectAnimator valueAnimator = ObjectAnimator.ofPropertyValuesHolder(imageView, objectAnimatorY, objectAnimatorX).
setDuration(1200);
AnimatorSet set = new AnimatorSet();
set.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
removeFromActivity(imageView);
}
@Override
public void onAnimationCancel(Animator animator) {
removeFromActivity(imageView);
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
set.playSequentially(valueAnimator, tada);
set.start();
}
/**
* 将创建的ExplosionField添加到Activity上
*/
private static void attach2Activity(Activity activity, View view) {
ViewGroup rootView = activity.findViewById(Window.ID_ANDROID_CONTENT);
rootView.addView(view);
}
/**
* 将ExplosionField从Activity上移除
*
* @param imageView
*/
private static void removeFromActivity(ImageView imageView) {
ViewGroup rootView = ((Activity) imageView.getContext()).findViewById(Window.ID_ANDROID_CONTENT);
rootView.removeView(imageView);
}
}

View File

@@ -0,0 +1,431 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.UserOnlineStatusBean;
import com.xscm.moduleutil.bean.room.RoomPitBean;
import java.util.List;
public class WheatLayoutManager {
private final Context context;
private final ViewGroup container;
private List<RoomPitBean> pitList;
private boolean isSingleMode = false;
private int currentSinglePit = -1;
private RoomDefaultWheatView singleWheatView;
private final int[] pitIndexMap = {9, 10, 1, 2, 3, 4, 5, 6, 7, 8};
public interface OnWheatClickListener {
void onWheatClick(RoomDefaultWheatView view, int pitNumber);
void onMakeWheatClick(RoomDefaultWheatView view, int pitNumber);
}
private @Nullable OnWheatClickListener wheatClickListener;
public WheatLayoutManager(Context context, ViewGroup container) {
this.context = context;
this.container = container;
}
public void setWheatData(List<RoomPitBean> pitList) {
this.pitList = pitList;
restoreMultiWheat();
}
public void setWheatDataPk(List<RoomPitBean> pitList, int layoutType) {
this.pitList = pitList;
restoreMultiWheatPk(layoutType);
}
public void setOnWheatClickListener(@Nullable OnWheatClickListener listener) {
this.wheatClickListener = listener;
}
public void showSingleWheat(int pitNumber) {
if (isSingleMode && this.currentSinglePit == pitNumber) return;
container.removeAllViews();
if (pitNumber < 1 || pitNumber > 10 || pitList == null || pitList.size() < 10)
return;
RoomPitBean bean = pitList.get(pitNumber - 1);
singleWheatView = new RoomDefaultWheatView(context);
singleWheatView.pitNumber = String.valueOf(pitNumber);
singleWheatView.setData(bean);
// 默认设置为 MATCH_PARENT也可以自定义
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
params.setMargins(20, 20, 20, 20);
singleWheatView.setLayoutParams(params);
// 添加点击事件
singleWheatView.setOnClickListener(v -> {
if (wheatClickListener != null) {
wheatClickListener.onWheatClick(singleWheatView, pitNumber);
}
restoreMultiWheat();
});
container.addView(singleWheatView);
isSingleMode = true;
currentSinglePit = pitNumber;
}
public void restoreMultiWheat() {
container.removeAllViews();
int screenWidth = getScreenWidth();
int itemWidth = screenWidth / 4; // 每个控件宽度为屏幕宽度的 1/4
LinearLayout row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL);
for (int i = 0; i < 10; i++) {
int pitNumber = pitIndexMap[i];
RoomDefaultWheatView wheatView = new RoomDefaultWheatView(context);
wheatView.pitNumber = String.valueOf(pitNumber);
wheatView.setData(pitList.get(pitNumber - 1));
LinearLayout.LayoutParams params;
if (i == 0) {
int fixedHeightInDp = 110; // 固定高度为 100dp
int fixedHeightInPx = dpToPx(fixedHeightInDp); // 调用已有的 dpToPx 方法
// 第一个控件:左边距 86dp右边距 100dp
params = new LinearLayout.LayoutParams(itemWidth, fixedHeightInPx);
params.rightMargin = dpToPx(50);
} else if (i == 1) {
int fixedHeightInDp = 110; // 固定高度为 100dp
int fixedHeightInPx = dpToPx(fixedHeightInDp); // 调用已有的 dpToPx 方法
// 第二个控件:右边距 86dp
params = new LinearLayout.LayoutParams(itemWidth, fixedHeightInPx);
} else {
int fixedHeightInDp = 90; // 固定高度为 100dp
int fixedHeightInPx = dpToPx(fixedHeightInDp); // 调用已有的 dpToPx 方法
params = new LinearLayout.LayoutParams(itemWidth - 30, fixedHeightInPx + 30);
// 其他控件保持原有逻辑
// if (i > 1 && (i - 2) % 4 != 0) {
// params.leftMargin = 12;
// params.rightMargin = 12;
// }
params.setMargins(0, 0, 0, 0); // 不设右边距,由 row padding 控制
}
wheatView.setLayoutParams(params);
wheatView.setOnClickListener(v -> {
if (wheatClickListener != null) {
wheatClickListener.onWheatClick(wheatView, Integer.parseInt(wheatView.pitNumber));
}
// showSingleWheat(Integer.parseInt(wheatView.pitNumber));
});
row.addView(wheatView);
// 第一行添加两个后换行
if (i == 1) {
container.addView(row);
row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL);
} else if (i > 1 && (i - 2) % 4 == 3) {
container.addView(row);
row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL);
}
}
// 添加最后一行可能存在的剩余 view
if (row.getChildCount() > 0) {
container.addView(row);
}
isSingleMode = false;
currentSinglePit = -1;
}
// public void restoreMultiWheatPk(int layoutType, int width) {
// container.removeAllViews();
//
// int screenWidth = getScreenWidth();
// int itemWidth = screenWidth / 8; // 每个控件宽度为屏幕宽度的 1/4
//
// LinearLayout row = new LinearLayout(context);
// row.setOrientation(LinearLayout.HORIZONTAL);
//
// for (int i = 0; i < 10; i++) {
// int pitNumber = pitIndexMap[i];
// RoomDefaultWheatView wheatView = new RoomDefaultWheatView(context);
// wheatView.pitNumber = String.valueOf(pitNumber);
// wheatView.setData(pitList.get(pitNumber - 1));
//
// LinearLayout.LayoutParams params;
//
// if (i == 0) {
// int fixedHeightInDp = 110; // 固定高度为 100dp
// int fixedHeightInPx =context.getResources().getDimensionPixelSize(R.dimen.dp_80); // 调用已有的 dpToPx 方法
// // 第一个控件:左边距 86dp右边距 100dp
// params = new LinearLayout.LayoutParams(itemWidth, fixedHeightInPx);
// params.rightMargin = context.getResources().getDimensionPixelSize(R.dimen.dp_50);
// } else if (i == 1) {
// int fixedHeightInDp = 110; // 固定高度为 100dp
// int fixedHeightInPx = context.getResources().getDimensionPixelSize(R.dimen.dp_80); // 调用已有的 dpToPx 方法
// // 第二个控件:右边距 86dp
// params = new LinearLayout.LayoutParams(itemWidth, fixedHeightInPx);
// } else {
// int fixedHeightInDp = 90; // 固定高度为 100dp
// int fixedHeightInPx = context.getResources().getDimensionPixelSize(R.dimen.dp_60); // 调用已有的 dpToPx 方法
// params = new LinearLayout.LayoutParams(itemWidth -10, fixedHeightInPx + 30);
// // 其他控件保持原有逻辑
//
//
//// if (i > 1 && (i - 2) % 4 != 0) {
//// params.leftMargin = 12;
//// params.rightMargin = 12;
//// }
// params.setMargins(0, 0, 0, 0); // 不设右边距,由 row padding 控制
// }
//
// wheatView.setLayoutParams(params);
// wheatView.setOnClickListener(v -> {
// if (wheatClickListener != null) {
// wheatClickListener.onWheatClick(wheatView, Integer.parseInt(wheatView.pitNumber));
// }
//// showSingleWheat(Integer.parseInt(wheatView.pitNumber));
// });
//
// row.addView(wheatView);
//
// // 第一行添加两个后换行
// if (i == 1) {
// container.addView(row);
// row = new LinearLayout(context);
// row.setOrientation(LinearLayout.HORIZONTAL);
// } else if (i > 1 && (i - 2) % 4 == 3) {
// container.addView(row);
// row = new LinearLayout(context);
// row.setOrientation(LinearLayout.HORIZONTAL);
// }
// }
//
//// 添加最后一行可能存在的剩余 view
// if (row.getChildCount() > 0) {
// container.addView(row);
// }
// isSingleMode = false;
// currentSinglePit = -1;
// }
public void restoreMultiWheatPk(int layoutType) {
if (layoutType == 1) {
container.removeAllViews();
}
int screenWidth = getScreenWidth();
int itemWidth = screenWidth / 8;
LinearLayout row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL);
// 根据 layoutType 调整前两个控件的顺序
int firstPitNumber, secondPitNumber;
if (layoutType == 1) {
firstPitNumber = 10; // 第一个显示 10
secondPitNumber = 9; // 第二个显示 9
} else if (layoutType == 2) {
firstPitNumber = 9; // 第一个显示 9
secondPitNumber = 10; // 第二个显示 10
} else {
firstPitNumber = 9;
secondPitNumber = 10;
}
// 添加第一个控件10 或 9
addWheatViewItem(row, firstPitNumber, itemWidth*2, layoutType);
// 添加第二个控件9 或 10
addWheatViewItem(row, secondPitNumber, itemWidth*2, layoutType);
container.addView(row);
row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL);
// 添加其余 8 个控件1~8
for (int i = 2; i < 10; i++) {
int pitNumber = pitIndexMap[i];
addWheatViewItem(row, pitNumber, itemWidth, layoutType);
if (i > 1 && (i - 2) % 4 == 3) {
container.addView(row);
row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL);
}
}
if (row.getChildCount() > 0) {
container.addView(row);
}
isSingleMode = false;
currentSinglePit = -1;
}
// 抽取公共方法:添加单个控件
private void addWheatViewItem(LinearLayout row, int pitNumber, int itemWidth, int layoutType) {
RoomDefaultWheatView wheatView = new RoomDefaultWheatView(context);
wheatView.pitNumber = String.valueOf(pitNumber);
wheatView.setData(pitList.get(pitNumber - 1));
LinearLayout.LayoutParams params;
if (pitNumber == 9 || pitNumber == 10) {
int fixedHeightInPx = context.getResources().getDimensionPixelSize(R.dimen.dp_90);
if (pitNumber == 9) {
params = new LinearLayout.LayoutParams(itemWidth-40, fixedHeightInPx);
if (layoutType == 1) {
// 9号在右边右边距10dp
params.rightMargin = context.getResources().getDimensionPixelSize(R.dimen.dp_1);
params.setMargins(20, -30, -20, 0);
} else if (layoutType == 2) {
// 9号在左边左边距10dp
params.leftMargin = context.getResources().getDimensionPixelSize(R.dimen.dp_1);
params.setMargins(-30, -20, 0, 0);
}
} else {
params = new LinearLayout.LayoutParams(itemWidth - 80, fixedHeightInPx);
if (layoutType == 1) {
// 10号在左边左边距15dp
// params.leftMargin = context.getResources().getDimensionPixelSize(R.dimen.dp_5);
params.setMargins(-30, 10, 0, 0);
} else if (layoutType == 2) {
// 10号在右边右边距15dp
// params.rightMargin = context.getResources().getDimensionPixelSize(R.dimen.dp_5);
params.setMargins(0, 10, -30, 0);
}
}
} else {
int fixedHeightInPx = context.getResources().getDimensionPixelSize(R.dimen.dp_60);
params = new LinearLayout.LayoutParams(itemWidth + 15, fixedHeightInPx + 20);
params.setMargins(-20, -20, -20, 0);
}
wheatView.setLayoutParams(params);
wheatView.setOnClickListener(v -> {
if (wheatClickListener != null) {
if (layoutType==1) {
wheatClickListener.onWheatClick(wheatView, Integer.parseInt(wheatView.pitNumber));
}else {
wheatClickListener.onMakeWheatClick(wheatView, Integer.parseInt(wheatView.pitNumber));
}
}
});
row.addView(wheatView);
}
private RoomDefaultWheatView createWheatView(int pitNumber) {
RoomDefaultWheatView wheatView = new RoomDefaultWheatView(context);
wheatView.pitNumber = String.valueOf(pitNumber);
wheatView.setData(pitList.get(pitNumber - 1));
return wheatView;
}
private RoomMakeWheatView createRoomMakeWheatView(int pitNumber) {
RoomMakeWheatView wheatView = new RoomMakeWheatView(context);
wheatView.pitNumber = String.valueOf(pitNumber);
wheatView.setData(pitList.get(pitNumber - 1));
return wheatView;
}
private int dpToPx(int dp) {
return Math.round(dp * context.getResources().getDisplayMetrics().density);
}
private int getScreenWidth() {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return metrics.widthPixels;
}
/**
* 更新指定 pitNumber 的麦位信息(用于局部刷新)
*/
public void updateSingleWheat(RoomPitBean pitBean, int pitNumber) {
if (pitList == null || pitList.isEmpty() || pitNumber < 1 || pitNumber > 10) return;
// 如果是单个展示模式且不是当前麦位,不处理
if (isSingleMode && this.currentSinglePit != pitNumber) return;
RoomDefaultWheatView wheatView = findWheatViewByPitNumber(pitNumber);
if (wheatView != null) {
// RoomPitBean bean = pitList.get(pitNumber - 1);
RoomPitBean bean = pitBean;
wheatView.setData(bean); // 刷新数据
}
}
@Nullable
private RoomDefaultWheatView findWheatViewByPitNumber(int pitNumber) {
for (int i = 0; i < container.getChildCount(); i++) {
View row = container.getChildAt(i);
if (row instanceof LinearLayout) {
LinearLayout linearRow = (LinearLayout) row;
for (int j = 0; j < linearRow.getChildCount(); j++) {
View child = linearRow.getChildAt(j);
if (child instanceof RoomDefaultWheatView) {
RoomDefaultWheatView view = (RoomDefaultWheatView) child;
if (Integer.parseInt(view.pitNumber) == pitNumber) {
return view;
}
}
}
} else if (row instanceof RoomDefaultWheatView) {
RoomDefaultWheatView view = (RoomDefaultWheatView) row;
if (Integer.parseInt(view.pitNumber) == pitNumber) {
return view;
}
}
}
return null;
}
/**
* 批量刷新多个麦位状态
*/
public void refreshWheatData(List<RoomPitBean> newPitList, List<Integer> changedPits) {
this.pitList = newPitList;
for (int pitNumber : changedPits) {
// updateSingleWheat(pitNumber);
}
}
public void updateSingleOnlineWheat(UserOnlineStatusBean bean) {
if (pitList == null || pitList.isEmpty()) return;
for (RoomPitBean pitBean : pitList) {
int pitNumber = Integer.parseInt(pitBean.getPit_number());
RoomDefaultWheatView wheatView = findWheatViewByPitNumber(pitNumber);
wheatView.setOnlineStatus(bean); // 刷新数据
}
}
}

View File

@@ -0,0 +1,439 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.UserOnlineStatusBean;
import com.xscm.moduleutil.bean.room.RoomPitBean;
import java.util.List;
/**
* @Author lxj$
* @Time 2025-8-6 17:29:04$ $
* @Description 二卡八显示布局$
*/
public class WheatLayoutSingManager {
private final Context context;
private final ViewGroup container;
private List<RoomPitBean> pitList;
private boolean isSingleMode = false;
private int currentSinglePit = -1;
private RoomSingSongWheatView singleWheatView;
private final int[] pitIndexMap = {9, 10, 1, 2, 3, 4, 5, 6, 7, 8};
public interface OnWheatClickListener {
void onWheatClick(RoomSingSongWheatView view, int pitNumber);
void onMakeWheatClick(RoomSingSongWheatView view, int pitNumber);
}
private @Nullable OnWheatClickListener wheatClickListener;
public WheatLayoutSingManager(Context context, ViewGroup container) {
this.context = context;
this.container = container;
}
public void setWheatData(List<RoomPitBean> pitList) {
this.pitList = pitList;
restoreMultiWheat();
}
public void setWheatDataPk(List<RoomPitBean> pitList, int layoutType) {
this.pitList = pitList;
restoreMultiWheatPk(layoutType);
}
public void setOnWheatClickListener(@Nullable OnWheatClickListener listener) {
this.wheatClickListener = listener;
}
public void showSingleWheat(int pitNumber) {
if (isSingleMode && this.currentSinglePit == pitNumber) return;
container.removeAllViews();
if (pitNumber < 1 || pitNumber > 10 || pitList == null || pitList.size() < 10)
return;
RoomPitBean bean = pitList.get(pitNumber - 1);
singleWheatView = new RoomSingSongWheatView(context);
singleWheatView.pitNumber = String.valueOf(pitNumber);
singleWheatView.setData(bean);
// 默认设置为 MATCH_PARENT也可以自定义
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
params.setMargins(20, 20, 20, 20);
singleWheatView.setLayoutParams(params);
// 添加点击事件
singleWheatView.setOnClickListener(v -> {
if (wheatClickListener != null) {
wheatClickListener.onWheatClick(singleWheatView, pitNumber);
}
restoreMultiWheat();
});
container.addView(singleWheatView);
isSingleMode = true;
currentSinglePit = pitNumber;
}
public void restoreMultiWheat() {
container.removeAllViews();
int screenWidth = getScreenWidth();
int itemWidth = screenWidth / 4; // 每个控件宽度为屏幕宽度的 1/4
LinearLayout row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL);
for (int i = 0; i < 10; i++) {
int pitNumber = pitIndexMap[i];
RoomSingSongWheatView wheatView = new RoomSingSongWheatView(context);
wheatView.pitNumber = String.valueOf(pitNumber);
wheatView.setData(pitList.get(pitNumber - 1));
LinearLayout.LayoutParams params;
if (i == 0) {
int fixedHeightInDp = 110; // 固定高度为 100dp
int fixedHeightInPx = dpToPx(fixedHeightInDp); // 调用已有的 dpToPx 方法
// 第一个控件:左边距 86dp右边距 100dp
params = new LinearLayout.LayoutParams(itemWidth, fixedHeightInPx);
params.rightMargin = dpToPx(50);
} else if (i == 1) {
int fixedHeightInDp = 110; // 固定高度为 100dp
int fixedHeightInPx = dpToPx(fixedHeightInDp); // 调用已有的 dpToPx 方法
// 第二个控件:右边距 86dp
params = new LinearLayout.LayoutParams(itemWidth, fixedHeightInPx);
} else {
int fixedHeightInDp = 90; // 固定高度为 100dp
int fixedHeightInPx = dpToPx(fixedHeightInDp); // 调用已有的 dpToPx 方法
params = new LinearLayout.LayoutParams(itemWidth - 30, fixedHeightInPx + 30);
// 其他控件保持原有逻辑
// if (i > 1 && (i - 2) % 4 != 0) {
// params.leftMargin = 12;
// params.rightMargin = 12;
// }
params.setMargins(0, 0, 0, 0); // 不设右边距,由 row padding 控制
}
wheatView.setLayoutParams(params);
wheatView.setOnClickListener(v -> {
if (wheatClickListener != null) {
wheatClickListener.onWheatClick(wheatView, Integer.parseInt(wheatView.pitNumber));
}
// showSingleWheat(Integer.parseInt(wheatView.pitNumber));
});
row.addView(wheatView);
// 第一行添加两个后换行
if (i == 1) {
container.addView(row);
row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL);
} else if (i > 1 && (i - 2) % 4 == 3) {
container.addView(row);
row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL);
}
}
// 添加最后一行可能存在的剩余 view
if (row.getChildCount() > 0) {
container.addView(row);
}
isSingleMode = false;
currentSinglePit = -1;
}
// public void restoreMultiWheatPk(int layoutType, int width) {
// container.removeAllViews();
//
// int screenWidth = getScreenWidth();
// int itemWidth = screenWidth / 8; // 每个控件宽度为屏幕宽度的 1/4
//
// LinearLayout row = new LinearLayout(context);
// row.setOrientation(LinearLayout.HORIZONTAL);
//
// for (int i = 0; i < 10; i++) {
// int pitNumber = pitIndexMap[i];
// RoomDefaultWheatView wheatView = new RoomDefaultWheatView(context);
// wheatView.pitNumber = String.valueOf(pitNumber);
// wheatView.setData(pitList.get(pitNumber - 1));
//
// LinearLayout.LayoutParams params;
//
// if (i == 0) {
// int fixedHeightInDp = 110; // 固定高度为 100dp
// int fixedHeightInPx =context.getResources().getDimensionPixelSize(R.dimen.dp_80); // 调用已有的 dpToPx 方法
// // 第一个控件:左边距 86dp右边距 100dp
// params = new LinearLayout.LayoutParams(itemWidth, fixedHeightInPx);
// params.rightMargin = context.getResources().getDimensionPixelSize(R.dimen.dp_50);
// } else if (i == 1) {
// int fixedHeightInDp = 110; // 固定高度为 100dp
// int fixedHeightInPx = context.getResources().getDimensionPixelSize(R.dimen.dp_80); // 调用已有的 dpToPx 方法
// // 第二个控件:右边距 86dp
// params = new LinearLayout.LayoutParams(itemWidth, fixedHeightInPx);
// } else {
// int fixedHeightInDp = 90; // 固定高度为 100dp
// int fixedHeightInPx = context.getResources().getDimensionPixelSize(R.dimen.dp_60); // 调用已有的 dpToPx 方法
// params = new LinearLayout.LayoutParams(itemWidth -10, fixedHeightInPx + 30);
// // 其他控件保持原有逻辑
//
//
//// if (i > 1 && (i - 2) % 4 != 0) {
//// params.leftMargin = 12;
//// params.rightMargin = 12;
//// }
// params.setMargins(0, 0, 0, 0); // 不设右边距,由 row padding 控制
// }
//
// wheatView.setLayoutParams(params);
// wheatView.setOnClickListener(v -> {
// if (wheatClickListener != null) {
// wheatClickListener.onWheatClick(wheatView, Integer.parseInt(wheatView.pitNumber));
// }
//// showSingleWheat(Integer.parseInt(wheatView.pitNumber));
// });
//
// row.addView(wheatView);
//
// // 第一行添加两个后换行
// if (i == 1) {
// container.addView(row);
// row = new LinearLayout(context);
// row.setOrientation(LinearLayout.HORIZONTAL);
// } else if (i > 1 && (i - 2) % 4 == 3) {
// container.addView(row);
// row = new LinearLayout(context);
// row.setOrientation(LinearLayout.HORIZONTAL);
// }
// }
//
//// 添加最后一行可能存在的剩余 view
// if (row.getChildCount() > 0) {
// container.addView(row);
// }
// isSingleMode = false;
// currentSinglePit = -1;
// }
public void restoreMultiWheatPk(int layoutType) {
if (layoutType == 1) {
container.removeAllViews();
}
int screenWidth = getScreenWidth();
int itemWidth = screenWidth / 8;
LinearLayout row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL);
// 根据 layoutType 调整前两个控件的顺序
int firstPitNumber, secondPitNumber;
if (layoutType == 1) {
firstPitNumber = 10; // 第一个显示 10
secondPitNumber = 9; // 第二个显示 9
} else if (layoutType == 2) {
firstPitNumber = 9; // 第一个显示 9
secondPitNumber = 10; // 第二个显示 10
} else {
firstPitNumber = 9;
secondPitNumber = 10;
}
// 添加第一个控件10 或 9
addWheatViewItem(row, firstPitNumber, itemWidth * 2, layoutType);
// 添加第二个控件9 或 10
addWheatViewItem(row, secondPitNumber, itemWidth * 2, layoutType);
container.addView(row);
row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL);
// 添加其余 8 个控件1~8
for (int i = 2; i < 10; i++) {
int pitNumber = pitIndexMap[i];
addWheatViewItem(row, pitNumber, itemWidth, layoutType);
if (i > 1 && (i - 2) % 4 == 3) {
container.addView(row);
row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL);
}
}
if (row.getChildCount() > 0) {
container.addView(row);
}
isSingleMode = false;
currentSinglePit = -1;
}
// 抽取公共方法:添加单个控件
private void addWheatViewItem(LinearLayout row, int pitNumber, int itemWidth, int layoutType) {
RoomSingSongWheatView wheatView = new RoomSingSongWheatView(context);
wheatView.pitNumber = String.valueOf(pitNumber);
wheatView.setData(pitList.get(pitNumber - 1));
LinearLayout.LayoutParams params;
if (pitNumber == 9 || pitNumber == 10) {
int fixedHeightInPx = context.getResources().getDimensionPixelSize(R.dimen.dp_90);
if (pitNumber == 9) {
params = new LinearLayout.LayoutParams(itemWidth - 40, fixedHeightInPx);
if (layoutType == 1) {
// 9号在右边右边距10dp
params.rightMargin = context.getResources().getDimensionPixelSize(R.dimen.dp_1);
params.setMargins(20, -30, -20, 0);
} else if (layoutType == 2) {
// 9号在左边左边距10dp
params.leftMargin = context.getResources().getDimensionPixelSize(R.dimen.dp_1);
params.setMargins(-30, -20, 0, 0);
}
} else {
params = new LinearLayout.LayoutParams(itemWidth - 80, fixedHeightInPx);
if (layoutType == 1) {
// 10号在左边左边距15dp
// params.leftMargin = context.getResources().getDimensionPixelSize(R.dimen.dp_5);
params.setMargins(-30, 10, 0, 0);
} else if (layoutType == 2) {
// 10号在右边右边距15dp
// params.rightMargin = context.getResources().getDimensionPixelSize(R.dimen.dp_5);
params.setMargins(0, 10, -30, 0);
}
}
} else {
int fixedHeightInPx = context.getResources().getDimensionPixelSize(R.dimen.dp_60);
params = new LinearLayout.LayoutParams(itemWidth + 15, fixedHeightInPx + 20);
params.setMargins(-20, -20, -20, 0);
}
wheatView.setLayoutParams(params);
wheatView.setOnClickListener(v -> {
if (wheatClickListener != null) {
if (layoutType == 1) {
wheatClickListener.onWheatClick(wheatView, Integer.parseInt(wheatView.pitNumber));
} else {
wheatClickListener.onMakeWheatClick(wheatView, Integer.parseInt(wheatView.pitNumber));
}
}
});
row.addView(wheatView);
}
private RoomSingSongWheatView createWheatView(int pitNumber) {
RoomSingSongWheatView wheatView = new RoomSingSongWheatView(context);
wheatView.pitNumber = String.valueOf(pitNumber);
wheatView.setData(pitList.get(pitNumber - 1));
return wheatView;
}
private RoomMakeWheatView createRoomMakeWheatView(int pitNumber) {
RoomMakeWheatView wheatView = new RoomMakeWheatView(context);
wheatView.pitNumber = String.valueOf(pitNumber);
wheatView.setData(pitList.get(pitNumber - 1));
return wheatView;
}
private int dpToPx(int dp) {
return Math.round(dp * context.getResources().getDisplayMetrics().density);
}
private int getScreenWidth() {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return metrics.widthPixels;
}
/**
* 更新指定 pitNumber 的麦位信息(用于局部刷新)
*/
public void updateSingleWheat(RoomPitBean pitBean, int pitNumber) {
if (pitList == null || pitList.isEmpty() || pitNumber < 1 || pitNumber > 10) return;
// 如果是单个展示模式且不是当前麦位,不处理
if (isSingleMode && this.currentSinglePit != pitNumber) return;
RoomSingSongWheatView wheatView = findWheatViewByPitNumber(pitNumber);
if (wheatView != null) {
// RoomPitBean bean = pitList.get(pitNumber - 1);
RoomPitBean bean = pitBean;
wheatView.setData(bean); // 刷新数据
}
}
@Nullable
private RoomSingSongWheatView findWheatViewByPitNumber(int pitNumber) {
for (int i = 0; i < container.getChildCount(); i++) {
View row = container.getChildAt(i);
if (row instanceof LinearLayout) {
LinearLayout linearRow = (LinearLayout) row;
for (int j = 0; j < linearRow.getChildCount(); j++) {
View child = linearRow.getChildAt(j);
if (child instanceof RoomSingSongWheatView) {
RoomSingSongWheatView view = (RoomSingSongWheatView) child;
if (Integer.parseInt(view.pitNumber) == pitNumber) {
return view;
}
}
}
} else if (row instanceof RoomSingSongWheatView) {
RoomSingSongWheatView view = (RoomSingSongWheatView) row;
if (Integer.parseInt(view.pitNumber) == pitNumber) {
return view;
}
}
}
return null;
}
/**
* 批量刷新多个麦位状态
*/
public void refreshWheatData(List<RoomPitBean> newPitList, List<Integer> changedPits) {
this.pitList = newPitList;
for (int pitNumber : changedPits) {
// updateSingleWheat(pitNumber);
}
}
public void updateSingleOnlineWheat(UserOnlineStatusBean bean) {
if (pitList == null || pitList.isEmpty()) return;
for (RoomPitBean pitBean : pitList) {
int pitNumber = Integer.parseInt(pitBean.getPit_number());
RoomSingSongWheatView wheatView = findWheatViewByPitNumber(pitNumber);
wheatView.setOnlineStatus(bean); // 刷新数据
}
}
}

View File

@@ -0,0 +1,97 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import android.widget.NumberPicker;
import java.util.Calendar;
public class WheelDatePicker extends LinearLayout {
private NumberPicker yearPicker, monthPicker, dayPicker;
private int maxDay = 31;
public WheelDatePicker(Context context) {
this(context, null);
}
public WheelDatePicker(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
setOrientation(HORIZONTAL);
yearPicker = new NumberPicker(context);
monthPicker = new NumberPicker(context);
dayPicker = new NumberPicker(context);
LayoutParams params = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1);
yearPicker.setLayoutParams(params);
monthPicker.setLayoutParams(params);
dayPicker.setLayoutParams(params);
addView(yearPicker);
addView(monthPicker);
addView(dayPicker);
// 初始化年份范围
int currentYear = Calendar.getInstance().get(Calendar.YEAR);
yearPicker.setMinValue(currentYear - 50);
yearPicker.setMaxValue(currentYear + 50);
// 月份从 1 到 12
monthPicker.setMinValue(1);
monthPicker.setMaxValue(12);
monthPicker.setOnValueChangedListener((picker, oldVal, newVal) -> updateDays());
// 默认天数范围
dayPicker.setMinValue(1);
dayPicker.setMaxValue(maxDay);
}
private void updateDays() {
int year = yearPicker.getValue();
int month = monthPicker.getValue();
int daysInMonth;
if (month == 4 || month == 6 || month == 9 || month == 11) {
daysInMonth = 30;
} else if (month == 2) {
daysInMonth = isLeapYear(year) ? 29 : 28;
} else {
daysInMonth = 31;
}
int currentDay = dayPicker.getValue();
dayPicker.setMaxValue(daysInMonth);
if (currentDay > daysInMonth) {
dayPicker.setValue(daysInMonth);
}
}
private boolean isLeapYear(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
public void init(int year, int month, int day) {
yearPicker.setValue(year);
monthPicker.setValue(month + 1); // Calendar 是 0-based
dayPicker.setValue(day);
updateDays();
}
public int getYear() {
return yearPicker.getValue();
}
public int getMonth() {
return monthPicker.getValue() - 1; // 转回 Calendar 的 0-based
}
public int getDay() {
return dayPicker.getValue();
}
}

View File

@@ -0,0 +1,67 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import android.widget.NumberPicker;
public class WheelTimePicker extends LinearLayout {
private NumberPicker hourPicker, minutePicker, secondPicker;
public WheelTimePicker(Context context) {
this(context, null);
}
public WheelTimePicker(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
setOrientation(HORIZONTAL);
hourPicker = new NumberPicker(context);
minutePicker = new NumberPicker(context);
secondPicker= new NumberPicker(context);
LayoutParams params = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1);
hourPicker.setLayoutParams(params);
minutePicker.setLayoutParams(params);
secondPicker.setLayoutParams(params);
addView(hourPicker);
addView(minutePicker);
addView(secondPicker);
// 设置小时范围
hourPicker.setMinValue(0);
hourPicker.setMaxValue(23);
// 设置分钟范围
minutePicker.setMinValue(0);
minutePicker.setMaxValue(59);
// 秒范围
secondPicker.setMinValue(0);
secondPicker.setMaxValue(59);
}
public void init(int hour, int minute, int second) {
hourPicker.setValue(hour);
minutePicker.setValue(minute);
secondPicker.setValue(second);
}
public int getHour() {
return hourPicker.getValue();
}
public int getMinute() {
return minutePicker.getValue();
}
public int getSecond() {
return secondPicker.getValue();
}
}

View File

@@ -0,0 +1,86 @@
package com.xscm.moduleutil.widget.animator;
import android.animation.ValueAnimator;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.view.View;
/**
* 说明:爆炸动画类,让离子移动和控制离子透明度
* 作者Jian
* 时间2017/12/26.
*/
class ExplosionAnimator extends ValueAnimator {
private static final int DEFAULT_DURATION = 3000;
private ParticleModel[][] mParticles;
private Paint mPaint;
private View mContainer;
public ExplosionAnimator(View view, Bitmap bitmap, Rect bound) {
setFloatValues(0.0f, 1.0f);
setDuration(DEFAULT_DURATION);
mPaint = new Paint();
mContainer = view;
mParticles = generateParticles(bitmap, bound);
}
// 生成粒子,按行按列生成全部粒子
private ParticleModel[][] generateParticles(Bitmap bitmap, Rect bound) {
int w = bound.width();
int h = bound.height();
// 横向粒子的个数
int horizontalCount = w / ParticleModel.PART_WH;
// 竖向粒子的个数
int verticalCount = h / ParticleModel.PART_WH;
// 粒子宽度
int bitmapPartWidth = horizontalCount == 0 ? 0 : bitmap.getWidth() / horizontalCount;
// 粒子高度
int bitmapPartHeight = verticalCount == 0 ? 0 : bitmap.getHeight() / verticalCount;
ParticleModel[][] particles = new ParticleModel[verticalCount][horizontalCount];
for (int row = 0; row < verticalCount; row++) {
for (int column = 0; column < horizontalCount; column++) {
//取得当前粒子所在位置的颜色
int color = bitmap.getPixel(column * bitmapPartWidth, row * bitmapPartHeight);
Point point = new Point(column, row);
particles[row][column] = new ParticleModel(color, bound, point);
}
}
return particles;
}
// 由view调用在View上绘制全部的粒子
void draw(Canvas canvas) {
// 动画结束时停止
if (!isStarted()) {
return;
}
// 遍历粒子并绘制在View上
for (ParticleModel[] particle : mParticles) {
for (ParticleModel p : particle) {
p.advance((Float) getAnimatedValue());
mPaint.setColor(p.color);
// 错误的设置方式只是这样设置,透明色会显示为黑色
// mPaint.setAlpha((int) (255 * p.alpha));
// 正确的设置方式,这样透明颜色就不是黑色了
mPaint.setAlpha((int) (Color.alpha(p.color) * p.alpha));
canvas.drawCircle(p.cx, p.cy, p.radius, mPaint);
}
}
mContainer.invalidate();
}
@Override
public void start() {
super.start();
mContainer.invalidate();
}
}

View File

@@ -0,0 +1,118 @@
package com.xscm.moduleutil.widget.animator;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import com.blankj.utilcode.util.BarUtils;
public class ExplosionField extends View {
private static final String TAG = "ExplosionField";
private static final Canvas mCanvas = new Canvas();
private ExplosionAnimator animator;
public ExplosionField(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
animator.draw(canvas);
}
/**
* 执行爆破破碎动画
*/
public void explode(final View view, final AnimatorListenerAdapter listener) {
Rect rect = new Rect();
view.getGlobalVisibleRect(rect); //得到view相对于整个屏幕的坐标
rect.offset(0, -BarUtils.getStatusBarHeight()); //去掉状态栏高度
if (rect.width() == 0 || rect.height() == 0) {
return;
}
animator = new ExplosionAnimator(this, createBitmapFromView(view), rect);
// 接口回调
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
if (listener != null) listener.onAnimationStart(animation);
// 延时添加到界面上
attach2Activity((Activity) getContext());
// 让被爆炸的View消失爆炸的View是新创建的View原View本身不会发生任何变化
view.animate().alpha(0f).setDuration(150).start();
}
@Override
public void onAnimationEnd(Animator animation) {
if (listener != null) listener.onAnimationEnd(animation);
// 从界面中移除
removeFromActivity((Activity) getContext());
// 让被爆炸的View显示爆炸的View是新创建的View原View本身不会发生任何变化
view.animate().alpha(1f).setDuration(150).start();
}
@Override
public void onAnimationCancel(Animator animation) {
if (listener != null) listener.onAnimationCancel(animation);
}
@Override
public void onAnimationRepeat(Animator animation) {
if (listener != null) listener.onAnimationRepeat(animation);
}
});
animator.start();
}
private Bitmap createBitmapFromView(View view) {
// 为什么屏蔽以下代码段?
// 如果ImageView直接得到位图那么当它设置背景backgroud)时,不会读取到背景颜色
// if (view instanceof ImageView) {
// Drawable drawable = ((ImageView)view).getDrawable();
// if (drawable != null && drawable instanceof BitmapDrawable) {
// return ((BitmapDrawable) drawable).getBitmap();
// }
// }
//view.clearFocus(); //不同焦点状态显示的可能不同——azz:不同就不同有什么关系?)
Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
if (bitmap != null) {
synchronized (mCanvas) {
mCanvas.setBitmap(bitmap);
view.draw(mCanvas);
// 清除引用
mCanvas.setBitmap(null);
}
}
return bitmap;
}
/**
* 将创建的ExplosionField添加到Activity上
*/
private void attach2Activity(Activity activity) {
ViewGroup rootView = activity.findViewById(Window.ID_ANDROID_CONTENT);
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
rootView.addView(this, lp);
}
/**
* 将ExplosionField从Activity上移除
*/
private void removeFromActivity(Activity activity) {
ViewGroup rootView = activity.findViewById(Window.ID_ANDROID_CONTENT);
rootView.removeView(this);
}
}

View File

@@ -0,0 +1,54 @@
package com.xscm.moduleutil.widget.animator;
import android.graphics.Point;
import android.graphics.Rect;
import com.blankj.utilcode.util.ConvertUtils;
import java.util.Random;
/**
* 说明:爆破粒子,每个移动与渐变的小块
* 作者Jian
* 时间2017/12/26.
*/
class ParticleModel {
// 默认小球宽高
static final int PART_WH = ConvertUtils.dp2px(5);
// 随机数,随机出位置和大小
static Random random = new Random();
//center x of circle
float cx;
//center y of circle
float cy;
// 半径
float radius;
// 颜色
int color;
// 透明度
float alpha;
// 整体边界
Rect mBound;
ParticleModel(int color, Rect bound, Point point) {
int row = point.y; //行是高
int column = point.x; //列是宽
this.mBound = bound;
this.color = color;
this.alpha = 1f;
this.radius = PART_WH;
this.cx = bound.left + PART_WH * column;
this.cy = bound.top + PART_WH * row;
}
// 每一步动画都得重新计算出自己的状态值
void advance(float factor) {
cx = cx + factor * random.nextInt(mBound.width()) * (random.nextFloat() - 0.5f);
cy = cy + factor * random.nextInt(mBound.height() / 2);
radius = radius - factor * random.nextInt(2);
alpha = (1f - factor) * (1 + random.nextFloat());
}
}

View File

@@ -0,0 +1,31 @@
package com.xscm.moduleutil.widget.animator;
import android.view.animation.Interpolator;
/**
* ProjectName: FarmDemo-master
* Package: com.lantern.core.myapplication
* Description: 弹簧插值器
* Author: 姚闻达
* CreateDate: 2020/11/10 17:50
* UpdateUser: 更新者
* UpdateDate: 2020/11/10 17:50
* UpdateRemark: 更新说明
* Version: 1.0
*/
public class SpringInterpolator implements Interpolator {
//控制弹簧系数
private float factor;
public SpringInterpolator(float factor) {
this.factor = factor;
}
@Override
public float getInterpolation(float input) {
//factor = 0.4
// pow(2, -10 * x) * sin((x - factor / 4) * (2 * PI) / factor) + 1
return (float) -(Math.pow(2, -10 * input) * Math.sin((input - factor / 4) * (2 * Math.PI) / factor) );
}
}

View File

@@ -0,0 +1,26 @@
package com.xscm.moduleutil.widget.animator;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;
/**
* ProjectName: FarmDemo-master
* Package: com.lantern.core.myapplication
* Description: java类作用描述
* Author: 姚闻达
* CreateDate: 2020/11/10 17:51
* UpdateUser: 更新者
* UpdateDate: 2020/11/10 17:51
* UpdateRemark: 更新说明
* Version: 1.0
*/
public class TreeAnimation {
public static Animation getAnimation() {
// 创建缩放的动画对象
ScaleAnimation sa = new ScaleAnimation(1f, 1.0f, 1.0f, 1.1f, ScaleAnimation.RELATIVE_TO_SELF, 0.0f, ScaleAnimation.RELATIVE_TO_SELF, 1.0f);
// 设置动画播放的时间
sa.setDuration(1500);
sa.setInterpolator(new SpringInterpolator(0.3f));
return sa;
}
}

View File

@@ -0,0 +1,89 @@
package com.xscm.moduleutil.widget.animator;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.blankj.utilcode.util.ConvertUtils;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.utils.GiftAnimatorUtil;
import com.xscm.moduleutil.utils.ImageUtils;
import java.util.Random;
public class TreeRewardAnim extends ConstraintLayout {
public TreeRewardAnim(Context context) {
super(context);
}
public TreeRewardAnim(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TreeRewardAnim(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void doAnim(String imageUrl) {
View view = inflate(getContext(), R.layout.room_view_water_tree_gift_anim, null);
view.setScaleX(0.5f);
view.setScaleY(0.5f);
ImageView imageView = view.findViewById(R.id.image);
LayoutParams params = new LayoutParams(ConvertUtils.dp2px(77), ConvertUtils.dp2px(75));
params.startToStart = LayoutParams.PARENT_ID;
params.endToEnd = LayoutParams.PARENT_ID;
params.topToTop = LayoutParams.PARENT_ID;
params.bottomToBottom = LayoutParams.PARENT_ID;
view.setLayoutParams(params);
addView(view);
ImageUtils.loadImageView(imageUrl, imageView);
ObjectAnimator tada = GiftAnimatorUtil.tada(view);
PropertyValuesHolder objectAnimatorY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, view.getY(), view.getY() - new Random().nextInt(ConvertUtils.dp2px(100)));
int max = this.getRight(), min = this.getLeft();
int ran2 = new Random().nextInt(max - min + 1) + min;
int random = new Random().nextInt(4);
int x = new Random().nextInt(ConvertUtils.dp2px(120));
if (random > 1) {
x = -x;
}
PropertyValuesHolder objectAnimatorX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X, view.getX(), x);
ObjectAnimator valueAnimator = ObjectAnimator.ofPropertyValuesHolder(view, objectAnimatorY, objectAnimatorX).
setDuration(1000);
PropertyValuesHolder scaleAnimatorY = PropertyValuesHolder.ofFloat(View.SCALE_Y, 0.5f, 1);
PropertyValuesHolder scaleAnimatorX = PropertyValuesHolder.ofFloat(View.SCALE_X, 0.5f, 1);
ObjectAnimator scaleAnimator = ObjectAnimator.ofPropertyValuesHolder(view, scaleAnimatorY, scaleAnimatorX).
setDuration(1000);
AnimatorSet set = new AnimatorSet();
set.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
removeView(view);
}
@Override
public void onAnimationCancel(Animator animator) {
removeView(view);
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
set.playSequentially(valueAnimator, scaleAnimator, tada);
set.start();
}
}

View File

@@ -0,0 +1,75 @@
package com.xscm.moduleutil.widget.animator;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.app.Activity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.ImageView;
import com.blankj.utilcode.util.ScreenUtils;
import com.xscm.moduleutil.utils.GiftAnimatorUtil;
import com.xscm.moduleutil.utils.ImageUtils;
public class WheatGiftAnim {
public static void addGift(View targetView, String imageUrl) {
final int[] location = new int[2];
targetView.getLocationOnScreen(location);
ImageView imageView = new ImageView(targetView.getContext());
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(targetView.getWidth(), targetView.getHeight());
imageView.setLayoutParams(params);
ImageUtils.loadImageView(imageUrl, imageView);
attach2Activity((Activity) targetView.getContext(), imageView);
ObjectAnimator tada = GiftAnimatorUtil.tada(imageView);
PropertyValuesHolder objectAnimatorY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, ScreenUtils.getScreenHeight(), location[1]);
PropertyValuesHolder objectAnimatorX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X, ScreenUtils.getScreenWidth() / 2f, location[0]);
ObjectAnimator valueAnimator = ObjectAnimator.ofPropertyValuesHolder(imageView, objectAnimatorY, objectAnimatorX).
setDuration(1200);
AnimatorSet set = new AnimatorSet();
set.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
removeFromActivity(imageView);
}
@Override
public void onAnimationCancel(Animator animator) {
removeFromActivity(imageView);
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
set.playSequentially(valueAnimator, tada);
set.start();
}
/**
* 将创建的ExplosionField添加到Activity上
*/
private static void attach2Activity(Activity activity, View view) {
ViewGroup rootView = activity.findViewById(Window.ID_ANDROID_CONTENT);
rootView.addView(view);
}
/**
* 将ExplosionField从Activity上移除
*
* @param imageView
*/
private static void removeFromActivity(ImageView imageView) {
ViewGroup rootView = ((Activity) imageView.getContext()).findViewById(Window.ID_ANDROID_CONTENT);
rootView.removeView(imageView);
}
}

View File

@@ -0,0 +1,49 @@
package com.xscm.moduleutil.widget.dialog;
import android.app.Dialog;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
import androidx.databinding.ViewDataBinding;
import com.xscm.moduleutil.R;
public abstract class BaseDialog<VDM extends ViewDataBinding> extends Dialog {
protected VDM mBinding;
public BaseDialog(@NonNull Context context) {
this(context, R.style.BaseDialogStyle);
}
public BaseDialog(@NonNull Context context, int themeResId) {
super(context, themeResId);
mBinding = DataBindingUtil.bind(LayoutInflater.from(context).inflate(getLayoutId(), null, false));
if (mBinding != null) {
setContentView(mBinding.getRoot());
}
getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
initView();
initData();
}
@Override
public void dismiss() {
super.dismiss();
if (mBinding!=null){
mBinding.unbind();
}
}
public abstract int getLayoutId();
public abstract void initView();
public abstract void initData();
}

View File

@@ -0,0 +1,107 @@
package com.xscm.moduleutil.widget.dialog;
import android.os.Bundle;
import android.view.Gravity;
import android.view.Window;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.adapter.LikeListAdapter;
import com.xscm.moduleutil.base.BaseMvpDialogFragment;
import com.xscm.moduleutil.bean.CircleListBean;
import com.xscm.moduleutil.databinding.FragmentCommentDialogBinding;
import com.xscm.moduleutil.presenter.CommentContacts;
import com.xscm.moduleutil.presenter.CommentPresenter;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.listener.OnRefreshLoadMoreListener;
import java.util.List;
/**
*@author qx
*@data 2025/5/30
*@description: 点赞列表弹窗
*/
public class CommentDialogFragment extends BaseMvpDialogFragment<CommentPresenter, FragmentCommentDialogBinding> implements
CommentContacts.View {
private int page;
private LikeListAdapter likeListAdapter;
@Override
protected CommentPresenter bindPresenter() {
return new CommentPresenter(this, getActivity());
}
public static void show(String id, FragmentManager fragmentManager) {
CommentDialogFragment dialogFragment = new CommentDialogFragment();
Bundle args = new Bundle();
args.putString("id", id); // 可选:传递参数
dialogFragment.setArguments(args);
dialogFragment.show(fragmentManager, "CommentDialogFragment");
}
@Override
protected void initData() {
MvpPre.getCommentList(getArguments().getString("id"));
}
@Override
public void onStart() {
super.onStart();
Window window = getDialog().getWindow();
if (window != null) {
// 设置固定高度为 500dp
int heightInDp = 450;
int heightInPx = (int) (heightInDp * getResources().getDisplayMetrics().density);
window.setLayout(WindowManager.LayoutParams.MATCH_PARENT, heightInPx);
// 可选:设置动画样式(从底部弹出)
window.setWindowAnimations(R.style.CommonShowDialogBottom);
}
}
@Override
protected void initView() {
mBinding.srl.setOnRefreshLoadMoreListener(new OnRefreshLoadMoreListener() {
@Override
public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
page++;
MvpPre.getCommentList(getArguments().getString("id"));
}
@Override
public void onRefresh(@NonNull RefreshLayout refreshLayout) {
// EventBus.getDefault().post(new BannerRefreshEvent());
page = 1;
MvpPre.getCommentList(getArguments().getString("id"));
}
});
mBinding.rvComment.setLayoutManager(new LinearLayoutManager(getActivity()));
likeListAdapter = new LikeListAdapter();
mBinding.rvComment.setAdapter(likeListAdapter);
}
@Override
protected void initDialogStyle(Window window) {
super.initDialogStyle(window);
window.setGravity(Gravity.BOTTOM);
}
@Override
protected int getLayoutId() {
return R.layout.fragment_comment_dialog;
}
@Override
public void getCommentList(List<CircleListBean.LikeList> likeLists) {
mBinding.tvNum.setText("已有"+likeLists.size()+"人点赞");
likeListAdapter.setNewData(likeLists);
}
}

View File

@@ -0,0 +1,82 @@
package com.xscm.moduleutil.widget.dialog;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.blankj.utilcode.util.ScreenUtils;
import com.xscm.moduleutil.R;
public class CommonDialog extends BaseDialog {
TextView tvContent;
TextView textViewLeft;
TextView textViewRight;
private OnClickListener mOnClickListener;
public CommonDialog(@NonNull Context context) {
super(context);
}
@Override
public int getLayoutId() {
return R.layout.dialog_common;
}
@Override
public void initView() {
getWindow().setLayout((int) (ScreenUtils.getScreenWidth() * 0.86), ViewGroup.LayoutParams.WRAP_CONTENT);
tvContent = mBinding.getRoot().findViewById(R.id.tv_content);
textViewLeft = mBinding.getRoot().findViewById(R.id.tv_left);
textViewRight = mBinding.getRoot().findViewById(R.id.tv_right);
textViewLeft.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
mOnClickListener.onLeftClick();
}
});
textViewRight.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
mOnClickListener.onRightClick();
}
});
}
@Override
public void initData() {
}
public void setContent(String content) {
tvContent.setText(content);
}
public void setLeftText(String leftText) {
textViewLeft.setText(leftText);
}
public void setRightText(String rightText) {
textViewRight.setText(rightText);
}
public void setmOnClickListener(OnClickListener onClickListener) {
this.mOnClickListener = onClickListener;
}
public interface OnClickListener {
void onLeftClick();
void onRightClick();
}
}

View File

@@ -0,0 +1,240 @@
package com.xscm.moduleutil.widget.dialog;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.PopupWindow;
import com.xscm.moduleutil.R;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* 项目名称 qipao-android
* 包名com.qpyy.room.widget
* 创建人 黄强
* 创建时间 2020/8/6 16:55
* 描述 自定义数字键盘
*/
public class KeyboardPopupWindow extends PopupWindow {
private static final String TAG = "KeyboardPopupWindow";
private Context context;
private View anchorView;
private View parentView;
private EditText editText;
private boolean isRandomSort = false;//数字是否随机排序
private List<Integer> list = new ArrayList<>();
private int[] commonButtonIds = new int[]{R.id.bt_keyboard1, R.id.bt_keyboard2, R.id.bt_keyboard3, R.id.bt_keyboard4,
R.id.bt_keyboard5, R.id.bt_keyboard6, R.id.bt_keyboard7, R.id.bt_keyboard8, R.id.bt_keyboard9, R.id.bt_keyboard0};
private KeyboardCompleteListener mListener;
/**
* @param context
* @param anchorView
* @param
* @param
*/
public KeyboardPopupWindow(Context context, View anchorView) {
this.context = context;
this.anchorView = anchorView;
this.isRandomSort = isRandomSort;
if (context == null || anchorView == null) {
return;
}
initConfig();
initView();
Log.d(TAG, "KeyboardPopupWindow: =====================打开了自定义键盘");
}
private void initConfig() {
setOutsideTouchable(false);
setFocusable(false);
setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
forbidDefaultSoftKeyboard();
}
/**
* 禁止系统默认的软键盘弹出
*/
private void forbidDefaultSoftKeyboard() {
if (editText == null) {
return;
}
if (android.os.Build.VERSION.SDK_INT > 10) {//4.0以上,使用反射的方式禁止系统自带的软键盘弹出
try {
Class<EditText> cls = EditText.class;
Method setShowSoftInputOnFocus;
setShowSoftInputOnFocus = cls.getMethod("setShowSoftInputOnFocus", boolean.class);
setShowSoftInputOnFocus.setAccessible(true);
setShowSoftInputOnFocus.invoke(editText, false);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 刷新自定义的popupwindow是否outside可触摸反应如果是不可触摸的则显示该软键盘view
*
* @param isTouchable
*/
public void refreshKeyboardOutSideTouchable(boolean isTouchable) {
setOutsideTouchable(isTouchable);
if (!isTouchable) {
show();
} else {
dismiss();
}
}
private void initView() {
parentView = LayoutInflater.from(context).inflate(R.layout.room_custom_num_keyboard, null);
initKeyboardView(parentView);
setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
setContentView(parentView);
this.setBackgroundDrawable(new BitmapDrawable());
this.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
this.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
this.setFocusable(true);
this.setOutsideTouchable(true);
this.setTouchable(true);
this.setAnimationStyle(R.style.mypopwindow_anim_style);
}
private void initKeyboardView(View view) {
//初始化编辑框
editText = view.findViewById(R.id.et_keyboard_input);
editText.setFocusable(false);//禁止编辑框获取焦点
//①给数字键设置点击监听
for (int i = 0; i < commonButtonIds.length; i++) {
final Button button = view.findViewById(commonButtonIds[i]);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int curSelection = editText.getSelectionStart();
int length = editText.getText().toString().length();
if (curSelection < length) {
String content = editText.getText().toString();
editText.setText(content.substring(0, curSelection) + button.getText() + content.subSequence(curSelection, length));
editText.setSelection(curSelection + 1);
} else {
if (length == 0 && button.getText().toString().equals("0") || equals("")) {
return;
}
editText.setText(editText.getText().toString() + button.getText());
editText.setSelection(editText.getText().toString().length());
}
}
});
}
//③给叉按键设置点击监听
view.findViewById(R.id.rl_keyboard_cross).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int length = editText.getText().toString().length();
int curSelection = editText.getSelectionStart();
if (length > 0 && curSelection > 0 && curSelection <= length) {
String content = editText.getText().toString();
editText.setText(content.substring(0, curSelection - 1) + content.subSequence(curSelection, length));
editText.setSelection(curSelection - 1);
}
}
});
//③给叉按键设置点击监听
view.findViewById(R.id.iv_keyboard_cross).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int length = editText.getText().toString().length();
int curSelection = editText.getSelectionStart();
if (length > 0 && curSelection > 0 && curSelection <= length) {
String content = editText.getText().toString();
editText.setText(content.substring(0, curSelection - 1) + content.subSequence(curSelection, length));
editText.setSelection(curSelection - 1);
}
}
});
//确定
view.findViewById(R.id.bt_keyboard_ok).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "onClick: 您输入的数量是:" + editText.getText().toString());
mListener.inputComplete(editText.getText().toString());
}
});
}
public void show() {
if (!isShowing() && anchorView != null) {
// doRandomSortOp();
this.showAtLocation(anchorView, Gravity.BOTTOM, 0, 0);
}
}
/**
* 随机分布数字
*/
private void doRandomSortOp() {
if (parentView == null) {
return;
}
if (!isRandomSort) {
for (int i = 0; i < commonButtonIds.length; i++) {
final Button button = parentView.findViewById(commonButtonIds[i]);
button.setText("" + i);
}
} else {
list.clear();
Random ran = new Random();
while (list.size() < commonButtonIds.length) {
int n = ran.nextInt(commonButtonIds.length);
if (!list.contains(n))
list.add(n);
}
for (int i = 0; i < commonButtonIds.length; i++) {
final Button button = parentView.findViewById(commonButtonIds[i]);
button.setText("" + list.get(i));
}
}
}
public void releaseResources() {
this.dismiss();
context = null;
anchorView = null;
if (list != null) {
list.clear();
list = null;
}
}
public void addRoomPasswordListener(KeyboardCompleteListener listener) {
mListener = listener;
}
public interface KeyboardCompleteListener {
void inputComplete(String inputContent);//输入完成
}
}

View File

@@ -0,0 +1,194 @@
package com.xscm.moduleutil.widget.dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.blankj.utilcode.util.ToastUtils;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.base.BaseMvpDialogFragment;
import com.xscm.moduleutil.bean.GiftLabelBean;
import com.xscm.moduleutil.bean.GiftPackBean;
import com.xscm.moduleutil.bean.RewardUserBean;
import com.xscm.moduleutil.bean.RoonGiftModel;
import com.xscm.moduleutil.bean.WalletBean;
import com.xscm.moduleutil.bean.room.RoomAuction;
import com.xscm.moduleutil.databinding.DialogRewardFragmentBinding;
import com.xscm.moduleutil.databinding.FragmentRewardGiftDialogBinding;
import com.xscm.moduleutil.presenter.RewardGiftContacts;
import com.xscm.moduleutil.presenter.RewardGiftPresenter;
import com.xscm.moduleutil.utils.ChatLauncher;
import com.xscm.moduleutil.utils.ImageUtils;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.listener.OnRefreshLoadMoreListener;
import java.util.List;
/**
* @author qx
* @data 2025/6/16
* @description: 广场中所有的打赏榜单列表
*/
public class RewardDialogFragment extends BaseMvpDialogFragment<RewardGiftPresenter, DialogRewardFragmentBinding> implements RewardGiftContacts.View {
private String circle_id;
private BaseQuickAdapter<RewardUserBean, BaseViewHolder> adapter;
private int page;
public static void show(String id, FragmentManager fragmentManager) {
RewardDialogFragment dialogFragment = new RewardDialogFragment();
Bundle args = new Bundle();
args.putString("circle_id", id); // 可选:传递参数
dialogFragment.setArguments(args);
dialogFragment.show(fragmentManager, "RewardDialogFragment");
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
circle_id = getArguments().getString("circle_id");
}
@Override
protected void initDialogStyle(Window window) {
super.initDialogStyle(window);
window.setGravity(Gravity.BOTTOM);
window = getDialog().getWindow();
if (window != null) {
// 设置固定高度为 500dp
int screenHeight = getResources().getDisplayMetrics().heightPixels;
int heightInDp = (int) (screenHeight * 0.8f);;
int heightInPx = (int) heightInDp;
window.setLayout(WindowManager.LayoutParams.MATCH_PARENT, heightInPx);
// 可选:设置动画样式(从底部弹出)
window.setWindowAnimations(com.xscm.moduleutil.R.style.CommonShowDialogBottom);
// 强制支持 adjustResize 行为
// window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
}
}
@Override
protected RewardGiftPresenter bindPresenter() {
return new RewardGiftPresenter(this, getActivity());
}
@Override
protected void initData() {
MvpPre.getRewardList(circle_id, 1, 10);
}
@Override
protected void initView() {
mBinding.srl.setOnRefreshLoadMoreListener(new OnRefreshLoadMoreListener() {
@Override
public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
page++;
MvpPre.getRewardList(circle_id, page, 10);
}
@Override
public void onRefresh(@NonNull RefreshLayout refreshLayout) {
// EventBus.getDefault().post(new BannerRefreshEvent());
page = 1;
MvpPre.getRewardList(circle_id, page, 10);
}
});
mBinding.rvComment.setLayoutManager(new LinearLayoutManager(getActivity()));
adapter = new BaseQuickAdapter<RewardUserBean, BaseViewHolder>(R.layout.item_dialog_reward, null) {
@Override
protected void convert(BaseViewHolder helper, RewardUserBean item) {
int position = helper.getLayoutPosition();
TextView tv_rank = helper.getView(R.id.rank_icon);
if (position < 3) {
switch (position) {
case 0:
tv_rank.setBackground(getResources().getDrawable(R.mipmap.rewar_1));
break;
case 1:
tv_rank.setBackground(getResources().getDrawable(R.mipmap.rewar_2));
break;
case 2:
tv_rank.setBackground(getResources().getDrawable(R.mipmap.rewar_3));
break;
}
} else {
tv_rank.setText(String.valueOf(position + 1)); // 显示从1开始的序号
}
// 绑定用户信息
helper.setText(R.id.tvName, item.getNickname());
helper.setText(R.id.tvPic, item.getTotal_price());
ImageUtils.loadHeadCC(item.getAvatar(), helper.getView(R.id.ivAvatar));
helper.getView(R.id.tvScore).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ChatLauncher.getInstance().launchC2CChat(getActivity(), item.getUser_id());
}
});
}
};
mBinding.rvComment.setAdapter(adapter);
}
@Override
protected int getLayoutId() {
return R.layout.dialog_reward_fragment;
}
@Override
public void getRewardList(List<RewardUserBean> rewardUserBeanList) {
adapter.addData(rewardUserBeanList);
}
@Override
public void getGiftLabel(List<GiftLabelBean> giftLabelBeans) {
}
@Override
public void setGiftList(List<RoonGiftModel> roonGiftModels, int type) {
}
@Override
public void giveGift() {
}
@Override
public void wallet(WalletBean walletBean) {
}
@Override
public void reward_zone() {
ToastUtils.showShort("打赏成功");
}
@Override
public void roomAuctionJoin(RoomAuction.AuctionListBean auctionListBean) {
}
@Override
public void giftPack(List<GiftPackBean> giftPackBean) {
}
}

View File

@@ -0,0 +1,321 @@
package com.xscm.moduleutil.widget.dialog;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.recyclerview.widget.LinearLayoutManager;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import com.hjq.toast.ToastUtils;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.adapter.GiftTwoDetailsFragment;
import com.xscm.moduleutil.base.BaseMvpDialogFragment;
import com.xscm.moduleutil.bean.GiftLabelBean;
import com.xscm.moduleutil.bean.GiftNumBean;
import com.xscm.moduleutil.bean.GiftPackBean;
import com.xscm.moduleutil.bean.RewardUserBean;
import com.xscm.moduleutil.bean.RoonGiftModel;
import com.xscm.moduleutil.bean.WalletBean;
import com.xscm.moduleutil.bean.room.RoomAuction;
import com.xscm.moduleutil.color.ThemeableDrawableUtils;
import com.xscm.moduleutil.databinding.FragmentRewardGiftDialogBinding;
import com.xscm.moduleutil.dialog.RechargeDialogFragment;
import com.xscm.moduleutil.event.GiftRewardEvent;
import com.xscm.moduleutil.presenter.RewardGiftContacts;
import com.xscm.moduleutil.presenter.RewardGiftPresenter;
import com.xscm.moduleutil.utils.ColorManager;
import com.xscm.moduleutil.utils.ImageUtils;
import com.xscm.moduleutil.widget.GifAvatarOvalView;
import org.greenrobot.eventbus.EventBus;
import java.util.ArrayList;
import java.util.List;
/**
* @author qx
* @data 2025/5/29
* @description: 打赏礼物的fragment
*/
public class RewardGiftDialogFragment extends BaseMvpDialogFragment<RewardGiftPresenter, FragmentRewardGiftDialogBinding> implements RewardGiftContacts.View {
private BaseQuickAdapter<RewardUserBean, BaseViewHolder> adapter;
private SelectGiftNumPopupWindow mSelectGiftNumPopupWindow;
private KeyboardPopupWindow mKeyboardPopupWindow;
private List<GiftNumBean> mGiftNumList;
private String circle_id;
private RoonGiftModel roonGiftModel = null;
private List<Fragment> fragmentList = new ArrayList<>();
private String user_id;
private String giftNumber = "";
private int point;
@Override
protected RewardGiftPresenter bindPresenter() {
return new RewardGiftPresenter(this, getActivity());
}
public static void show(String id,String userId,int point, FragmentManager fragmentManager) {
RewardGiftDialogFragment dialogFragment = new RewardGiftDialogFragment();
Bundle args = new Bundle();
args.putString("circle_id", id); // 可选:传递参数
args.putString("user_id", userId);
args.putInt("point",point);
// 设置参数...
dialogFragment.setArguments(args);
dialogFragment.show(fragmentManager, "RewardGiftDialogFragment");
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
circle_id=getArguments().getString("circle_id");
user_id=getArguments().getString("user_id");
point=getArguments().getInt("point");
}
@Override
protected void initDialogStyle(Window window) {
super.initDialogStyle(window);
window.setGravity(Gravity.BOTTOM);
}
@Override
protected void initData() {
MvpPre.getRewardList(circle_id, 1, 10);
MvpPre.getGiftLabel("1");
MvpPre.wallet();
mGiftNumList=new ArrayList<>();
mGiftNumList.add(new GiftNumBean("20", "x20"));
mGiftNumList.add(new GiftNumBean("15", "x15"));
mGiftNumList.add(new GiftNumBean("10", "x10"));
mGiftNumList.add(new GiftNumBean("5", "x5"));
mGiftNumList.add(new GiftNumBean("1", "x1"));
}
@Override
protected void initView() {
mBinding.recycleView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
adapter = new BaseQuickAdapter<RewardUserBean, BaseViewHolder>(com.xscm.moduleutil.R.layout.item_reward1, null) {
@Override
protected void convert(BaseViewHolder helper, RewardUserBean item) {
GifAvatarOvalView gifAvatarOvalView = helper.getView(com.xscm.moduleutil.R.id.im_reward1);
if (item!=null ) {
ImageUtils.loadHeadCC(item.getAvatar(), helper.getView(com.xscm.moduleutil.R.id.im_reward1));
}
}
};
mBinding.recycleView.setAdapter(adapter);
mBinding.tvGiveCoinNum.setOnClickListener(this::onClisk);
mBinding.tvRewardNum.setOnClickListener(this::onClisk);
mBinding.cz.setOnClickListener(this::onClisk);
mBinding.tvGive.setOnClickListener(this::onClisk);
float[] corners = {0f, 65f, 65f, 0f};
ThemeableDrawableUtils.setThemeableRoundedBackground( mBinding.tvGive, ColorManager.getInstance().getPrimaryColorInt(), corners);
mBinding.tvGive.setTextColor(ColorManager.getInstance().getButtonColorInt());
mBinding.cz.setTextColor(ColorManager.getInstance().getPrimaryColorInt());
}
private void onClisk(View view1) {
if (view1.getId()==R.id.tv_give_coin_num){
if (mSelectGiftNumPopupWindow == null) {
mSelectGiftNumPopupWindow = new SelectGiftNumPopupWindow(getSelfActivity(), (adapter, view, position) -> {
GiftNumBean giftNumBean = (GiftNumBean) adapter.getItem(position);
if ("0".equals(giftNumBean.getNumber())) {//自定义
mKeyboardPopupWindow = new KeyboardPopupWindow(getSelfActivity(), getView());
mKeyboardPopupWindow.refreshKeyboardOutSideTouchable(false);//false开启键盘 true关闭键盘
mKeyboardPopupWindow.addRoomPasswordListener(new KeyboardPopupWindow.KeyboardCompleteListener() {//监听自定键盘的完成
@Override
public void inputComplete(String inputContent) {
mBinding.tvGiveCoinNum.setText(inputContent);
mKeyboardPopupWindow.releaseResources();
}
});
} else {
mBinding.tvGiveCoinNum.setText(giftNumBean.getNumber());
}
mSelectGiftNumPopupWindow.dismiss();
});
}
mSelectGiftNumPopupWindow.setData(mGiftNumList);
mSelectGiftNumPopupWindow.showAtLocation(mBinding.tvGiveCoinNum, Gravity.BOTTOM | Gravity.RIGHT, 100, 230);
}else if (view1.getId()== com.xscm.moduleutil.R.id.tv_reward_num){
RewardDialogFragment.show(circle_id,getChildFragmentManager());
}else if (view1.getId()== R.id.cz){
RechargeDialogFragment.show("",null, getActivity().getSupportFragmentManager());
}else if (view1.getId()== R.id.tv_give){
for (int i=0;i<mGiftNumList.size();i++) {
if (mBinding.tvGiveCoinNum.getText().toString().equals(mGiftNumList.get(i).getText())) {
giftNumber = mGiftNumList.get(i).getNumber();
}
}
giveGift(giftNumber);
}
}
@Override
protected int getLayoutId() {
return R.layout.fragment_reward_gift_dialog;
}
@Override
public void getRewardList(List<RewardUserBean> rewardUserBeanList) {
if (rewardUserBeanList != null && !rewardUserBeanList.isEmpty()) {
mBinding.recycleView.setVisibility(View.VISIBLE);
mBinding.tvRewardNum.setVisibility(View.VISIBLE);
mBinding.tvRewardTitle.setVisibility(View.INVISIBLE);
mBinding.tvRewardNum.setText("已有" + String.valueOf(rewardUserBeanList.size()) + "个人打赏");
int limit = Math.min(rewardUserBeanList.size(), 5);
List<RewardUserBean> limitedList = rewardUserBeanList.subList(0, limit);
adapter.setNewData(limitedList);
} else {
adapter.setNewData(null);
mBinding.recycleView.setVisibility(View.INVISIBLE);
mBinding.tvRewardNum.setVisibility(View.INVISIBLE);
mBinding.tvRewardTitle.setVisibility(View.VISIBLE);
}
}
@Override
public void getGiftLabel(List<GiftLabelBean> giftLabelBeans) {
mBinding.viewPager.setAdapter(new MyFragmentPagerAdapter(getChildFragmentManager(), giftLabelBeans,fragmentList));
mBinding.slidingTabLayout.setViewPager(mBinding.viewPager);
mBinding.slidingTabLayout.setCurrentTab(0);
}
private int getSelectedGift() {
int currentItem = mBinding.viewPager.getCurrentItem();
// if (currentItem < 1) { //比2小是才是礼物
GiftTwoDetailsFragment fragment = (GiftTwoDetailsFragment) fragmentList.get(currentItem);
roonGiftModel = fragment.getGiftList();
// }
// else {
// GiftFragment fragment = (GiftFragment) fragmentList.get(currentItem);
// giftModel = fragment.getmData();
// }
return currentItem;
}
private void giveGift(String num) {
getSelectedGift();
int currentItem = mBinding.viewPager.getCurrentItem();
String userId = user_id;
if (currentItem < 1) {
if (roonGiftModel == null) {
ToastUtils.show("请选择礼物");
return;
}
if (TextUtils.isEmpty(num)) {
ToastUtils.show("请选择打赏礼物数量");
return;
}
if (Integer.valueOf(num) <= 0) {
ToastUtils.show("请选择打赏礼物数量");
return;
}
} else {
if (roonGiftModel == null) {
ToastUtils.show("请选择礼物");
return;
}
if (TextUtils.isEmpty(num)) {
ToastUtils.show("请选择打赏礼物数量");
return;
}
if (Integer.valueOf(num) <= 0) {
ToastUtils.show("请选择打赏礼物数量");
return;
}
}
if (currentItem == 0) {
//礼物打赏
giftNumber = num;
MvpPre.reward_zone(circle_id, roonGiftModel.getGift_id(), num, "1");
} else {
//背包礼物打赏
// giftNumber = num;
// roomGiftGiveEvent = new RoomGiftGiveEvent(userId, roomId, pit, num, 1, giftModel, null);
//
// MvpPre.giveBackGift(userId, giftModel.getGift_id(), roomId, pit, num, giftModel, 1);
}
}
@Override
public void setGiftList(List<RoonGiftModel> roonGiftModels,int type) {
}
@Override
public void giveGift() {
}
@Override
public void wallet(WalletBean walletBean) {
mBinding.tvRewardGift.setText(walletBean.getCoin());
}
@Override
public void reward_zone() {
com.blankj.utilcode.util.ToastUtils.showShort("打赏成功");
EventBus.getDefault().post(new GiftRewardEvent(point));
dismiss();
}
@Override
public void roomAuctionJoin(RoomAuction.AuctionListBean auctionListBean) {
}
@Override
public void giftPack(List<GiftPackBean> giftPackBean) {
}
private static class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {
private List<GiftLabelBean> list;
private List<Fragment> fragmentList ;
public MyFragmentPagerAdapter(FragmentManager fm, List<GiftLabelBean> list,List<Fragment> fragmentList) {
super(fm);
this.list = list;
this.fragmentList = fragmentList;
}
@Override
public Fragment getItem(int position) {
GiftLabelBean model = list.get(position);
Fragment fragment = GiftTwoDetailsFragment.newInstance(model.getId(), 2);
fragmentList.add(fragment); // 保存 Fragment 实例
return fragment;
}
@Override
public int getCount() {
return list.size();
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
GiftLabelBean model = list.get(position);
return model.getName();
}
}
}

View File

@@ -0,0 +1,41 @@
package com.xscm.moduleutil.widget.dialog;
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.GiftNumBean;
/**
*@author qx
*@data 2025/5/30
*@description: 展示礼物打赏数量的适配器
*/
public class SelectGiftNumAdapter extends BaseQuickAdapter<GiftNumBean, BaseViewHolder> {
public SelectGiftNumAdapter() {
super(R.layout.room_rv_item_gift_num_windows);
}
@Override
protected void convert(BaseViewHolder helper, GiftNumBean item) {
// helper.setText(R.id.tv_gift_mum, item.getNumber())
helper.setText(R.id.tv_gift_name, item.getText());
if ("0".equals(item.getNumber())) {
// helper.setText(R.id.tv_custom, item.getText());
// helper.setVisible(R.id.tv_custom,true);
// helper.setVisible(R.id.tv_gift_mum, false).
helper.setVisible(R.id.tv_gift_name, false);
}else {
// helper.setVisible(R.id.tv_custom,false);
// helper.setVisible(R.id.tv_gift_mum, false)
helper.setVisible(R.id.tv_gift_name, true);
}
if("1".equals(item.getNumber())){
helper.getView(R.id.iv_split).setVisibility(View.GONE);
}
}
}

View File

@@ -0,0 +1,54 @@
package com.xscm.moduleutil.widget.dialog;
import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.PopupWindow;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.GiftNumBean;
import java.util.List;
/**
*@author qx
*@data 2025/5/30
*@description: 选择礼物数量窗口
*/
public class SelectGiftNumPopupWindow extends PopupWindow {
private View rootView;
private SelectGiftNumAdapter selectGiftNumAdapter;
public SelectGiftNumPopupWindow(Context context, BaseQuickAdapter.OnItemClickListener onItemClickListener) {
LayoutInflater inflater = LayoutInflater.from(context);
rootView = inflater.inflate(R.layout.room_window_pop_gift, null);
RecyclerView recyclerView = rootView.findViewById(R.id.rv_gift_num_window);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
recyclerView.setAdapter(selectGiftNumAdapter = new SelectGiftNumAdapter());
selectGiftNumAdapter.setOnItemClickListener(onItemClickListener);
setContentView(rootView);
this.setBackgroundDrawable(new BitmapDrawable());
this.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
this.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
this.setFocusable(true);
this.setOutsideTouchable(true);
this.setTouchable(true);
this.setAnimationStyle(R.style.mypopwindow_anim_style);
}
public void setData(List<GiftNumBean> data) {
if (selectGiftNumAdapter != null) {
selectGiftNumAdapter.setNewData(data);
}
}
}

View File

@@ -0,0 +1,217 @@
package com.xscm.moduleutil.widget.dialog;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static com.blankj.utilcode.util.ActivityUtils.startActivity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import com.blankj.utilcode.util.ImageUtils;
import com.blankj.utilcode.util.ToastUtils;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.activity.WebViewActivity;
import com.xscm.moduleutil.bean.CircleListBean;
import com.xscm.moduleutil.databinding.RoomDialogShareBinding;
import com.xscm.moduleutil.utils.BaseBottomSheetDialog;
import com.xscm.moduleutil.utils.SpUtil;
import com.xscm.moduleutil.utils.wx.WeChatShareUtils;
import com.tencent.mm.opensdk.modelmsg.SendMessageToWX;
/**
* 房间分享弹窗
*/
public class ShareDialog extends BaseBottomSheetDialog<RoomDialogShareBinding> {
public WeChatShareUtils weChatShareUtils;
private Context mContext;
private String mcontent;
private String murl;
private String ids;
private int types;//1用户 2房间3动态
private String mUserId;
private CircleListBean bean;
public interface OnShareDataListener {
void onShareDataLoaded(String id);
}
private OnShareDataListener listener;
public void setOnShareDataListener(OnShareDataListener listener) {
this.listener = listener;
}
private static final String TAG = "ShareDialog";
public ShareDialog(Context context, String content, String url, String id, int type,String userId,CircleListBean bean) {
super(context);
this.mcontent = content;
this.murl = url;
this.mContext = getContext();
this.ids = id;
this.types = type;
this.mUserId = userId;
this.bean = bean;
Log.i(TAG, "(Start)启动了===========================ShareDialog");
iniv();
}
private void iniv() {
if (types == 1) {//1是分享动态
} else if (types == 2) {//分享相册
mBinding.tvQq.setVisibility(GONE);
mBinding.tvQqq.setVisibility(GONE);
mBinding.rl.setVisibility(GONE);
}
if (mUserId.equals(SpUtil.getUserId()+"")){
mBinding.shanc.setVisibility(VISIBLE);
}else {
mBinding.shanc.setVisibility(GONE);
}
}
@Override
public int getLayout() {
return R.layout.room_dialog_share;
}
@Override
public void initView() {
Window window = getWindow();
WindowManager.LayoutParams lp = window.getAttributes();
lp.dimAmount = 0.4f;
window.setAttributes(lp);
window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
window.setGravity(Gravity.BOTTOM);
mBinding.tvQq.setOnClickListener(this::onClick);
mBinding.tvWx.setOnClickListener(this::onClick);
mBinding.tvWxq.setOnClickListener(this::onClick);
mBinding.tvQqq.setOnClickListener(this::onClick);
mBinding.tvClean.setOnClickListener(this::onClick);
mBinding.tvCopy.setOnClickListener(this::onClick);
mBinding.tvJub.setOnClickListener(this::onClick);
mBinding.shanc.setOnClickListener(this::onClick);
}
@Override
public void initData() {
weChatShareUtils = WeChatShareUtils.getInstance(getContext());
// if (mUserId.equals(SpUtil.getUserId()+"")){
// mBinding.shanc.setVisibility(GONE);
// }else {
// mBinding.shanc.setVisibility(VISIBLE);
// }
}
public void onClick(View view) {
int id = view.getId();
// String appName = CommonAppContext.getInstance().getResources().getString(R.string.app_name);
if (R.id.tv_qq == id) {
// AppLogUtil.reportAppLog(AppLogEvent.D0108, "share_way", "QQ分享");
// ShareUtil.shareWeb((Activity) view.getContext(), URLConstants.SHARE, String.format("我在%s玩呢,大家都在玩,快来展示您的精彩", appName)
// , appName, "", R.mipmap.ic_launcher_new, SHARE_MEDIA.QQ);
} else if (R.id.tv_wx == id) {
// AppLogUtil.reportAppLog(AppLogEvent.D0108, "share_way", "微信分享");
// ShareUtil.shareWeb((Activity) view.getContext(), URLConstants.SHARE, String.format("我在%s玩呢,大家都在玩,快来展示您的精彩", appName)
// , appName, "", R.mipmap.ic_launcher_new, SHARE_MEDIA.WEIXIN);
fun_handleShare(SendMessageToWX.Req.WXSceneSession);
} else if (R.id.tv_wxq == id) {
fun_handleShare1(SendMessageToWX.Req.WXSceneTimeline);
} else if (R.id.tv_qqq == id) {
} else if (R.id.shanc == id) {
if (listener != null) {
listener.onShareDataLoaded(ids);
}
} else if (R.id.tv_jub == id) {
if (types == 3) {
Intent intent = new Intent(getContext(), WebViewActivity.class);
intent.putExtra("url", "https://vespa.qxmier.com/web/index.html#/pages/feedback/report?id=" + SpUtil.getToken() + "&fromType=" + types + "&fromId=" + bean.getId());
intent.putExtra("title", "举报");
startActivity(intent);
}else if (types == 1){
Intent intent = new Intent(getContext(), WebViewActivity.class);
intent.putExtra("url", "https://vespa.qxmier.com/web/index.html#/pages/feedback/report?id=" + SpUtil.getToken() + "&fromType=" + types + "&fromId=" + bean.getUser_id());
intent.putExtra("title", "举报");
startActivity(intent);
}
} else if (R.id.tv_copy == id) {
ClipboardManager clipboard = (ClipboardManager)mContext.getSystemService( Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("链接",bean.getShare_url() );
if (clipboard != null) {
clipboard.setPrimaryClip(clip);
com.hjq.toast.ToastUtils.show("复制成功");
} else {
com.hjq.toast.ToastUtils.show("复制失败");
}
}
dismiss();
}
// todo fun_handleShare 分享小程序类型 (只支持分享给微信好友)
private void fun_handleShare(int scene) {
if (weChatShareUtils.isSupportWX()) {
String appName = mContext.getPackageManager().getApplicationLabel(mContext.getApplicationInfo()).toString();
if (types == 2){
com.xscm.moduleutil.utils.ImageUtils.loadBitmap(murl, new com.xscm.moduleutil.utils.ImageUtils.onLoadBitmap() {
@Override
public void onReady(Bitmap resource) {
weChatShareUtils.sharePic(resource, scene);
}
});
}else {
String shortContent = mcontent.length() > 20 ? mcontent.substring(0, 20) : mcontent;
String desc = shortContent;
String title = appName;
String url = murl;
Bitmap bitmap = ImageUtils.drawable2Bitmap(mContext.getResources().getDrawable(R.mipmap.ic_launcher));
weChatShareUtils.shareUrl(url, title, bitmap, desc, scene);
}
} else {
ToastUtils.showShort("手机上微信版本不支持分享功能");
}
}
private void fun_handleShare1(int scene) {
if (weChatShareUtils.isSupportWX()) {
String appName = mContext.getPackageManager().getApplicationLabel(mContext.getApplicationInfo()).toString();
if (types == 2) {
com.xscm.moduleutil.utils.ImageUtils.loadBitmap(murl, new com.xscm.moduleutil.utils.ImageUtils.onLoadBitmap() {
@Override
public void onReady(Bitmap resource) {
weChatShareUtils.sharePic(resource, scene);
}
});
}else {
String shortContent = mcontent.length() > 20 ? mcontent.substring(0, 20) : mcontent;
String desc = shortContent;
String title = appName;
String url = murl;
// Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(),R.mipmap.ic_launcher);
Bitmap bitmap = ImageUtils.drawable2Bitmap(mContext.getResources().getDrawable(R.mipmap.ic_launcher));
weChatShareUtils.shareUrl(url, title, bitmap, desc, scene);
}
} else {
ToastUtils.showShort("手机上微信版本不支持分享功能");
}
}
}

View File

@@ -0,0 +1,245 @@
package com.xscm.moduleutil.widget.floatingView;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.blankj.utilcode.util.BarUtils;
/**
* @Author $
* @Time $ $
* @Description $
*/
public class Floa extends FrameLayout {
public static final int MARGIN_EDGE = 13;
private float mOriginalRawX;
private float mOriginalRawY;
private float mOriginalX;
private float mOriginalY;
private MagnetViewListener mMagnetViewListener;
private static final int TOUCH_TIME_THRESHOLD = 150;
private long mLastTouchDownTime;
protected MoveAnimator mMoveAnimator;
protected int mScreenWidth;
private int mScreenHeight;
private int mStatusBarHeight;
private boolean isNearestLeft = true;
private float mPortraitY;
public void setMagnetViewListener(MagnetViewListener magnetViewListener) {
this.mMagnetViewListener = magnetViewListener;
}
public interface MagnetViewListener {
void onClick(Floa floa);
}
public Floa(Context context) {
this(context, null);
}
public Floa(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Floa(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mMoveAnimator = new MoveAnimator();
mStatusBarHeight = BarUtils.getStatusBarHeight();
setClickable(true);
updateSize();
// 确保视图初始位置在屏幕内
setX(Math.max(0, Math.min(getX(), mScreenWidth - getWidth())));
setY(Math.max(mStatusBarHeight, Math.min(getY(), mScreenHeight - getHeight())));
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event == null) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
changeOriginalTouchParams(event);
updateSize();
mMoveAnimator.stop();
break;
case MotionEvent.ACTION_MOVE:
Log.d("FloatView", "ACTION_MOVE: " + event.getX() + ", " + event.getY()); // 添加日志
updateViewPosition(event);
break;
case MotionEvent.ACTION_UP:
clearPortraitY();
moveToEdge();
if (isOnClickEvent()) {
dealClickEvent();
}
break;
}
return true;
}
protected void dealClickEvent() {
if (mMagnetViewListener != null) {
mMagnetViewListener.onClick(this);
}
}
protected boolean isOnClickEvent() {
return System.currentTimeMillis() - mLastTouchDownTime < TOUCH_TIME_THRESHOLD;
}
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);
// 确保视图可见
if (getVisibility() != VISIBLE) {
setVisibility(VISIBLE);
}
}
private void changeOriginalTouchParams(MotionEvent event) {
mOriginalX = getX();//getX()相对于控件X坐标的距离
mOriginalY = getY();
mOriginalRawX = event.getRawX();//getRawX()指控件在屏幕上的X坐标
mOriginalRawY = event.getRawY();
mLastTouchDownTime = System.currentTimeMillis();
}
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;
}
// mScreenWidth = (SystemUtils.getScreenWidth(getContext()) - this.getWidth());
// mScreenHeight = SystemUtils.getScreenHeight(getContext());
}
public void moveToEdge() {
moveToEdge(isNearestLeft(), false);
}
public void moveToEdge(boolean isLeft, boolean isLandscape) {
float moveDistance = isLeft ? MARGIN_EDGE : mScreenWidth - MARGIN_EDGE;
float y = getY();
if (!isLandscape && mPortraitY != 0) {
y = mPortraitY;
clearPortraitY();
}
mMoveAnimator.start(moveDistance, Math.min(Math.max(0, y), mScreenHeight - getHeight()));
}
private void clearPortraitY() {
mPortraitY = 0;
}
protected boolean isNearestLeft() {
int middle = mScreenWidth / 2;
isNearestLeft = getX() < middle;
return isNearestLeft;
}
public void onRemove() {
if (mMagnetViewListener != null) {
mMagnetViewListener=null;
}
}
protected class MoveAnimator implements Runnable {
private Handler handler = new Handler(Looper.getMainLooper());
private float destinationX;
private float destinationY;
private long startingTime;
void start(float x, float y) {
this.destinationX = x;
this.destinationY = y;
startingTime = System.currentTimeMillis();
handler.post(this);
}
@Override
public void run() {
if (getRootView() == null || getRootView().getParent() == null) {
return;
}
float progress = Math.min(1, (System.currentTimeMillis() - startingTime) / 400f);
float deltaX = (destinationX - getX()) * progress;
float deltaY = (destinationY - getY()) * progress;
move(deltaX, deltaY);
if (progress < 1) {
handler.post(this);
}
}
private void stop() {
handler.removeCallbacks(this);
}
}
private void move(float deltaX, float deltaY) {
setX(getX() + deltaX);
setY(getY() + deltaY);
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (getParent() != null) {
final boolean isLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
markPortraitY(isLandscape);
((ViewGroup) getParent()).post(new Runnable() {
@Override
public void run() {
updateSize();
moveToEdge(isNearestLeft, isLandscape);
}
});
}
}
private void markPortraitY(boolean isLandscape) {
if (isLandscape) {
mPortraitY = getY();
}
}
}

View File

@@ -0,0 +1,201 @@
package com.xscm.moduleutil.widget.floatingView;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.FrameLayout;
import com.blankj.utilcode.util.BarUtils;
import com.blankj.utilcode.util.ScreenUtils;
/**
* 可拖拽悬浮
* 稳定版悬浮窗控件 - 支持点击/拖动分离、适配横竖屏、自动吸附边缘
*/
public class FloatingMagnetView extends FrameLayout {
public static final int MARGIN_EDGE = 0;
private float mOriginalX, mOriginalY; // 当前 View 的坐标
private float mOriginalRawX, mOriginalRawY; // 触摸点的原始屏幕坐标
private long mLastTouchDownTime;
private boolean isDragging = false;
private boolean isNearestLeft = true;
private int mScreenWidth;
private int mScreenHeight;
private int mStatusBarHeight;
private MoveAnimator mMoveAnimator;
private OnFloatingClickListener listener;
private static final int TOUCH_TIME_THRESHOLD = 150;
private static final float CLICK_DRAG_THRESHOLD = 10;
public FloatingMagnetView(Context context) {
this(context, null);
}
public FloatingMagnetView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FloatingMagnetView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mMoveAnimator = new MoveAnimator();
mStatusBarHeight = BarUtils.getStatusBarHeight();
setClickable(true);
updateSize();
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateSize();
moveToEdge(isNearestLeft);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
return false; // 不拦截,让子控件优先处理点击
case MotionEvent.ACTION_MOVE:
float dx = Math.abs(ev.getX() - (mOriginalRawX - mOriginalX));
float dy = Math.abs(ev.getY() - (mOriginalRawY - mOriginalY));
if (dx > CLICK_DRAG_THRESHOLD || dy > CLICK_DRAG_THRESHOLD) {
isDragging = true;
}
return isDragging;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
isDragging = false;
return false;
default:
return super.onInterceptTouchEvent(ev);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event == null) return false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mOriginalX = getX();
mOriginalY = getY();
mOriginalRawX = event.getRawX();
mOriginalRawY = event.getRawY();
mLastTouchDownTime = System.currentTimeMillis();
mMoveAnimator.stop();
isDragging = false;
break;
case MotionEvent.ACTION_MOVE:
if (isDragging) {
float newX = mOriginalX + (event.getRawX() - mOriginalRawX);
float newY = mOriginalY + (event.getRawY() - mOriginalRawY);
// 限制 Y 轴边界
if (newY < mStatusBarHeight) newY = mStatusBarHeight;
if (newY > mScreenHeight - getHeight()) newY = mScreenHeight - getHeight();
setX(newX);
setY(newY);
}
break;
case MotionEvent.ACTION_UP:
if (isOnClickEvent()) {
if (listener != null) {
listener.onClick();
}
} else {
moveToEdge();
}
isDragging = false;
break;
}
return true;
}
private boolean isOnClickEvent() {
long time = System.currentTimeMillis() - mLastTouchDownTime;
float dx = Math.abs(getX() - mOriginalX);
float dy = Math.abs(getY() - mOriginalY);
return time < TOUCH_TIME_THRESHOLD && dx < CLICK_DRAG_THRESHOLD && dy < CLICK_DRAG_THRESHOLD;
}
private void updateSize() {
mScreenWidth = ScreenUtils.getScreenWidth() - getWidth();
mScreenHeight = ScreenUtils.getScreenHeight();
}
public void moveToEdge() {
moveToEdge(isNearestLeft());
}
public void moveToEdge(boolean isLeft) {
float moveX = isLeft ? MARGIN_EDGE : mScreenWidth - MARGIN_EDGE;
mMoveAnimator.start(moveX, getY());
isNearestLeft = isLeft;
}
protected boolean isNearestLeft() {
int middle = mScreenWidth / 2;
return getX() < middle;
}
protected class MoveAnimator implements Runnable {
private Handler handler = new Handler(Looper.getMainLooper());
private float destinationX, destinationY;
private long startTime;
void start(float x, float y) {
this.destinationX = x;
this.destinationY = y;
this.startTime = System.currentTimeMillis();
handler.post(this);
}
@Override
public void run() {
if (getRootView() == null || getRootView().getParent() == null) return;
float progress = Math.min(1f, (System.currentTimeMillis() - startTime) / 400f);
float deltaX = (destinationX - getX()) * progress;
float deltaY = (destinationY - getY()) * progress;
setX(getX() + deltaX);
setY(getY() + deltaY);
if (progress < 1f) {
handler.post(this);
}
}
void stop() {
handler.removeCallbacks(this);
}
}
public void setOnFloatingClickListener(OnFloatingClickListener listener) {
this.listener = listener;
}
public interface OnFloatingClickListener {
void onClick();
}
}

View File

@@ -0,0 +1,197 @@
package com.xscm.moduleutil.widget.floatingView;
import android.app.Activity;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import androidx.annotation.LayoutRes;
import androidx.core.view.ViewCompat;
import com.xscm.moduleutil.base.CommonAppContext;
import java.lang.ref.WeakReference;
/**
* @ClassName FloatingView
* @Description 悬浮窗管理器
*/
public class FloatingView implements IFloatingView {
private View mFloatingView;
private static volatile FloatingView mInstance;
private WeakReference<FrameLayout> mContainer;
@LayoutRes
private int mLayoutId;
private ViewGroup.LayoutParams mLayoutParams = getParams();
private FloatingView() {
}
public static FloatingView get() {
if (mInstance == null) {
synchronized (FloatingView.class) {
if (mInstance == null) {
mInstance = new FloatingView();
}
}
}
return mInstance;
}
@Override
public FloatingView remove() {
orderId = 0;
if (mFloatingView == null) {
return this;
}
if (ViewCompat.isAttachedToWindow(mFloatingView) && getContainer() != null) {
getContainer().removeView(mFloatingView);
}
mFloatingView = null;
return this;
}
private void ensureFloatingView() {
synchronized (this) {
if (mFloatingView != null) {
addViewToWindow(mFloatingView);
return;
}
View view = LayoutInflater.from(CommonAppContext.getInstance()).inflate(mLayoutId, null);
mFloatingView = view;
view.setLayoutParams(mLayoutParams);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
addViewToWindow(view);
}
}
@Override
public FloatingView add() {
ensureFloatingView();
return this;
}
@Override
public FloatingView attach(Activity activity) {
attach(getActivityRoot(activity));
return this;
}
@Override
public FloatingView attach(FrameLayout container) {
if (container == null || mFloatingView == null) {
mContainer = new WeakReference<>(container);
return this;
}
if (mFloatingView.getParent() == container) {
return this;
}
if (getContainer() != null && mFloatingView.getParent() == getContainer()) {
getContainer().removeView(mFloatingView);
}
mContainer = new WeakReference<>(container);
container.addView(mFloatingView);
return this;
}
@Override
public FloatingView detach(Activity activity) {
detach(getActivityRoot(activity));
return this;
}
@Override
public FloatingView detach(FrameLayout container) {
if (mFloatingView != null && container != null && ViewCompat.isAttachedToWindow(mFloatingView)) {
container.removeView(mFloatingView);
}
if (getContainer() == container) {
mContainer = null;
}
return this;
}
@Override
public View getView() {
return mFloatingView;
}
@Override
public FloatingView customView(ViewGroup viewGroup) {
mFloatingView = viewGroup;
return this;
}
@Override
public FloatingView customView(@LayoutRes int resource) {
mLayoutId = resource;
return this;
}
@Override
public FloatingView layoutParams(ViewGroup.LayoutParams params) {
mLayoutParams = params;
if (mFloatingView != null) {
mFloatingView.setLayoutParams(params);
}
return this;
}
private void addViewToWindow(final View view) {
if (getContainer() == null) {
return;
}
getContainer().addView(view);
}
private FrameLayout getContainer() {
if (mContainer == null) {
return null;
}
return mContainer.get();
}
private FrameLayout.LayoutParams getParams() {
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.BOTTOM | Gravity.START;
params.setMargins(13, params.topMargin, params.rightMargin, 500);
return params;
}
private FrameLayout getActivityRoot(Activity activity) {
if (activity == null) {
return null;
}
try {
return (FrameLayout) activity.getWindow().getDecorView().findViewById(android.R.id.content);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private boolean isExpand = true;
/**
* 设置展开或者收起
*
* @param expand
*/
public void setExpand(boolean expand) {
isExpand = expand;
}
private int orderId;
}

View File

@@ -0,0 +1,33 @@
package com.xscm.moduleutil.widget.floatingView;
import android.app.Activity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.LayoutRes;
public interface IFloatingView {
FloatingView remove();
FloatingView add();
FloatingView attach(Activity activity);
FloatingView attach(FrameLayout container);
FloatingView detach(Activity activity);
FloatingView detach(FrameLayout container);
View getView();
FloatingView customView(ViewGroup viewGroup);
FloatingView customView(@LayoutRes int resource);
FloatingView layoutParams(ViewGroup.LayoutParams params);
}

View File

@@ -0,0 +1,195 @@
package com.xscm.moduleutil.widget.floatingView;
import android.app.Activity;
import android.os.Handler;
import android.os.Looper;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import androidx.annotation.LayoutRes;
import androidx.core.view.ViewCompat;
import com.xscm.moduleutil.base.CommonAppContext;
import java.lang.ref.WeakReference;
/**
* @ClassName FloatingView
* @Description 悬浮窗管理器
*/
public class NotifyFloatingView {
private View mFloatingView;
private static volatile NotifyFloatingView mInstance;
private WeakReference<FrameLayout> mContainer;
@LayoutRes
private int mLayoutId;
private ViewGroup.LayoutParams mLayoutParams = getParams();
private NotifyFloatingView() {
}
public static NotifyFloatingView get() {
if (mInstance == null) {
synchronized (NotifyFloatingView.class) {
if (mInstance == null) {
mInstance = new NotifyFloatingView();
}
}
}
return mInstance;
}
public NotifyFloatingView remove() {
if (mFloatingView == null) {
return this;
}
if (ViewCompat.isAttachedToWindow(mFloatingView) && getContainer() != null) {
getContainer().removeView(mFloatingView);
}
mFloatingView = null;
return this;
}
private void ensureFloatingView() {
synchronized (this) {
if (mFloatingView != null) {
addViewToWindow(mFloatingView);
return;
}
View view = LayoutInflater.from(CommonAppContext.getInstance()).inflate(mLayoutId, null);
mFloatingView = view;
view.setLayoutParams(mLayoutParams);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
addViewToWindow(view);
}
}
public NotifyFloatingView add() {
ensureFloatingView();
handler.removeCallbacks(action);
handler.postDelayed(action, 3000);
return this;
}
public NotifyFloatingView attach(Activity activity) {
attach(getActivityRoot(activity));
return this;
}
public NotifyFloatingView attach(FrameLayout container) {
if (container == null || mFloatingView == null) {
mContainer = new WeakReference<>(container);
return this;
}
if (mFloatingView.getParent() == container) {
return this;
}
if (getContainer() != null && mFloatingView.getParent() == getContainer()) {
getContainer().removeView(mFloatingView);
}
mContainer = new WeakReference<>(container);
container.addView(mFloatingView);
return this;
}
public NotifyFloatingView detach(Activity activity) {
detach(getActivityRoot(activity));
return this;
}
public NotifyFloatingView detach(FrameLayout container) {
if (mFloatingView != null && container != null && ViewCompat.isAttachedToWindow(mFloatingView)) {
container.removeView(mFloatingView);
}
if (getContainer() == container) {
mContainer = null;
}
return this;
}
public View getView() {
return mFloatingView;
}
public NotifyFloatingView customView(ViewGroup viewGroup) {
mFloatingView = viewGroup;
return this;
}
public NotifyFloatingView customView(@LayoutRes int resource) {
mLayoutId = resource;
return this;
}
public NotifyFloatingView layoutParams(ViewGroup.LayoutParams params) {
mLayoutParams = params;
if (mFloatingView != null) {
mFloatingView.setLayoutParams(params);
}
return this;
}
private void addViewToWindow(final View view) {
if (getContainer() == null) {
return;
}
getContainer().addView(view);
}
private FrameLayout getContainer() {
if (mContainer == null) {
return null;
}
return mContainer.get();
}
private FrameLayout.LayoutParams getParams() {
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.BOTTOM | Gravity.START;
params.setMargins(13, params.topMargin, params.rightMargin, 500);
return params;
}
private FrameLayout getActivityRoot(Activity activity) {
if (activity == null) {
return null;
}
try {
return (FrameLayout) activity.getWindow().getDecorView().findViewById(android.R.id.content);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private Handler handler = new Handler(Looper.getMainLooper());
private Runnable action = new Runnable() {
@Override
public void run() {
NotifyFloatingView.get().remove();
}
};
}

View File

@@ -0,0 +1,116 @@
package com.xscm.moduleutil.widget.img;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Point;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.viewpager.widget.ViewPager;
import com.hjq.toast.ToastUtils;
import com.xscm.moduleutil.R;
import java.util.List;
/**
* 看大图
*/
public class FullScreenUtil {
public static void showFullScreenDialog(Context context, final int pos, final List<String> imgList) {
final Dialog dialog = new Dialog(context, R.style.big_pic_dialog);
//设置是否允许Dialog可以被点击取消,也会阻止Back键
dialog.setCancelable(true);
dialog.setCanceledOnTouchOutside(true);
Window window = dialog.getWindow();
window.setGravity(Gravity.CENTER);
//获取Dialog窗体的根容器
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
ViewGroup root = (ViewGroup) dialog.getWindow().getDecorView().findViewById(android.R.id.content);
//设置窗口大小为屏幕大小item_img_pv
WindowManager wm = (WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
Point screenSize = new Point();
wm.getDefaultDisplay().getSize(screenSize);
root.setLayoutParams(new LinearLayout.LayoutParams(screenSize.x, screenSize.y));
// 获取自定义布局,并设置给Dialog
View view = inflater.inflate(R.layout.pop_photo_vp, root, false);
final ViewPager img_vp = view.findViewById(R.id.img_vp);
final TextView img_num_iv = view.findViewById(R.id.img_num_iv);
final ImageView img_down_iv = view.findViewById(R.id.img_down_iv);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
ImgVPAdapter vpAdapter = new ImgVPAdapter(context, imgList);
img_vp.setAdapter(vpAdapter);
img_vp.setCurrentItem(pos);
img_num_iv.setText((pos + 1) + "/" + imgList.size());
img_down_iv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//保存
/* if (imgList.get(pos) != null) {
ImageSaveUtils.saveImg(XQDetailActivity.this, imgList.get(pos));
}*/
}
});
img_vp.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
ToastUtils.show("保存成功");
return false;
}
});
img_vp.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(final int position) {
img_num_iv.setText((position + 1) + "/" + imgList.size());
img_down_iv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// if (imgList.get(position) != null) {
// ImageSaveUtils.saveImg(XQDetailActivity.this, imgList.get(position));
// }
}
});
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
vpAdapter.setAllClickListener(new ImgVPAdapter.AllClickListener() {
@Override
public void allclick(int pos) {
dialog.dismiss();
}
});
dialog.setContentView(view);
dialog.show();
}
}

View File

@@ -0,0 +1,99 @@
package com.xscm.moduleutil.widget.img;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.PagerAdapter;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.widget.ImageSaveUtils;
import java.util.List;
public class ImgVPAdapter extends PagerAdapter {
private Context context;
private List<String> paths;
public ImgVPAdapter(Context context, List<String> paths) {
this.context = context;
this.paths = paths;
}
@Override
public int getCount() {
return paths.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, final int position) {
ImageView iv_img = (ImageView) LayoutInflater.from(context).inflate(R.layout.item_img_pv, null);
// iv_img.setScaleType(ImageView.ScaleType.CENTER);
Glide.with(context).load( paths.get(position)).error(R.mipmap.default_image).into(iv_img);
iv_img.setScaleType(ImageView.ScaleType.CENTER);
iv_img.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (allClickListener != null) {
allClickListener.allclick(position);
}
}
});
iv_img.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
Glide.with(context)
.asBitmap()
.load(paths.get(position))
.into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
ImageSaveUtils.saveImg(context, resource);
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
Toast.makeText(context, "图片加载失败", Toast.LENGTH_SHORT).show();
}
});
return true;
}
});
container.addView(iv_img);
return iv_img;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
public interface AllClickListener {
void allclick(int pos);
}
public AllClickListener allClickListener;
public void setAllClickListener(AllClickListener allClickListener) {
this.allClickListener = allClickListener;
}
}

View File

@@ -0,0 +1,87 @@
package com.xscm.moduleutil.widget.pagerecyclerview;
import android.util.Log;
/**
* ProjectName: BubbleVoice
* Package: com.qpyy.room.widget.pagerecyclerview
* Description: Pager配置
* Author: 姚闻达
* CreateDate: 2021/1/13 21:00
* UpdateUser: 更新者
* UpdateDate: 2021/1/13 21:00
* UpdateRemark: 更新说明
* Version: 1.0
*/
public class PagerConfig {
private static final String TAG = "PagerGrid";
private static boolean sShowLog = false;
private static int sFlingThreshold = 1000; // Fling 阀值,滚动速度超过该阀值才会触发滚动
private static float sMillisecondsPreInch = 60f; // 每一个英寸滚动需要的微秒数,数值越大,速度越慢
/**
* 判断是否输出日志
*
* @return true 输出false 不输出
*/
public static boolean isShowLog() {
return sShowLog;
}
/**
* 设置是否输出日志
*
* @param showLog 是否输出
*/
public static void setShowLog(boolean showLog) {
sShowLog = showLog;
}
/**
* 获取当前滚动速度阀值
*
* @return 当前滚动速度阀值
*/
public static int getFlingThreshold() {
return sFlingThreshold;
}
/**
* 设置当前滚动速度阀值
*
* @param flingThreshold 滚动速度阀值
*/
public static void setFlingThreshold(int flingThreshold) {
sFlingThreshold = flingThreshold;
}
/**
* 获取滚动速度 英寸/微秒
*
* @return 英寸滚动速度
*/
public static float getMillisecondsPreInch() {
return sMillisecondsPreInch;
}
/**
* 设置像素滚动速度 英寸/微秒
*
* @param millisecondsPreInch 英寸滚动速度
*/
public static void setMillisecondsPreInch(float millisecondsPreInch) {
sMillisecondsPreInch = millisecondsPreInch;
}
//--- 日志 -------------------------------------------------------------------------------------
public static void Logi(String msg) {
if (!PagerConfig.isShowLog()) return;
Log.i(TAG, msg);
}
public static void Loge(String msg) {
if (!PagerConfig.isShowLog()) return;
Log.e(TAG, msg);
}
}

View File

@@ -0,0 +1,892 @@
package com.xscm.moduleutil.widget.pagerecyclerview;
import static android.view.View.MeasureSpec.EXACTLY;
import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
import static com.xscm.moduleutil.widget.pagerecyclerview.PagerConfig.Loge;
import static com.xscm.moduleutil.widget.pagerecyclerview.PagerConfig.Logi;
import android.annotation.SuppressLint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.RecyclerView;
/**
* ProjectName: BubbleVoice
* Package: com.qpyy.room.widget.pagerecyclerview
* Description: 分页的网格布局管理器
* Author: 姚闻达
* CreateDate: 2021/1/13 21:03
* UpdateUser: 更新者
* UpdateDate: 2021/1/13 21:03
* UpdateRemark: 更新说明
* Version: 1.0
*/
public class PagerGridLayoutManager extends RecyclerView.LayoutManager
implements RecyclerView.SmoothScroller.ScrollVectorProvider {
private static final String TAG = PagerGridLayoutManager.class.getSimpleName();
public static final int VERTICAL = 0; // 垂直滚动
public static final int HORIZONTAL = 1; // 水平滚动
@IntDef({VERTICAL, HORIZONTAL})
public @interface OrientationType {
} // 滚动类型
@OrientationType
private int mOrientation; // 默认水平滚动
private int mOffsetX = 0; // 水平滚动距离(偏移量)
private int mOffsetY = 0; // 垂直滚动距离(偏移量)
private int mRows; // 行数
private int mColumns; // 列数
private int mOnePageSize; // 一页的条目数量
private SparseArray<Rect> mItemFrames; // 条目的显示区域
private int mItemWidth = 0; // 条目宽度
private int mItemHeight = 0; // 条目高度
private int mWidthUsed = 0; // 已经使用空间用于测量View
private int mHeightUsed = 0; // 已经使用空间用于测量View
private int mMaxScrollX; // 最大允许滑动的宽度
private int mMaxScrollY; // 最大允许滑动的高度
private int mScrollState = SCROLL_STATE_IDLE; // 滚动状态
private boolean mAllowContinuousScroll = true; // 是否允许连续滚动
private RecyclerView mRecyclerView;
/**
* 构造函数
*
* @param rows 行数
* @param columns 列数
* @param orientation 方向
*/
public PagerGridLayoutManager(@IntRange(from = 1, to = 100) int rows,
@IntRange(from = 1, to = 100) int columns,
@OrientationType int orientation) {
mItemFrames = new SparseArray<>();
mOrientation = orientation;
mRows = rows;
mColumns = columns;
mOnePageSize = mRows * mColumns;
}
@Override
public void onAttachedToWindow(RecyclerView view) {
super.onAttachedToWindow(view);
mRecyclerView = view;
}
//--- 处理布局 ----------------------------------------------------------------------------------
/**
* 布局子View
*
* @param recycler Recycler
* @param state State
*/
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
Logi("Item onLayoutChildren");
Logi("Item onLayoutChildren isPreLayout = " + state.isPreLayout());
Logi("Item onLayoutChildren isMeasuring = " + state.isMeasuring());
Loge("Item onLayoutChildren state = " + state);
// 如果是 preLayout 则不重新布局
if (state.isPreLayout() || !state.didStructureChange()) {
return;
}
if (getItemCount() == 0) {
removeAndRecycleAllViews(recycler);
// 页面变化回调
setPageCount(0);
setPageIndex(0, false);
return;
} else {
setPageCount(getTotalPageCount());
setPageIndex(getPageIndexByOffset(), false);
}
// 计算页面数量
int mPageCount = getItemCount() / mOnePageSize;
if (getItemCount() % mOnePageSize != 0) {
mPageCount++;
}
// 计算可以滚动的最大数值,并对滚动距离进行修正
if (canScrollHorizontally()) {
mMaxScrollX = (mPageCount - 1) * getUsableWidth();
mMaxScrollY = 0;
if (mOffsetX > mMaxScrollX) {
mOffsetX = mMaxScrollX;
}
} else {
mMaxScrollX = 0;
mMaxScrollY = (mPageCount - 1) * getUsableHeight();
if (mOffsetY > mMaxScrollY) {
mOffsetY = mMaxScrollY;
}
}
// 接口回调
// setPageCount(mPageCount);
// setPageIndex(mCurrentPageIndex, false);
Logi("count = " + getItemCount());
if (mItemWidth <= 0) {
mItemWidth = getUsableWidth() / mColumns;
}
if (mItemHeight <= 0) {
mItemHeight = getUsableHeight() / mRows;
}
mWidthUsed = getUsableWidth() - mItemWidth;
mHeightUsed = getUsableHeight() - mItemHeight;
// 预存储两页的View显示区域
for (int i = 0; i < mOnePageSize * 2; i++) {
getItemFrameByPosition(i);
}
if (mOffsetX == 0 && mOffsetY == 0) {
// 预存储View
for (int i = 0; i < mOnePageSize; i++) {
if (i >= getItemCount()) break; // 防止数据过少时导致数组越界异常
View view = recycler.getViewForPosition(i);
addView(view);
measureChildWithMargins(view, mWidthUsed, mHeightUsed);
}
}
// 回收和填充布局
recycleAndFillItems(recycler, state, true);
}
/**
* 布局结束
*
* @param state State
*/
@Override
public void onLayoutCompleted(RecyclerView.State state) {
super.onLayoutCompleted(state);
if (state.isPreLayout()) return;
// 页面状态回调
setPageCount(getTotalPageCount());
setPageIndex(getPageIndexByOffset(), false);
}
/**
* 回收和填充布局
*
* @param recycler Recycler
* @param state State
* @param isStart 是否从头开始用于控制View遍历方向true 为从头到尾false 为从尾到头
*/
@SuppressLint("CheckResult")
private void recycleAndFillItems(RecyclerView.Recycler recycler, RecyclerView.State state,
boolean isStart) {
if (state.isPreLayout()) {
return;
}
Logi("mOffsetX = " + mOffsetX);
Logi("mOffsetY = " + mOffsetY);
// 计算显示区域区前后多存储一列或则一行
Rect displayRect = new Rect(mOffsetX - mItemWidth, mOffsetY - mItemHeight,
getUsableWidth() + mOffsetX + mItemWidth, getUsableHeight() + mOffsetY + mItemHeight);
// 对显显示区域进行修正(计算当前显示区域和最大显示区域对交集)
displayRect.intersect(0, 0, mMaxScrollX + getUsableWidth(), mMaxScrollY + getUsableHeight());
Loge("displayRect = " + displayRect.toString());
int startPos = 0; // 获取第一个条目的Pos
int pageIndex = getPageIndexByOffset();
startPos = pageIndex * mOnePageSize;
Logi("startPos = " + startPos);
startPos = startPos - mOnePageSize * 2;
if (startPos < 0) {
startPos = 0;
}
int stopPos = startPos + mOnePageSize * 4;
if (stopPos > getItemCount()) {
stopPos = getItemCount();
}
Loge("startPos = " + startPos);
Loge("stopPos = " + stopPos);
detachAndScrapAttachedViews(recycler); // 移除所有View
if (isStart) {
for (int i = startPos; i < stopPos; i++) {
addOrRemove(recycler, displayRect, i);
}
} else {
for (int i = stopPos - 1; i >= startPos; i--) {
addOrRemove(recycler, displayRect, i);
}
}
Loge("child count = " + getChildCount());
}
/**
* 添加或者移除条目
*
* @param recycler RecyclerView
* @param displayRect 显示区域
* @param i 条目下标
*/
private void addOrRemove(RecyclerView.Recycler recycler, Rect displayRect, int i) {
View child = recycler.getViewForPosition(i);
Rect rect = getItemFrameByPosition(i);
if (!Rect.intersects(displayRect, rect)) {
removeAndRecycleView(child, recycler); // 回收入暂存区
} else {
addView(child);
measureChildWithMargins(child, mWidthUsed, mHeightUsed);
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
layoutDecorated(child,
rect.left - mOffsetX + lp.leftMargin + getPaddingLeft(),
rect.top - mOffsetY + lp.topMargin + getPaddingTop(),
rect.right - mOffsetX - lp.rightMargin + getPaddingLeft(),
rect.bottom - mOffsetY - lp.bottomMargin + getPaddingTop());
}
}
//--- 处理滚动 ----------------------------------------------------------------------------------
/**
* 水平滚动
*
* @param dx 滚动距离
* @param recycler 回收器
* @param state 滚动状态
* @return 实际滚动距离
*/
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State
state) {
int newX = mOffsetX + dx;
int result = dx;
if (newX > mMaxScrollX) {
result = mMaxScrollX - mOffsetX;
} else if (newX < 0) {
result = 0 - mOffsetX;
}
mOffsetX += result;
setPageIndex(getPageIndexByOffset(), true);
offsetChildrenHorizontal(-result);
if (result > 0) {
recycleAndFillItems(recycler, state, true);
} else {
recycleAndFillItems(recycler, state, false);
}
return result;
}
/**
* 垂直滚动
*
* @param dy 滚动距离
* @param recycler 回收器
* @param state 滚动状态
* @return 实际滚动距离
*/
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State
state) {
int newY = mOffsetY + dy;
int result = dy;
if (newY > mMaxScrollY) {
result = mMaxScrollY - mOffsetY;
} else if (newY < 0) {
result = 0 - mOffsetY;
}
mOffsetY += result;
setPageIndex(getPageIndexByOffset(), true);
offsetChildrenVertical(-result);
if (result > 0) {
recycleAndFillItems(recycler, state, true);
} else {
recycleAndFillItems(recycler, state, false);
}
return result;
}
/**
* 监听滚动状态,滚动结束后通知当前选中的页面
*
* @param state 滚动状态
*/
@Override
public void onScrollStateChanged(int state) {
Logi("onScrollStateChanged = " + state);
mScrollState = state;
super.onScrollStateChanged(state);
if (state == SCROLL_STATE_IDLE) {
setPageIndex(getPageIndexByOffset(), false);
}
}
//--- 私有方法 ----------------------------------------------------------------------------------
/**
* 获取条目显示区域
*
* @param pos 位置下标
* @return 显示区域
*/
private Rect getItemFrameByPosition(int pos) {
Rect rect = mItemFrames.get(pos);
if (null == rect) {
rect = new Rect();
// 计算显示区域 Rect
// 1. 获取当前View所在页数
int page = pos / mOnePageSize;
// 2. 计算当前页数左上角的总偏移量
int offsetX = 0;
int offsetY = 0;
if (canScrollHorizontally()) {
offsetX += getUsableWidth() * page;
} else {
offsetY += getUsableHeight() * page;
}
// 3. 根据在当前页面中的位置确定具体偏移量
int pagePos = pos % mOnePageSize; // 在当前页面中是第几个
int row = pagePos / mColumns; // 获取所在行
int col = pagePos - (row * mColumns); // 获取所在列
offsetX += col * mItemWidth;
offsetY += row * mItemHeight;
// 状态输出,用于调试
Logi("pagePos = " + pagePos);
Logi("行 = " + row);
Logi("列 = " + col);
Logi("offsetX = " + offsetX);
Logi("offsetY = " + offsetY);
rect.left = offsetX;
rect.top = offsetY;
rect.right = offsetX + mItemWidth;
rect.bottom = offsetY + mItemHeight;
// 存储
mItemFrames.put(pos, rect);
}
return rect;
}
/**
* 获取可用的宽度
*
* @return 宽度 - padding
*/
private int getUsableWidth() {
return getWidth() - getPaddingLeft() - getPaddingRight();
}
/**
* 获取可用的高度
*
* @return 高度 - padding
*/
private int getUsableHeight() {
return getHeight() - getPaddingTop() - getPaddingBottom();
}
//--- 页面相关(私有) -----------------------------------------------------------------------------
/**
* 获取总页数
*/
private int getTotalPageCount() {
if (getItemCount() <= 0) return 0;
int totalCount = getItemCount() / mOnePageSize;
if (getItemCount() % mOnePageSize != 0) {
totalCount++;
}
return totalCount;
}
/**
* 根据pos获取该View所在的页面
*
* @param pos position
* @return 页面的页码
*/
private int getPageIndexByPos(int pos) {
return pos / mOnePageSize;
}
/**
* 根据 offset 获取页面Index
*
* @return 页面 Index
*/
private int getPageIndexByOffset() {
int pageIndex;
if (canScrollVertically()) {
int pageHeight = getUsableHeight();
if (mOffsetY <= 0 || pageHeight <= 0) {
pageIndex = 0;
} else {
pageIndex = mOffsetY / pageHeight;
if (mOffsetY % pageHeight > pageHeight / 2) {
pageIndex++;
}
}
} else {
int pageWidth = getUsableWidth();
if (mOffsetX <= 0 || pageWidth <= 0) {
pageIndex = 0;
} else {
pageIndex = mOffsetX / pageWidth;
if (mOffsetX % pageWidth > pageWidth / 2) {
pageIndex++;
}
}
}
Logi("getPageIndexByOffset pageIndex = " + pageIndex);
return pageIndex;
}
//--- 公开方法 ----------------------------------------------------------------------------------
/**
* 创建默认布局参数
*
* @return 默认布局参数
*/
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
/**
* 处理测量逻辑
*
* @param recycler RecyclerView
* @param state 状态
* @param widthMeasureSpec 宽度属性
* @param heightMeasureSpec 高估属性
*/
@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(recycler, state, widthMeasureSpec, heightMeasureSpec);
int widthsize = View.MeasureSpec.getSize(widthMeasureSpec); //取出宽度的确切数值
int widthmode = View.MeasureSpec.getMode(widthMeasureSpec); //取出宽度的测量模式
int heightsize = View.MeasureSpec.getSize(heightMeasureSpec); //取出高度的确切数值
int heightmode = View.MeasureSpec.getMode(heightMeasureSpec); //取出高度的测量模式
// 将 wrap_content 转换为 match_parent
if (widthmode != EXACTLY && widthsize > 0) {
widthmode = EXACTLY;
}
if (heightmode != EXACTLY && heightsize > 0) {
heightmode = EXACTLY;
}
setMeasuredDimension(View.MeasureSpec.makeMeasureSpec(widthsize, widthmode),
View.MeasureSpec.makeMeasureSpec(heightsize, heightmode));
}
/**
* 是否可以水平滚动
*
* @return true 是false 不是。
*/
@Override
public boolean canScrollHorizontally() {
return mOrientation == HORIZONTAL;
}
/**
* 是否可以垂直滚动
*
* @return true 是false 不是。
*/
@Override
public boolean canScrollVertically() {
return mOrientation == VERTICAL;
}
/**
* 找到下一页第一个条目的位置
*
* @return 第一个搞条目的位置
*/
int findNextPageFirstPos() {
int page = mLastPageIndex;
page++;
if (page >= getTotalPageCount()) {
page = getTotalPageCount() - 1;
}
Loge("computeScrollVectorForPosition next = " + page);
return page * mOnePageSize;
}
/**
* 找到上一页的第一个条目的位置
*
* @return 第一个条目的位置
*/
int findPrePageFirstPos() {
// 在获取时由于前一页的View预加载出来了所以获取到的直接就是前一页
int page = mLastPageIndex;
page--;
Loge("computeScrollVectorForPosition pre = " + page);
if (page < 0) {
page = 0;
}
Loge("computeScrollVectorForPosition pre = " + page);
return page * mOnePageSize;
}
/**
* 获取当前 X 轴偏移量
*
* @return X 轴偏移量
*/
public int getOffsetX() {
return mOffsetX;
}
/**
* 获取当前 Y 轴偏移量
*
* @return Y 轴偏移量
*/
public int getOffsetY() {
return mOffsetY;
}
//--- 页面对齐 ----------------------------------------------------------------------------------
/**
* 计算到目标位置需要滚动的距离{@link RecyclerView.SmoothScroller.ScrollVectorProvider}
*
* @param targetPosition 目标控件
* @return 需要滚动的距离
*/
@Override
public PointF computeScrollVectorForPosition(int targetPosition) {
PointF vector = new PointF();
int[] pos = getSnapOffset(targetPosition);
vector.x = pos[0];
vector.y = pos[1];
return vector;
}
/**
* 获取偏移量(为PagerGridSnapHelper准备)
* 用于分页滚动,确定需要滚动的距离。
* {@link PagerGridSnapHelper}
*
* @param targetPosition 条目下标
*/
int[] getSnapOffset(int targetPosition) {
int[] offset = new int[2];
int[] pos = getPageLeftTopByPosition(targetPosition);
offset[0] = pos[0] - mOffsetX;
offset[1] = pos[1] - mOffsetY;
return offset;
}
/**
* 根据条目下标获取该条目所在页面的左上角位置
*
* @param pos 条目下标
* @return 左上角位置
*/
private int[] getPageLeftTopByPosition(int pos) {
int[] leftTop = new int[2];
int page = getPageIndexByPos(pos);
if (canScrollHorizontally()) {
leftTop[0] = page * getUsableWidth();
leftTop[1] = 0;
} else {
leftTop[0] = 0;
leftTop[1] = page * getUsableHeight();
}
return leftTop;
}
/**
* 获取需要对齐的View
*
* @return 需要对齐的View
*/
public View findSnapView() {
if (null != getFocusedChild()) {
return getFocusedChild();
}
if (getChildCount() <= 0) {
return null;
}
int targetPos = getPageIndexByOffset() * mOnePageSize; // 目标Pos
for (int i = 0; i < getChildCount(); i++) {
int childPos = getPosition(getChildAt(i));
if (childPos == targetPos) {
return getChildAt(i);
}
}
return getChildAt(0);
}
//--- 处理页码变化 -------------------------------------------------------------------------------
private boolean mChangeSelectInScrolling = true; // 是否在滚动过程中对页面变化回调
private int mLastPageCount = -1; // 上次页面总数
private int mLastPageIndex = -1; // 上次页面下标
/**
* 设置页面总数
*
* @param pageCount 页面总数
*/
private void setPageCount(int pageCount) {
if (pageCount >= 0) {
if (mPageListener != null && pageCount != mLastPageCount) {
mPageListener.onPageSizeChanged(pageCount);
}
mLastPageCount = pageCount;
}
}
/**
* 设置当前选中页面
*
* @param pageIndex 页面下标
* @param isScrolling 是否处于滚动状态
*/
private void setPageIndex(int pageIndex, boolean isScrolling) {
Loge("setPageIndex = " + pageIndex + ":" + isScrolling);
if (pageIndex == mLastPageIndex) return;
// 如果允许连续滚动,那么在滚动过程中就会更新页码记录
if (isAllowContinuousScroll()) {
mLastPageIndex = pageIndex;
} else {
// 否则,只有等滚动停下时才会更新页码记录
if (!isScrolling) {
mLastPageIndex = pageIndex;
}
}
if (isScrolling && !mChangeSelectInScrolling) return;
if (pageIndex >= 0) {
if (null != mPageListener) {
mPageListener.onPageSelect(pageIndex);
}
}
}
/**
* 设置是否在滚动状态更新选中页码
*
* @param changeSelectInScrolling true更新、false不更新
*/
public void setChangeSelectInScrolling(boolean changeSelectInScrolling) {
mChangeSelectInScrolling = changeSelectInScrolling;
}
/**
* 设置滚动方向
*
* @param orientation 滚动方向
* @return 最终的滚动方向
*/
@OrientationType
public int setOrientationType(@OrientationType int orientation) {
if (mOrientation == orientation || mScrollState != SCROLL_STATE_IDLE) return mOrientation;
mOrientation = orientation;
mItemFrames.clear();
int x = mOffsetX;
int y = mOffsetY;
mOffsetX = y / getUsableHeight() * getUsableWidth();
mOffsetY = x / getUsableWidth() * getUsableHeight();
int mx = mMaxScrollX;
int my = mMaxScrollY;
mMaxScrollX = my / getUsableHeight() * getUsableWidth();
mMaxScrollY = mx / getUsableWidth() * getUsableHeight();
return mOrientation;
}
//--- 滚动到指定位置 -----------------------------------------------------------------------------
@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
int targetPageIndex = getPageIndexByPos(position);
smoothScrollToPage(targetPageIndex);
}
/**
* 平滑滚动到上一页
*/
public void smoothPrePage() {
smoothScrollToPage(getPageIndexByOffset() - 1);
}
/**
* 平滑滚动到下一页
*/
public void smoothNextPage() {
smoothScrollToPage(getPageIndexByOffset() + 1);
}
/**
* 平滑滚动到指定页面
*
* @param pageIndex 页面下标
*/
public void smoothScrollToPage(int pageIndex) {
if (pageIndex < 0 || pageIndex >= mLastPageCount) {
Log.e(TAG, "pageIndex is outOfIndex, must in [0, " + mLastPageCount + ").");
return;
}
if (null == mRecyclerView) {
Log.e(TAG, "RecyclerView Not Found!");
return;
}
// 如果滚动到页面之间距离过大,先直接滚动到目标页面到临近页面,在使用 smoothScroll 最终滚动到目标
// 否则在滚动距离很大时,会导致滚动耗费的时间非常长
int currentPageIndex = getPageIndexByOffset();
if (Math.abs(pageIndex - currentPageIndex) > 3) {
if (pageIndex > currentPageIndex) {
scrollToPage(pageIndex - 3);
} else if (pageIndex < currentPageIndex) {
scrollToPage(pageIndex + 3);
}
}
// 具体执行滚动
LinearSmoothScroller smoothScroller = new PagerGridSmoothScroller(mRecyclerView);
int position = pageIndex * mOnePageSize;
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
//=== 直接滚动 ===
@Override
public void scrollToPosition(int position) {
int pageIndex = getPageIndexByPos(position);
scrollToPage(pageIndex);
}
/**
* 上一页
*/
public void prePage() {
scrollToPage(getPageIndexByOffset() - 1);
}
/**
* 下一页
*/
public void nextPage() {
scrollToPage(getPageIndexByOffset() + 1);
}
/**
* 滚动到指定页面
*
* @param pageIndex 页面下标
*/
public void scrollToPage(int pageIndex) {
if (pageIndex < 0 || pageIndex >= mLastPageCount) {
Log.e(TAG, "pageIndex = " + pageIndex + " is out of bounds, mast in [0, " + mLastPageCount + ")");
return;
}
if (null == mRecyclerView) {
Log.e(TAG, "RecyclerView Not Found!");
return;
}
int mTargetOffsetXBy = 0;
int mTargetOffsetYBy = 0;
if (canScrollVertically()) {
mTargetOffsetXBy = 0;
mTargetOffsetYBy = pageIndex * getUsableHeight() - mOffsetY;
} else {
mTargetOffsetXBy = pageIndex * getUsableWidth() - mOffsetX;
mTargetOffsetYBy = 0;
}
Loge("mTargetOffsetXBy = " + mTargetOffsetXBy);
Loge("mTargetOffsetYBy = " + mTargetOffsetYBy);
mRecyclerView.scrollBy(mTargetOffsetXBy, mTargetOffsetYBy);
setPageIndex(pageIndex, false);
}
/**
* 是否允许连续滚动,默认为允许
*
* @return true 允许, false 不允许
*/
public boolean isAllowContinuousScroll() {
return mAllowContinuousScroll;
}
/**
* 设置是否允许连续滚动
*
* @param allowContinuousScroll true 允许false 不允许
*/
public void setAllowContinuousScroll(boolean allowContinuousScroll) {
mAllowContinuousScroll = allowContinuousScroll;
}
//--- 对外接口 ----------------------------------------------------------------------------------
private PageListener mPageListener = null;
public void setPageListener(PageListener pageListener) {
mPageListener = pageListener;
}
public interface PageListener {
/**
* 页面总数量变化
*
* @param pageSize 页面总数
*/
void onPageSizeChanged(int pageSize);
/**
* 页面被选中
*
* @param pageIndex 选中的页面
*/
void onPageSelect(int pageIndex);
}
}

View File

@@ -0,0 +1,55 @@
package com.xscm.moduleutil.widget.pagerecyclerview;
import static com.xscm.moduleutil.widget.pagerecyclerview.PagerConfig.Logi;
import android.util.DisplayMetrics;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.RecyclerView;
/**
* ProjectName: BubbleVoice
* Package: com.qpyy.room.widget.pagerecyclerview
* Description: 用于处理平滑滚动,用于用户手指抬起后页面对齐或者 Fling 事件
* Author: 姚闻达
* CreateDate: 2021/1/13 21:08
* UpdateUser: 更新者
* UpdateDate: 2021/1/13 21:08
* UpdateRemark: 更新说明
* Version: 1.0
*/
public class PagerGridSmoothScroller extends LinearSmoothScroller {
private RecyclerView mRecyclerView;
public PagerGridSmoothScroller(@NonNull RecyclerView recyclerView) {
super(recyclerView.getContext());
mRecyclerView = recyclerView;
}
@Override
protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
RecyclerView.LayoutManager manager = mRecyclerView.getLayoutManager();
if (null == manager) return;
if (manager instanceof PagerGridLayoutManager) {
PagerGridLayoutManager layoutManager = (PagerGridLayoutManager) manager;
int pos = mRecyclerView.getChildAdapterPosition(targetView);
int[] snapDistances = layoutManager.getSnapOffset(pos);
final int dx = snapDistances[0];
final int dy = snapDistances[1];
Logi("dx = " + dx);
Logi("dy = " + dy);
final int time = calculateTimeForScrolling(Math.max(Math.abs(dx), Math.abs(dy)));
if (time > 0) {
action.update(dx, dy, time, mDecelerateInterpolator);
}
}
}
@Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return PagerConfig.getMillisecondsPreInch() / displayMetrics.densityDpi;
}
}

View File

@@ -0,0 +1,187 @@
package com.xscm.moduleutil.widget.pagerecyclerview;
import static com.xscm.moduleutil.widget.pagerecyclerview.PagerConfig.Loge;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SnapHelper;
/**
* ProjectName: BubbleVoice
* Package: com.qpyy.room.widget.pagerecyclerview
* Description: 分页居中工具 每次只滚动一个页面
* Author: 姚闻达
* CreateDate: 2021/1/13 21:10
* UpdateUser: 更新者
* UpdateDate: 2021/1/13 21:10
* UpdateRemark: 更新说明
* Version: 1.0
*/
public class PagerGridSnapHelper extends SnapHelper {
private RecyclerView mRecyclerView; // RecyclerView
/**
* 用于将滚动工具和 Recycler 绑定
*
* @param recyclerView RecyclerView
* @throws IllegalStateException 状态异常
*/
@Override
public void attachToRecyclerView(@Nullable RecyclerView recyclerView) throws
IllegalStateException {
super.attachToRecyclerView(recyclerView);
mRecyclerView = recyclerView;
}
/**
* 计算需要滚动的向量,用于页面自动回滚对齐
*
* @param layoutManager 布局管理器
* @param targetView 目标控件
* @return 需要滚动的距离
*/
@Nullable
@Override
public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,
@NonNull View targetView) {
int pos = layoutManager.getPosition(targetView);
Loge("findTargetSnapPosition, pos = " + pos);
int[] offset = new int[2];
if (layoutManager instanceof PagerGridLayoutManager) {
PagerGridLayoutManager manager = (PagerGridLayoutManager) layoutManager;
offset = manager.getSnapOffset(pos);
}
return offset;
}
/**
* 获得需要对齐的View对于分页布局来说就是页面第一个
*
* @param layoutManager 布局管理器
* @return 目标控件
*/
@Nullable
@Override
public View findSnapView(RecyclerView.LayoutManager layoutManager) {
if (layoutManager instanceof PagerGridLayoutManager) {
PagerGridLayoutManager manager = (PagerGridLayoutManager) layoutManager;
return manager.findSnapView();
}
return null;
}
/**
* 获取目标控件的位置下标
* (获取滚动后第一个View的下标)
*
* @param layoutManager 布局管理器
* @param velocityX X 轴滚动速率
* @param velocityY Y 轴滚动速率
* @return 目标控件的下标
*/
@Override
public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager,
int velocityX, int velocityY) {
int target = RecyclerView.NO_POSITION;
Loge("findTargetSnapPosition, velocityX = " + velocityX + ", velocityY" + velocityY);
if (null != layoutManager && layoutManager instanceof PagerGridLayoutManager) {
PagerGridLayoutManager manager = (PagerGridLayoutManager) layoutManager;
if (manager.canScrollHorizontally()) {
if (velocityX > PagerConfig.getFlingThreshold()) {
target = manager.findNextPageFirstPos();
} else if (velocityX < -PagerConfig.getFlingThreshold()) {
target = manager.findPrePageFirstPos();
}
} else if (manager.canScrollVertically()) {
if (velocityY > PagerConfig.getFlingThreshold()) {
target = manager.findNextPageFirstPos();
} else if (velocityY < -PagerConfig.getFlingThreshold()) {
target = manager.findPrePageFirstPos();
}
}
}
Loge("findTargetSnapPosition, target = " + target);
return target;
}
/**
* 一扔(快速滚动)
*
* @param velocityX X 轴滚动速率
* @param velocityY Y 轴滚动速率
* @return 是否消费该事件
*/
@Override
public boolean onFling(int velocityX, int velocityY) {
RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
if (layoutManager == null) {
return false;
}
RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
if (adapter == null) {
return false;
}
int minFlingVelocity = PagerConfig.getFlingThreshold();
Loge("minFlingVelocity = " + minFlingVelocity);
return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity)
&& snapFromFling(layoutManager, velocityX, velocityY);
}
/**
* 快速滚动的具体处理方案
*
* @param layoutManager 布局管理器
* @param velocityX X 轴滚动速率
* @param velocityY Y 轴滚动速率
* @return 是否消费该事件
*/
private boolean snapFromFling(@NonNull RecyclerView.LayoutManager layoutManager, int velocityX,
int velocityY) {
if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
return false;
}
RecyclerView.SmoothScroller smoothScroller = createSnapScroller(layoutManager);
if (smoothScroller == null) {
return false;
}
int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY);
if (targetPosition == RecyclerView.NO_POSITION) {
return false;
}
smoothScroller.setTargetPosition(targetPosition);
layoutManager.startSmoothScroll(smoothScroller);
return true;
}
/**
* 通过自定义 LinearSmoothScroller 来控制速度
*
* @param layoutManager 布局故哪里去
* @return 自定义 LinearSmoothScroller
*/
protected LinearSmoothScroller createSnapScroller(RecyclerView.LayoutManager layoutManager) {
if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
return null;
}
return new PagerGridSmoothScroller(mRecyclerView);
}
//--- 公开方法 ----------------------------------------------------------------------------------
/**
* 设置滚动阀值
*
* @param threshold 滚动阀值
*/
public void setFlingThreshold(int threshold) {
PagerConfig.setFlingThreshold(threshold);
}
}

View File

@@ -0,0 +1,730 @@
package com.xscm.moduleutil.widget.picker;
import static lombok.Lombok.checkNotNull;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.text.Layout;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.OverScroller;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.utils.logger.Logger;
import com.xscm.moduleutil.widget.Utils;
import java.lang.ref.WeakReference;
import java.util.List;
@SuppressWarnings("unused")
public class PickerView extends View {
private static final boolean DEBUG = false;
static final int DEFAULT_TEXT_SIZE_IN_SP = 14;
static final int DEFAULT_ITEM_HEIGHT_IN_DP = 24;
static final int DEFAULT_MAX_OFFSET_ITEM_COUNT = 3;
private int preferredMaxOffsetItemCount = DEFAULT_MAX_OFFSET_ITEM_COUNT;
private int selectedItemPosition;
private static final int DURATION_SHORT = 250;
private static final int DURATION_LONG = 1000;
private Adapter<? extends PickerItem> adapter;
private Paint textPaint;
private Rect textBounds = new Rect();
private GestureDetector gestureDetector;
private OverScroller scroller;
private boolean scrolling;
private boolean pendingJustify;
private boolean isScrollSuspendedByDownEvent;
private float actionDownY;
private float previousTouchedY;
private int previousScrollerY;
private int yOffset;
private int minY;
private int maxY;
private int maxOverScrollY;
private int touchSlop;
private int itemHeight;
private int textSize;
private int textColor = Color.BLACK;
private int selectTextColor = Color.BLACK;
private Typeface typeface;
private boolean isCyclic;
private boolean autoFitSize;
private boolean curved;
private Drawable selectedItemDrawable;
private int selectedMaskPadding;
private int[] DEFAULT_GRADIENT_COLORS = new int[]{0xcfffffff, 0x9fffffff, 0x5fffffff};
private int[] gradientColors = DEFAULT_GRADIENT_COLORS;
private GradientDrawable topMask;
private GradientDrawable bottomMask;
private Layout.Alignment textAlign = Layout.Alignment.ALIGN_CENTER;
private float radius;
private Camera camera;
private Matrix matrix;
public interface PickerItem {
String getText();
}
public PickerView(Context context) {
this(context, null);
}
public PickerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PickerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
gestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
int startScrollerY = yOffset - itemHeight * selectedItemPosition;
if (startScrollerY <= minY || startScrollerY >= maxY) {
justify(DURATION_LONG);
return true;
}
scroller.fling(
0, startScrollerY,
0, (int) velocityY,
0, 0,
minY,
maxY,
0, maxOverScrollY);
if (DEBUG) {
Logger.d("fling: " + startScrollerY + ", velocityY: " + velocityY);
}
previousScrollerY = scroller.getCurrY();
pendingJustify = true;
return true;
}
});
scroller = new OverScroller(getContext());
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
if (isInEditMode()) {
adapter = new Adapter<PickerItem>() {
@Override
public int getItemCount() {
return getMaxCount();
}
@Override
public PickerItem getItem(final int index) {
return new PickerItem() {
@Override
public String getText() {
return "Item " + index;
}
};
}
};
} else {
selectedMaskPadding = dp2px(18);
selectedItemDrawable = Utils.getDrawable(getContext(), R.drawable.top_defaults_view_pickerview_selected_item);
}
topMask = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, gradientColors);
bottomMask = new GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP, gradientColors);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PickerView);
preferredMaxOffsetItemCount = typedArray.getInt(R.styleable.PickerView_preferredMaxOffsetItemCount, DEFAULT_MAX_OFFSET_ITEM_COUNT);
if (preferredMaxOffsetItemCount <= 0)
preferredMaxOffsetItemCount = DEFAULT_MAX_OFFSET_ITEM_COUNT;
int defaultItemHeight = Utils.pixelOfDp(getContext(), DEFAULT_ITEM_HEIGHT_IN_DP);
itemHeight = typedArray.getDimensionPixelSize(R.styleable.PickerView_itemHeight, defaultItemHeight);
if (itemHeight <= 0) itemHeight = defaultItemHeight;
int defaultTextSize = Utils.pixelOfScaled(getContext(), DEFAULT_TEXT_SIZE_IN_SP);
textSize = typedArray.getDimensionPixelSize(R.styleable.PickerView_textSizec, defaultTextSize);
textColor = typedArray.getColor(R.styleable.PickerView_textColorc, Color.BLACK);
selectTextColor = typedArray.getColor(R.styleable.PickerView_selectTextColor, Color.BLACK);
isCyclic = typedArray.getBoolean(R.styleable.PickerView_isCyclic, false);
autoFitSize = typedArray.getBoolean(R.styleable.PickerView_autoFitSize, true);
curved = typedArray.getBoolean(R.styleable.PickerView_curved, false);
typedArray.recycle();
initPaints();
camera = new Camera();
matrix = new Matrix();
}
protected int dp2px(float dp) {
final float scale = getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
private void initPaints() {
textPaint = new Paint();
textPaint.setAntiAlias(true);
}
public Adapter getAdapter() {
return adapter;
}
public <T extends PickerItem> void setAdapter(final Adapter<T> adapter) {
checkNotNull(adapter, "adapter == null");
if (adapter.getItemCount() > Integer.MAX_VALUE / itemHeight) {
throw new RuntimeException("getItemCount() is too large, max count can be PickerView.getMaxCount()");
}
adapter.setPickerView(this);
this.adapter = adapter;
}
public interface OnItemSelectedListener<T extends PickerItem> {
void onItemSelected(T item);
}
public <T extends PickerItem> void setItems(final List<T> items, final OnItemSelectedListener<T> listener) {
final Adapter<T> simpleAdapter = new Adapter<T>() {
@Override
public int getItemCount() {
return items.size();
}
@Override
public T getItem(int index) {
return items.get(index);
}
};
setAdapter(simpleAdapter);
setOnSelectedItemChangedListener(new OnSelectedItemChangedListener() {
@Override
public void onSelectedItemChanged(PickerView pickerView, int previousPosition, int selectedItemPosition) {
if (listener != null) {
listener.onItemSelected(simpleAdapter.getItem(selectedItemPosition));
}
}
});
}
protected int getMaxCount() {
return Integer.MAX_VALUE / itemHeight;
}
public void notifyDataSetChanged() {
if (adapter != null) {
adapter.notifyDataSetChanged();
}
}
private void updateSelectedItem() {
float centerPosition = centerPosition();
int newSelectedItemPosition = (int) Math.floor(centerPosition);
notifySelectedItemChangedIfNeeded(newSelectedItemPosition, true);
}
@SuppressWarnings("WeakerAccess")
public abstract static class Adapter<T extends PickerItem> {
private WeakReference<PickerView> pickerViewRef;
private void setPickerView(PickerView pickerView) {
this.pickerViewRef = new WeakReference<>(pickerView);
}
public void notifyDataSetChanged() {
if (pickerViewRef != null) {
PickerView pickerView = pickerViewRef.get();
if (pickerView != null) {
pickerView.updateSelectedItem();
pickerView.computeScrollParams();
if (!pickerView.scroller.isFinished()) {
pickerView.scroller.forceFinished(true);
}
pickerView.justify(0);
pickerView.invalidate();
}
}
}
public abstract int getItemCount();
public abstract T getItem(int index);
public T getLastItem() {
return getItem(getItemCount() - 1);
}
public String getText(int index) {
if (getItem(index) == null) return "null";
return getItem(index).getText();
}
}
public void setPreferredMaxOffsetItemCount(int preferredMaxOffsetItemCount) {
this.preferredMaxOffsetItemCount = preferredMaxOffsetItemCount;
}
public void setItemHeight(int itemHeight) {
if (this.itemHeight != itemHeight) {
this.itemHeight = itemHeight;
invalidate();
requestLayout();
}
}
public void setTextSize(int textSize) {
if (this.textSize != textSize) {
this.textSize = textSize;
invalidate();
}
}
public void setTextColor(int textColor) {
if (this.textColor != textColor) {
this.textColor = textColor;
invalidate();
}
}
public void setTypeface(Typeface typeface) {
if (this.typeface != typeface) {
this.typeface = typeface;
textPaint.setTypeface(typeface);
invalidate();
}
}
public void setCyclic(boolean cyclic) {
if (this.isCyclic != cyclic) {
isCyclic = cyclic;
invalidate();
}
}
public void setAutoFitSize(boolean autoFitSize) {
if (this.autoFitSize != autoFitSize) {
this.autoFitSize = autoFitSize;
invalidate();
}
}
public void setCurved(boolean curved) {
if (this.curved != curved) {
this.curved = curved;
invalidate();
requestLayout();
}
}
public int getSelectedItemPosition() {
return clampItemPosition(selectedItemPosition);
}
public void setSelectedItemPosition(int selectedItemPosition) {
checkNotNull(adapter, "adapter must be set first");
notifySelectedItemChangedIfNeeded(selectedItemPosition);
invalidate();
}
public <T extends PickerItem> T getSelectedItem(Class<T> cls) {
checkNotNull(adapter, "adapter must be set first");
PickerItem item = adapter.getItem(getSelectedItemPosition());
if (!cls.isInstance(item)) {
return null;
}
return cls.cast(item);
}
public interface OnSelectedItemChangedListener {
void onSelectedItemChanged(PickerView pickerView, int previousPosition, int selectedItemPosition);
}
private OnSelectedItemChangedListener onSelectedItemChangedListener;
public void setOnSelectedItemChangedListener(OnSelectedItemChangedListener onSelectedItemChangedListener) {
this.onSelectedItemChangedListener = onSelectedItemChangedListener;
}
private void notifySelectedItemChangedIfNeeded(int newSelectedItemPosition) {
notifySelectedItemChangedIfNeeded(newSelectedItemPosition, false);
}
private void notifySelectedItemChangedIfNeeded(int newSelectedItemPosition, boolean forceNotify) {
int oldSelectedItemPosition = selectedItemPosition;
int clampedNewSelectedItemPosition = clampItemPosition(newSelectedItemPosition);
boolean changed = forceNotify;
if (isCyclic) {
if (selectedItemPosition != newSelectedItemPosition) {
selectedItemPosition = newSelectedItemPosition;
changed = true;
}
} else {
if (selectedItemPosition != clampedNewSelectedItemPosition) {
selectedItemPosition = clampedNewSelectedItemPosition;
changed = true;
}
}
if (changed && onSelectedItemChangedListener != null) {
onSelectedItemChangedListener.onSelectedItemChanged(this, oldSelectedItemPosition, clampedNewSelectedItemPosition);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
checkNotNull(adapter, "adapter == null");
int height = resolveSizeAndState(calculateIntrinsicHeight(), heightMeasureSpec, 0);
computeScrollParams();
setMeasuredDimension(widthMeasureSpec, height);
}
private int calculateIntrinsicHeight() {
if (curved) {
radius = itemHeight / (float) Math.sin(Math.PI / (3 + 2 * preferredMaxOffsetItemCount));
return (int) Math.ceil(2 * radius);
} else {
return (1 + 2 * preferredMaxOffsetItemCount) * itemHeight;
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
checkNotNull(adapter, "adapter == null");
if (adapter.getItemCount() == 0 || itemHeight == 0) return;
if (!isInEditMode()) {
selectedItemDrawable.setBounds(selectedMaskPadding, (getMeasuredHeight() - itemHeight) / 2, getMeasuredWidth() - selectedMaskPadding, (getMeasuredHeight() + itemHeight) / 2);
selectedItemDrawable.draw(canvas);
}
drawItems(canvas);
drawMasks(canvas);
}
private void drawItems(Canvas canvas) {
// 绘制选中项
float drawYOffset = yOffset + (getMeasuredHeight() - itemHeight) / 2;
int itemPosition = selectedItemPosition;
String text = adapter.getText(clampItemPosition(itemPosition));
drawText(canvas, text, drawYOffset, 0);
drawYOffset -= itemHeight;
// 绘制选中项上方的item
itemPosition = selectedItemPosition - 1;
while (drawYOffset + (itemHeight * (curved ? 2 : 1)) > 0) {
if (isPositionInvalid(itemPosition) && !isCyclic) {
break;
}
text = adapter.getText(clampItemPosition(itemPosition));
drawText(canvas, text, drawYOffset, -1);
drawYOffset -= itemHeight;
itemPosition--;
}
// 绘制选中项下方的item
drawYOffset = yOffset + (getMeasuredHeight() + itemHeight) / 2;
itemPosition = selectedItemPosition + 1;
while (drawYOffset - (itemHeight * (curved ? 1 : 0)) < getMeasuredHeight()) {
if (isPositionInvalid(itemPosition) && !isCyclic) {
break;
}
text = adapter.getText(clampItemPosition(itemPosition));
drawText(canvas, text, drawYOffset, 1);
drawYOffset += itemHeight;
itemPosition++;
}
}
private void drawMasks(Canvas canvas) {
topMask.setBounds(0, 0, getMeasuredWidth(), (getMeasuredHeight() - itemHeight) / 2);
topMask.draw(canvas);
bottomMask.setBounds(0, (getMeasuredHeight() + itemHeight) / 2, getMeasuredWidth(), getMeasuredHeight());
bottomMask.draw(canvas);
}
private void drawText(Canvas canvas, String text, float offset, int postion) {
textPaint.setTextSize(textSize);
if (postion == 0) {
textPaint.setColor(selectTextColor);
} else {
textPaint.setColor(textColor);
}
textPaint.getTextBounds(text, 0, text.length(), textBounds);
if (autoFitSize) {
while (getMeasuredWidth() < textBounds.width() && textPaint.getTextSize() > 16) {
textPaint.setTextSize(textPaint.getTextSize() - 1);
textPaint.getTextBounds(text, 0, text.length(), textBounds);
}
}
float textBottom = offset + (itemHeight + (textBounds.height())) / 2;
if (curved) {
// 根据当前item的offset换算得到对应的倾斜角度rotateRatio用于减小倾斜角度否则倾斜角度过大会导致视觉效果不佳
float rotateRatio = 2f / preferredMaxOffsetItemCount;
double radian = Math.atan((radius - (offset + itemHeight / 2)) / radius) * rotateRatio;
float degree = (float) (radian * 180 / Math.PI);
camera.save();
camera.rotateX(degree);
camera.translate(0, 0, -Math.abs((radius / (2 + preferredMaxOffsetItemCount)) * (float) Math.sin(radian)));
camera.getMatrix(matrix);
matrix.preTranslate(-getMeasuredWidth() / 2, -getMeasuredHeight() / 2);
matrix.postTranslate(getMeasuredWidth() / 2, getMeasuredHeight() / 2);
canvas.save();
canvas.concat(matrix);
}
if (textAlign == Layout.Alignment.ALIGN_CENTER) {
textPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(text, getMeasuredWidth() / 2, textBottom, textPaint);
} else if (textAlign == Layout.Alignment.ALIGN_OPPOSITE) {
textPaint.setTextAlign(Paint.Align.RIGHT);
canvas.drawText(text, getMeasuredWidth(), textBottom, textPaint);
} else {
textPaint.setTextAlign(Paint.Align.LEFT);
canvas.drawText(text, 0, textBottom, textPaint);
}
if (curved) {
canvas.restore();
camera.restore();
}
}
@Override
public boolean performClick() {
return super.performClick();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (gestureDetector.onTouchEvent(event)) {
invalidate();
return true;
}
float y = event.getY();
int dy;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
pendingJustify = false;
actionDownY = y;
previousTouchedY = y;
scrolling = false;
if (!scroller.isFinished()) {
scroller.forceFinished(true);
isScrollSuspendedByDownEvent = true;
}
break;
case MotionEvent.ACTION_MOVE:
if (!scrolling && Math.abs(y - actionDownY) <= touchSlop) {
break;
}
if (!scrolling) {
scrolling = true;
previousTouchedY = y;
break;
}
pendingJustify = false;
dy = (int) (y - previousTouchedY);
handleOffset(dy);
previousTouchedY = y;
break;
case MotionEvent.ACTION_UP:
if (!isScrollSuspendedByDownEvent && !scrolling && Math.abs(y - actionDownY) <= touchSlop) {
// 单击事件
performClick();
previousScrollerY = yOffset - itemHeight * selectedItemPosition;
// 最近的整数倍数的单元格高度
int centerItemTopY = (getMeasuredHeight() - itemHeight) / 2;
int centerItemBottomY = (getMeasuredHeight() + itemHeight) / 2;
if (y >= centerItemTopY && y <= centerItemBottomY) break;
int scrollOffset;
if (y < centerItemTopY) {
scrollOffset = ((int) y - centerItemBottomY) / itemHeight * itemHeight;
if (selectedItemPosition + scrollOffset / itemHeight < 0) break;
} else {
scrollOffset = ((int) y - centerItemTopY) / itemHeight * itemHeight;
if (selectedItemPosition + scrollOffset / itemHeight > adapter.getItemCount() - 1)
break;
}
scroller.startScroll(
0, previousScrollerY,
0, -scrollOffset,
DURATION_SHORT);
if (DEBUG) {
Logger.d("scrollOffset = %d", scrollOffset);
}
break;
}
scrolling = false;
isScrollSuspendedByDownEvent = false;
// align items
dy = (int) (y - previousTouchedY);
handleOffset(dy);
justify(DURATION_SHORT);
break;
default:
break;
}
invalidate();
return true;
}
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
int scrollerY = scroller.getCurrY();
if (DEBUG) {
Logger.d("scrollerY = %d, previousScrollerY = %d", scrollerY, previousScrollerY);
}
int dy = scrollerY - previousScrollerY;
handleOffset(dy);
previousScrollerY = scrollerY;
invalidate();
} else {
if (pendingJustify) {
justify(DURATION_SHORT);
}
}
}
private void computeScrollParams() {
if (isCyclic) {
minY = Integer.MIN_VALUE;
maxY = Integer.MAX_VALUE;
} else {
minY = -(adapter.getItemCount() - 1) * itemHeight;
maxY = 0;
}
maxOverScrollY = 2 * itemHeight;
}
private boolean isPositionInvalid(int itemPosition) {
return itemPosition < 0 || itemPosition >= adapter.getItemCount();
}
private int clampItemPosition(int itemPosition) {
if (adapter.getItemCount() == 0) return 0;
if (isCyclic) {
if (itemPosition < 0) {
itemPosition = itemPosition % adapter.getItemCount();
if (itemPosition != 0) itemPosition += adapter.getItemCount();
} else if (itemPosition >= adapter.getItemCount())
itemPosition %= adapter.getItemCount();
}
if (itemPosition < 0) itemPosition = 0;
else if (itemPosition >= adapter.getItemCount()) itemPosition = adapter.getItemCount() - 1;
return itemPosition;
}
// 中心线切割的单元位置
private float centerPosition() {
return selectedItemPosition + 0.5f - yOffset / itemHeight;
}
// 对齐item
private void justify(int duration) {
if (yOffset != 0) {
int scrollOffset = -yOffset;
if (selectedItemPosition != 0 && selectedItemPosition != adapter.getItemCount() - 1) {
if (yOffset > 0) {
if (yOffset > itemHeight / 3) {
scrollOffset = itemHeight - yOffset;
}
} else {
if (Math.abs(yOffset) > itemHeight / 3) {
scrollOffset = -(itemHeight + yOffset);
}
}
}
// 如果item数量为1总是回到0偏移
if (adapter.getItemCount() > 1) {
if (selectedItemPosition == 0 && yOffset < 0) {
if (Math.abs(yOffset) > itemHeight / 3) {
scrollOffset = -(itemHeight + yOffset);
}
}
if (selectedItemPosition == adapter.getItemCount() - 1 && yOffset > 0) {
if (yOffset > itemHeight / 3) {
scrollOffset = itemHeight - yOffset;
}
}
}
previousScrollerY = yOffset - itemHeight * selectedItemPosition;
scroller.startScroll(
0, previousScrollerY,
0, scrollOffset,
duration);
if (DEBUG) {
Logger.d("justify: duration = %d, yOffset = %d, scrollOffset = %d", duration, yOffset, scrollOffset);
}
invalidate();
}
pendingJustify = false;
}
private void handleOffset(int dy) {
if (DEBUG) {
Logger.d("yOffset = %d, dy = %d", yOffset, dy);
}
yOffset += dy;
if (Math.abs(yOffset) >= itemHeight) {
// 滚动到边界时
if (selectedItemPosition == 0 && dy >= 0 || selectedItemPosition == adapter.getItemCount() - 1 && dy <= 0) {
if (Math.abs(yOffset) > maxOverScrollY) {
yOffset = yOffset > 0 ? maxOverScrollY : -maxOverScrollY;
}
return;
}
int preSelection = selectedItemPosition;
notifySelectedItemChangedIfNeeded(selectedItemPosition - (yOffset / itemHeight));
yOffset -= (preSelection - selectedItemPosition) * itemHeight;
}
}
}

View File

@@ -0,0 +1,176 @@
package com.xscm.moduleutil.widget.picker;
import static com.xscm.moduleutil.utils.UtilConfig.getContext;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import com.luck.picture.lib.engine.CropFileEngine;
import com.luck.picture.lib.style.BottomNavBarStyle;
import com.luck.picture.lib.style.PictureSelectorStyle;
import com.luck.picture.lib.style.SelectMainStyle;
import com.luck.picture.lib.style.TitleBarStyle;
import com.luck.picture.lib.utils.DensityUtil;
import com.luck.picture.lib.utils.StyleUtils;
import com.xscm.moduleutil.R;
import com.yalantis.ucrop.UCrop;
import com.yalantis.ucrop.UCropImageEngine;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
/**
* @Author lxj$
* @Time 2025-8-5 21:50:02$ $
* @Description 图片选择器$
*/
public class PictureSelectorUtil {
public CropFileEngine ImageFileCropEngine;
private PictureSelectorStyle selectorStyle;
public PictureSelectorStyle syltPictureSelector() {
selectorStyle = new PictureSelectorStyle();
// 主体风格
SelectMainStyle numberSelectMainStyle = new SelectMainStyle();
numberSelectMainStyle.setSelectNumberStyle(true);
numberSelectMainStyle.setPreviewSelectNumberStyle(false);
numberSelectMainStyle.setPreviewDisplaySelectGallery(true);
numberSelectMainStyle.setSelectBackground(com.luck.picture.lib.R.drawable.ps_default_num_selector);
numberSelectMainStyle.setPreviewSelectBackground(com.luck.picture.lib.R.drawable.ps_preview_checkbox_selector);
numberSelectMainStyle.setSelectNormalBackgroundResources(com.luck.picture.lib.R.drawable.ps_select_complete_normal_bg);
numberSelectMainStyle.setSelectNormalTextColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_53575e));
numberSelectMainStyle.setSelectNormalText(com.luck.picture.lib.R.string.ps_send);
numberSelectMainStyle.setAdapterPreviewGalleryBackgroundResource(com.luck.picture.lib.R.drawable.ps_preview_gallery_bg);
numberSelectMainStyle.setAdapterPreviewGalleryItemSize(DensityUtil.dip2px(getContext(), 52));
numberSelectMainStyle.setPreviewSelectText(com.luck.picture.lib.R.string.ps_select);
numberSelectMainStyle.setPreviewSelectTextSize(14);
numberSelectMainStyle.setPreviewSelectTextColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_white));
numberSelectMainStyle.setPreviewSelectMarginRight(DensityUtil.dip2px(getContext(), 6));
numberSelectMainStyle.setSelectBackgroundResources(com.luck.picture.lib.R.drawable.ps_select_complete_bg);
numberSelectMainStyle.setSelectText(com.luck.picture.lib.R.string.ps_send_num);
numberSelectMainStyle.setSelectTextColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_white));
numberSelectMainStyle.setMainListBackgroundColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_black));
numberSelectMainStyle.setCompleteSelectRelativeTop(true);
numberSelectMainStyle.setPreviewSelectRelativeBottom(true);
numberSelectMainStyle.setAdapterItemIncludeEdge(false);
// 头部TitleBar 风格
TitleBarStyle numberTitleBarStyle = new TitleBarStyle();
numberTitleBarStyle.setHideCancelButton(true);
numberTitleBarStyle.setAlbumTitleRelativeLeft(true);
numberTitleBarStyle.setTitleAlbumBackgroundResource(com.luck.picture.lib.R.drawable.ps_album_bg);
numberTitleBarStyle.setTitleDrawableRightResource(com.luck.picture.lib.R.drawable.ps_ic_grey_arrow);
numberTitleBarStyle.setPreviewTitleLeftBackResource(com.luck.picture.lib.R.drawable.ps_ic_normal_back);
// 底部NavBar 风格
BottomNavBarStyle numberBottomNavBarStyle = new BottomNavBarStyle();
numberBottomNavBarStyle.setBottomPreviewNarBarBackgroundColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_half_grey));
numberBottomNavBarStyle.setBottomPreviewNormalText(com.luck.picture.lib.R.string.ps_preview);
numberBottomNavBarStyle.setBottomPreviewNormalTextColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_9b));
numberBottomNavBarStyle.setBottomPreviewNormalTextSize(16);
numberBottomNavBarStyle.setCompleteCountTips(false);
numberBottomNavBarStyle.setBottomPreviewSelectText(com.luck.picture.lib.R.string.ps_preview_num);
numberBottomNavBarStyle.setBottomPreviewSelectTextColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_white));
selectorStyle.setTitleBarStyle(numberTitleBarStyle);
selectorStyle.setBottomBarStyle(numberBottomNavBarStyle);
selectorStyle.setSelectMainStyle(numberSelectMainStyle);
return selectorStyle;
}
public class ImageFileCropEngine implements CropFileEngine {
@Override
public void onStartCrop(Fragment fragment, Uri srcUri, Uri destinationUri, ArrayList<String> dataSource, int requestCode) {
UCrop.Options options = buildOptions();
UCrop uCrop = UCrop.of(srcUri, destinationUri, dataSource);
uCrop.withOptions(options);
uCrop.setImageEngine(new UCropImageEngine() {
@Override
public void loadImage(Context context, String url, ImageView imageView) {
// if (!ImageLoaderUtils.assertValidRequest(context)) {
// return;
// }
Glide.with(context).load(url).override(180, 180).into(imageView);
}
@Override
public void loadImage(Context context, Uri url, int maxWidth, int maxHeight, OnCallbackListener<Bitmap> call) {
Glide.with(context).asBitmap().load(url).override(maxWidth, maxHeight).into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
if (call != null) {
call.onCall(resource);
}
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
if (call != null) {
call.onCall(null);
}
}
});
}
});
uCrop.start(fragment.requireActivity(), fragment, requestCode);
}
}
/**
* 配制UCrop可根据需求自我扩展
*
* @return
*/
public UCrop.Options buildOptions() {
UCrop.Options options = new UCrop.Options();
options.setHideBottomControls(true);//是否显示裁剪
options.setFreeStyleCropEnabled(true);//裁剪是否可以拖动
options.setShowCropFrame(true);//是否显示裁剪边缘
options.setShowCropGrid(true);//是否显示裁剪网格
options.setCircleDimmedLayer(false);//圆形头像裁剪
options.withAspectRatio(1, 1);
options.isCropDragSmoothToCenter(false);
options.isForbidCropGifWebp(false);
options.isForbidSkipMultipleCrop(true);
options.setMaxScaleMultiplier(100);
if (selectorStyle != null && selectorStyle.getSelectMainStyle().getStatusBarColor() != 0) {
SelectMainStyle mainStyle = selectorStyle.getSelectMainStyle();
boolean isDarkStatusBarBlack = mainStyle.isDarkStatusBarBlack();
int statusBarColor = mainStyle.getStatusBarColor();
options.isDarkStatusBarBlack(isDarkStatusBarBlack);
if (StyleUtils.checkStyleValidity(statusBarColor)) {
options.setStatusBarColor(statusBarColor);
options.setToolbarColor(statusBarColor);
} else {
options.setStatusBarColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_grey));
options.setToolbarColor(ContextCompat.getColor(getContext(),com.luck.picture.lib.R.color.ps_color_grey));
}
TitleBarStyle titleBarStyle = selectorStyle.getTitleBarStyle();
if (StyleUtils.checkStyleValidity(titleBarStyle.getTitleTextColor())) {
options.setToolbarWidgetColor(titleBarStyle.getTitleTextColor());
} else {
options.setToolbarWidgetColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_white));
}
} else {
options.setStatusBarColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_grey));
options.setToolbarColor(ContextCompat.getColor(getContext(), com.luck.picture.lib.R.color.ps_color_grey));
options.setToolbarWidgetColor(ContextCompat.getColor(getContext(),com.luck.picture.lib. R.color.ps_color_white));
}
return options;
}
}