修改名称。

This commit is contained in:
2025-11-07 09:22:39 +08:00
parent d9cf55b053
commit a8dcfbb6a7
2203 changed files with 3 additions and 4 deletions

View File

@@ -0,0 +1,63 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import androidx.appcompat.widget.AppCompatImageView;
import com.xscm.moduleutil.R;
/**
* com.xscm.moduleutil.widget
* qx
* 2025/10/24
* 自适应图片展示
*/
public class AdaptiveImageView extends AppCompatImageView {
public AdaptiveImageView(Context context) {
super(context);
}
public AdaptiveImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AdaptiveImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.d("DebugImageView", "=== onMeasure START ===");
Log.d("DebugImageView", "widthMeasureSpec: " + MeasureSpec.toString(widthMeasureSpec));
Log.d("DebugImageView", "heightMeasureSpec: " + MeasureSpec.toString(heightMeasureSpec));
// 先调用父类测量
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.d("DebugImageView", "After super - Width: " + getMeasuredWidth() + ", Height: " + getMeasuredHeight());
Drawable drawable = getDrawable();
if (drawable != null) {
Log.d("DebugImageView", "Drawable - Width: " + drawable.getIntrinsicWidth() + ", Height: " + drawable.getIntrinsicHeight());
}
// 强制设置为最小尺寸,确保不会太小
int measuredWidth = getMeasuredWidth();
int measuredHeight = getMeasuredHeight();
int minSize = (int) getContext().getResources().getDimension(R.dimen.dp_40);
if (measuredWidth < minSize) {
measuredWidth = minSize;
}
if (measuredHeight < minSize) {
measuredHeight = minSize;
}
Log.d("DebugImageView", "Final - Width: " + measuredWidth + ", Height: " + measuredHeight);
Log.d("DebugImageView", "=== onMeasure END ===");
setMeasuredDimension(measuredWidth, measuredHeight);
}
}

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);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,691 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.os.CountDownTimer;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.blankj.utilcode.util.LogUtils;
import com.opensource.svgaplayer.SVGAImageView;
import com.opensource.svgaplayer.SVGAParser;
import com.opensource.svgaplayer.SVGAVideoEntity;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.base.CommonAppContext;
import com.xscm.moduleutil.base.RoomRollModel;
import com.xscm.moduleutil.bean.FaceBean;
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.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.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;
private SVGAParser parser = new SVGAParser(getContext());
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
*/
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());
if (bean.getUser_id() != null && !bean.getUser_id().equals("0") && !bean.getUser_id().isEmpty()) {
if (CommonAppContext.getInstance().getOnlineMap() != null && CommonAppContext.getInstance().getOnlineMap().get(bean.getUser_id()) != null) {
iv_on_line.setVisibility(CommonAppContext.getInstance().getOnlineMap().get(bean.getUser_id()) == 1 ? GONE : VISIBLE);
} else {
iv_on_line.setVisibility(GONE);
}
} else {
iv_on_line.setVisibility(GONE);
}
parser.decodeFromAssets("ripple3695.svga", new SVGAParser.ParseCompletion() {
@Override
public void onComplete(@Nullable SVGAVideoEntity videoItem) {
if (videoItem != null) {
mIvRipple.setVideoItem(videoItem);
}
}
@Override
public void onError() {
Log.e("SVGA", "解析 ripple.svga 失败");
}
});
setPitData(bean);
//心动值
//显示心动
if ("1".equals(pitBean.getShutup())) {
mIvShutup.setVisibility(VISIBLE);
} else {
mIvShutup.setVisibility(GONE);
}
//自动调节麦位波纹
if (mIvRipple != null) {
mIvRipple.setScaleX(1.1f);
mIvRipple.setScaleY(1.1f);
}
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 (mIvRipple == null)
return;
if (userId.equals(pitBean.getUser_id())) {
if (soundLevel == 0) {
mIvRipple.pauseAnimation();
CommonAppContext.getInstance().onlineMap.put(pitBean.getUser_id(), 1);
mIvRipple.setVisibility(INVISIBLE);
} else {
mIvRipple.setVisibility(VISIBLE);
mIvRipple.startAnimation();
CommonAppContext.getInstance().onlineMap.put(pitBean.getUser_id(), 1);
iv_on_line.setVisibility(GONE);
}
}
}
@Override
public void onLocalSoundLevelUpdate(int volume) {
if (mIvRipple == null)
return;
if (volume == 0) {
mIvRipple.setVisibility(GONE);
} else {
mIvRipple.setVisibility(VISIBLE);
if (!mIvRipple.isAnimating()) {
mIvRipple.startAnimation();
CommonAppContext.getInstance().onlineMap.put(pitBean.getUser_id(), 1);
iv_on_line.setVisibility(GONE);
}
}
}
@Override
public void userJoined(int userId, int elapsd) {
}
@Override
public void userOffline(int userId, int reason) {
}
});
}
public void setCharm(String charm) {
mCharmView.setSex(pitBean.getSex(), pitBean.getUser_id(), charm, false);
}
/**
* 开始倒计时
*
* @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);
}
}
public void setOnlineStatus() {
if (pitBean.getUser_id() != null && !pitBean.getUser_id().equals("0") && !pitBean.getUser_id().isEmpty()) {
if (CommonAppContext.getInstance().getOnlineMap() != null && CommonAppContext.getInstance().getOnlineMap().get(pitBean.getUser_id()) != null) {
iv_on_line.setVisibility(CommonAppContext.getInstance().getOnlineMap().get(pitBean.getUser_id()) == 1 ? GONE : VISIBLE);
} else {
iv_on_line.setVisibility(GONE);
}
} else {
iv_on_line.setVisibility(GONE);
}
}
@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;
}
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.setSex(pitBean.getSex(), pitBean.getUser_id(), pitBean.getCharm(), false);
} else {
mCharmView.setVisibility(VISIBLE);
mCharmView.setSex(pitBean.getSex(), pitBean.getUser_id(), pitBean.getCharm(), false);
}
}
}
/**
* 清空心动值
*/
@Override
public void clearCardiac() {
if (mCharmView != null) {
pitBean.setCharm("0");
mCharmView.setSex(pitBean.getSex(), pitBean.getUser_id(), pitBean.getCharm(), true);
}
}
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) {
}
@Override
public void unRegister(Object obj) {
AgoraManager.getInstance(getContext()).removeSoundLevelListener(this);
}
/**
* 心动值显示开关
*
* @param roomBeckoningEvent
*/
@Subscribe(threadMode = ThreadMode.MAIN)
@Override
public void subscribeMessages(RoomBeckoningEvent roomBeckoningEvent) {
if (roomId != null && 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));
}
/**
* 是否主持
*
* @return
*/
@Override
public boolean isHost() {
return WHEAT_HOST.equals(pitNumber);
}
}

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,271 @@
package com.xscm.moduleutil.widget;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.RecyclerView;
/**
* @Author lxj$
* @Time $ 2025-9-4 21:04:34$
* @Description 自定义滚动辅助类$
*/
public class CenterScrollHelper {
private RecyclerView recyclerView;
public CenterScrollHelper(RecyclerView recyclerView) {
this.recyclerView = recyclerView;
}
/**
* 循环滚动指定圈数后停在目标位置
* @param targetPosition 目标位置
* @param circles 滚动圈数
* @param durationPerItem 每个item滚动的持续时间控制速度
* @param adapterSize 适配器总大小
*/
public void scrollWithCircles(int targetPosition, int circles, int durationPerItem, int adapterSize) {
scrollWithCircles(targetPosition, circles, durationPerItem, adapterSize, null);
}
/**
* 循环滚动指定圈数后停在目标位置(带回调)
* @param targetPosition 目标位置
* @param circles 滚动圈数
* @param durationPerItem 每个item滚动的持续时间控制速度
* @param adapterSize 适配器总大小
* @param onComplete 滚动完成回调
*/
public void scrollWithCircles(int targetPosition, int circles, int durationPerItem,
int adapterSize, Runnable onComplete) {
if (recyclerView.getLayoutManager() == null) {
if (onComplete != null) onComplete.run();
return;
}
// 计算总滚动位置(多圈+目标位置)
int totalItems = circles * adapterSize + targetPosition;
// 使用LinearSmoothScroller进行平滑滚动
LinearSmoothScroller smoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {
private static final float ACCELERATION = 0.5f; // 加速度
private static final float DECELERATION = 0.8f; // 减速度
@Override
protected int calculateTimeForScrolling(int dx) {
// 使用缓动函数计算时间,实现从慢到快再到慢的效果
// return calculateEasingTime(dx, durationPerItem);
// // 简单线性时间计算,确保滚动速度一致
// int screenWidth = recyclerView.getWidth();
// if (screenWidth <= 0) {
// return durationPerItem * 3;
// }
// int itemWidth = screenWidth / 3;
// int items = Math.max(1, dx / itemWidth);
// return durationPerItem * items;
// 使用缓动函数计算时间,实现从慢到快再到慢的效果
return calculateEasingTime(dx, durationPerItem);
}
@Override
protected void onStop() {
super.onStop();
// 滚动停止后确保目标位置居中
// scrollToCenter(targetPosition);
// 执行完成回调
if (onComplete != null) {
recyclerView.post(onComplete);
}
}
};
smoothScroller.setTargetPosition(totalItems);
recyclerView.getLayoutManager().startSmoothScroll(smoothScroller);
}
// 在 CenterScrollHelper 类中添加
public void reset() {
// 清理可能存在的回调或状态
if (recyclerView != null) {
recyclerView.stopScroll();
recyclerView.getHandler().removeCallbacksAndMessages(null);
}
}
/**
* 使用缓动函数计算滚动时间,实现加速减速效果
* @param dx 滚动距离
* @param baseDurationPerItem 基础时间
* @return 计算后的时间
*/
private int calculateEasingTime(int dx, int baseDurationPerItem) {
if (dx <= 0) return 0;
// 获取屏幕宽度作为参考
int screenWidth = recyclerView.getWidth();
if (screenWidth <= 0) {
return baseDurationPerItem * 3; // 默认值
}
// 计算item宽度假设每屏3个item
int itemWidth = screenWidth / 3;
// 计算滚动的item数量
int items = Math.max(1, dx / itemWidth);
// 使用easeInOutQuad缓动函数实现先慢中快后慢的效果
double progress = Math.min(1.0, (double) items / 100); // 假设100个item为完整过程
// easeInOutQuad缓动函数
double easeProgress;
if (progress < 0.5) {
easeProgress = 2 * progress * progress; // 先慢后快
} else {
easeProgress = 1 - Math.pow(-2 * progress + 2, 2) / 2; // 后慢
}
// 计算时间:开始慢(500ms),后来快(50ms)
int minDuration = 1000; // 最快速度
int maxDuration = 2000; // 最慢速度
int calculatedTime = (int) (maxDuration - (maxDuration - minDuration) * easeProgress);
return Math.max(minDuration, calculatedTime);
}
/**
* 将指定位置的item精确居中显示
* @param position 需要居中的位置(在循环列表中的实际位置)
* @param originalSize 原始数据大小
*/
public void scrollToCenter(int position, int originalSize) {
if (recyclerView.getLayoutManager() == null) return;
int screenWidth = recyclerView.getWidth();
if (screenWidth <= 0) return;
// 计算item宽度假设每个item等宽
int itemWidth = screenWidth / 3; // 每屏显示3个item
// 计算使item居中需要滚动的总距离
int targetScrollX = position * itemWidth - (screenWidth - itemWidth) / 2;
// 获取当前滚动位置
int currentScrollX = recyclerView.computeHorizontalScrollOffset();
// 计算需要滚动的距离
int scrollDistance = targetScrollX - currentScrollX;
// 执行滚动
recyclerView.smoothScrollBy(scrollDistance, 0);
}
/**
* 将指定位置的item精确居中显示简化版
* @param position 需要居中的位置
*/
public void scrollToCenter(int position) {
if (recyclerView.getLayoutManager() == null) return;
int screenWidth = recyclerView.getWidth();
if (screenWidth <= 0) return;
// 计算item宽度
int itemWidth = screenWidth / 3;
// 计算目标位置的左边缘
int targetLeft = position * itemWidth;
// 计算使item居中需要滚动到的位置
int targetScrollX = targetLeft - (screenWidth - itemWidth) / 2;
// 获取当前滚动位置
int currentScrollX = recyclerView.computeHorizontalScrollOffset();
// 计算需要滚动的距离
int scrollDistance = targetScrollX - currentScrollX;
// 如果距离很小,就不需要滚动了
if (Math.abs(scrollDistance) > 5) {
// 执行滚动
recyclerView.smoothScrollBy(scrollDistance, 0);
}
}
/**
* 将指定位置的item居中显示支持循环
*/
public void centerItem(int targetPosition, int adapterSize) {
if (recyclerView.getLayoutManager() == null) return;
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int screenWidth = recyclerView.getWidth();
if (screenWidth <= 0) return;
int itemWidth = screenWidth / 3; // 每个item占屏幕1/3
// 计算需要滚动的距离使item居中
// 这里需要根据实际的item宽度和屏幕宽度来计算居中位置
int targetScrollX = targetPosition * itemWidth - (screenWidth - itemWidth) / 2;
int currentScrollX = recyclerView.computeHorizontalScrollOffset();
int scrollDistance = targetScrollX - currentScrollX;
// 使用smoothScrollBy确保平滑滚动到居中位置
recyclerView.smoothScrollBy(scrollDistance, 0);
}
/**
* 使用更复杂的缓动函数计算滚动时间
* @param dx 滚动距离
* @param baseDurationPerItem 基础时间
* @return 计算后的时间
*/
private int calculateAdvancedEasingTime(int dx, int baseDurationPerItem) {
if (dx <= 0) return 0;
int screenWidth = recyclerView.getWidth();
if (screenWidth <= 0) {
return baseDurationPerItem * 3;
}
int itemWidth = screenWidth / 3;
int totalItems = dx / itemWidth;
// 分段处理:开始慢,中间快,结束慢
int totalTime = 0;
for (int i = 0; i < totalItems; i++) {
// 计算当前位置的进度 (0-1)
double progress = (double) i / Math.max(1, totalItems);
// 使用贝塞尔缓动函数:慢-快-慢
double easedProgress = bezierEasing(progress, 0.25, 0.1, 0.25, 1.0);
// 根据进度调整时间
int minTime = 30; // 最快时间
int maxTime = baseDurationPerItem * 2; // 最慢时间
int itemTime = (int) (maxTime - (maxTime - minTime) * easedProgress);
totalTime += itemTime;
}
return Math.max(100, totalTime); // 至少100ms
}
/**
* 贝塞尔缓动函数
* @param t 时间进度 (0-1)
* @param x1 控制点1 x
* @param y1 控制点1 y
* @param x2 控制点2 x
* @param y2 控制点2 y
* @return 缓动后的值
*/
private double bezierEasing(double t, double x1, double y1, double x2, double y2) {
// 简化的贝塞尔曲线计算
// 这里使用一个近似算法
double a = 1 - t;
return 3 * a * a * t * y1 + 3 * a * t * t * y2 + t * t * t;
}
}

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.index_icon_close);
}
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.qxcm.qxlive";
//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,419 @@
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 POST_FOLLOW_ROOM = "/api/User/follow_room";//收藏
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 REDPACKET_CREATE = "/api/Redpacket/create";//创建红包
public static final String ROOM_REDPACKET = "/api/Redpacket/roomRedPackets";//红包列表
public static final String GET_REDPACKET_DETAIL = "/api/Redpacket/detail";//红包详情
public static final String POST_GRAB = "/api/Redpacket/grab";//抢红包
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 POST_SEARCH_ALL = "/api/Search/search_list";//搜索, 全部搜索
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_BIND_DETAIL = "/api/Bind/bind_detail";//绑定详情
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 GET_NEW_CHARGE_GIFT = "/api/Activities/new_charge_gift";//新人好礼列表接口(弹框)
public static final String GET_ACTIVITIES_PERMISSION = "/api/Activities/activities_permission";//活动弹框权限
public static final String GET_DAY_DROP_GIFT = "/api/Activities/day_drop_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 POST_KEEP_XINTIAO = "/api/Xintiao/keep_xintiao";//心跳
public static final String END_FRIEND = "/api/Friend/end_friend";//点击结束 交友房
public static final String CREATE_RELATION = "/api/Friend/create_relation";//卡关系 (创建关系) 交友房
public static final String GET_BOX_GIFT_LIST = "/api/BlindBoxTurntable/get_gift_list";//获取活动礼物列表
public static final String POST_DRAW_GIFT_LIST = "/api/BlindBoxTurntable/draw_gift";//盲盒转盘抽奖
public static final String GET_MY_RECORD = "/api/BlindBoxTurntable/get_my_record";//获取我的抽奖记录
public static final String GET_ALL_RECORD = "/api/BlindBoxTurntable/get_all_record";//获取全服抽奖记录
public static final String GIFT_SEND = "/api/BlindBoxTurntable/gift_send";//礼物发放
public static final String GET_BOX_GIFT_LIST_XLH = "/api/BlindBoxTurntable/xlh";//巡乐会
public static final String POST_DRAW_GIFT_LIST_XLH = "/api/BlindBoxTurntable/xlh_draw_gift";//巡乐会抽奖
public static final String POST_XLH_ALL_RECORD = "/api/BlindBoxTurntable/get_xlh_all_record";//巡乐会榜单
public static final String Get_XH_RANking = "/api/BlindBoxTurntable/get_xlh_ranking";//巡乐会幸运
public static final String GET_XLH_MY_RECORD = "/api/BlindBoxTurntable/get_xlh_my_record";//巡乐会记录
public static final String POST_GIFT_ALL_CLEAR = "/api/Room/room_gift_all_clear";//背包礼物全清
public static final String POST_ROOM_USER_CHARM_LIST = "/api/Room/room_user_charm_list";//房间用户当前魅力值列表
public static final String GET_GIFT_PACK_LIST_COUNT = "/api/UserGiftPack/get_gift_pack_list_count";//背包礼物总价值
public static final String ROOM_USER_RECONNECT = "/api/Room/user_reconnect";//用户重连
public static final String USER_ROOM_BACK = "/api/Room/user_in_room_background";//用户在房间内切后台保留数据操作
public static final String ROOM_HOUR_BEAN = "/api/RoomHourRanking/room_hour_ranking";//房间小时榜
public static final String GET_EMOTION = "/api/RoomEmoji/type_list";//表情类型列表
public static final String GET_EMOTION_DEATILS = "/api/RoomEmoji/emoji_list";//表情列表
public static final String GET_TEMP_KEY = "/api/Upload/getTempKeys";//获取上传cos的临时秘钥
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,288 @@
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.view.ViewGroup;
import android.widget.LinearLayout;
import com.blankj.utilcode.util.ScreenUtils;
import com.xscm.moduleutil.utils.BarUtils;
/**
* com.xscm.moduleutil.widget
* qx
* 2025/10/27
*/
public class DropDayTaskView extends LinearLayout {
private int rightMargin = 0;
private float lastX, lastY;
private int screenWidth;
private int screenHeight; // 添加屏幕高度变量
public DropDayTaskView(Context context) {
super(context);
init();
}
public DropDayTaskView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DropDayTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
void init() {
// 初始化屏幕尺寸
screenWidth = ScreenUtils.getScreenWidth();
screenHeight = ScreenUtils.getScreenHeight();
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.7f * sh) - getHeight();
// 确保Y坐标不会超出屏幕范围
y = Math.max(0, Math.min(y, sh - getHeight()));
int x = sw - getWidth();
setTranslationX(x);
setTranslationY(y);
}
});
updateSize();
mStatusBarHeight = BarUtils.getStatusBarHeight();
}
/**
* 更新屏幕尺寸信息
*/
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;
}
}
boolean starDrap = false;
float X1;
float X2;
float Y1;
float Y2;
// 记录视图初始位置
private float originalX;
private float originalY;
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (starDrap) return true;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
X1 = event.getRawX();
Y1 = event.getRawY();
// 记录视图当前位置
originalX = getTranslationX();
originalY = getTranslationY();
break;
case MotionEvent.ACTION_MOVE:
X2 = event.getRawX();
Y2 = event.getRawY();
Action(X1, X2, Y1, Y2);
break;
}
return starDrap;
}
String TAG = "DropHourlView";
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;
}
}
private float mOriginalRawX;
private float mOriginalRawY;
private float mOriginalX;
private float mOriginalY;
protected int mScreenWidth;
private int mScreenHeight;
private int mStatusBarHeight;
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);
}
private void changeOriginalTouchParams(MotionEvent event) {
mOriginalX = getX();//getX()相对于控件X坐标的距离
mOriginalY = getY();
mOriginalRawX = event.getRawX();//getRawX()指控件在屏幕上的X坐标
mOriginalRawY = event.getRawY();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event == null) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
changeOriginalTouchParams(event);
updateSize(); // 添加这行确保尺寸是最新的
// ... 其他现有代码 ...
break;
case MotionEvent.ACTION_MOVE:
updateViewPosition(event); // 使用更新后的带边界检查的方法
// setBackgroundResource(R.drawable.bg_home_drop_view);
// 使用屏幕绝对坐标计算新位置
// float newX = originalX + (event.getRawX() - X1);
// float newY = originalY + (event.getRawY() - Y1);
//
// // 限制X和Y坐标在屏幕范围内
// newX = Math.max(0, Math.min(newX, screenWidth - getWidth()));
// newY = Math.max(0, Math.min(newY, screenHeight - getHeight()));
//
// setTranslationX(newX);
// setTranslationY(newY);
// X2 = event.getRawX();
break;
case MotionEvent.ACTION_UP:
starDrap = false;
int sw = ScreenUtils.getScreenWidth();
Log.i(TAG, "onTouchEvent: " + sw + "," + X2);
boolean isR = getTranslationX() + getWidth() / 2 >= sw / 2;//贴边方向
// 获取当前Y坐标
float currentY = getTranslationY();
// 创建X轴和Y轴的动画
ObjectAnimator animX = ObjectAnimator.ofFloat(this, "translationX", isR ? sw - getWidth() : 0f).setDuration(200);
// Y轴保持当前位置但确保在屏幕范围内
currentY = Math.max(0, Math.min(currentY, screenHeight - getHeight()));
ObjectAnimator animY = ObjectAnimator.ofFloat(this, "translationY", currentY).setDuration(200);
animX.start();
animY.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,285 @@
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.view.ViewGroup;
import android.widget.LinearLayout;
import com.blankj.utilcode.util.ScreenUtils;
import com.xscm.moduleutil.utils.BarUtils;
/**
* 描述:小时榜的显示视图
*/
public class DropHourlView extends LinearLayout {
private int rightMargin = 0;
private float lastX, lastY;
private int screenWidth;
private int screenHeight; // 添加屏幕高度变量
public DropHourlView(Context context) {
super(context);
init();
}
public DropHourlView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DropHourlView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
void init() {
// 初始化屏幕尺寸
screenWidth = ScreenUtils.getScreenWidth();
screenHeight = ScreenUtils.getScreenHeight();
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();
// 确保Y坐标不会超出屏幕范围
y = Math.max(0, Math.min(y, sh - getHeight()));
int x = sw - getWidth();
setTranslationX(x);
setTranslationY(y);
}
});
updateSize();
mStatusBarHeight = BarUtils.getStatusBarHeight();
}
/**
* 更新屏幕尺寸信息
*/
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;
}
}
boolean starDrap = false;
float X1;
float X2;
float Y1;
float Y2;
// 记录视图初始位置
private float originalX;
private float originalY;
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (starDrap) return true;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
X1 = event.getRawX();
Y1 = event.getRawY();
// 记录视图当前位置
originalX = getTranslationX();
originalY = getTranslationY();
break;
case MotionEvent.ACTION_MOVE:
X2 = event.getRawX();
Y2 = event.getRawY();
Action(X1, X2, Y1, Y2);
break;
}
return starDrap;
}
String TAG = "DropHourlView";
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;
}
}
private float mOriginalRawX;
private float mOriginalRawY;
private float mOriginalX;
private float mOriginalY;
protected int mScreenWidth;
private int mScreenHeight;
private int mStatusBarHeight;
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);
}
private void changeOriginalTouchParams(MotionEvent event) {
mOriginalX = getX();//getX()相对于控件X坐标的距离
mOriginalY = getY();
mOriginalRawX = event.getRawX();//getRawX()指控件在屏幕上的X坐标
mOriginalRawY = event.getRawY();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event == null) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
changeOriginalTouchParams(event);
updateSize(); // 添加这行确保尺寸是最新的
// ... 其他现有代码 ...
break;
case MotionEvent.ACTION_MOVE:
updateViewPosition(event); // 使用更新后的带边界检查的方法
// setBackgroundResource(R.drawable.bg_home_drop_view);
// 使用屏幕绝对坐标计算新位置
// float newX = originalX + (event.getRawX() - X1);
// float newY = originalY + (event.getRawY() - Y1);
//
// // 限制X和Y坐标在屏幕范围内
// newX = Math.max(0, Math.min(newX, screenWidth - getWidth()));
// newY = Math.max(0, Math.min(newY, screenHeight - getHeight()));
//
// setTranslationX(newX);
// setTranslationY(newY);
// X2 = event.getRawX();
break;
case MotionEvent.ACTION_UP:
starDrap = false;
int sw = ScreenUtils.getScreenWidth();
Log.i(TAG, "onTouchEvent: " + sw + "," + X2);
boolean isR = getTranslationX() + getWidth() / 2 >= sw / 2;//贴边方向
// 获取当前Y坐标
float currentY = getTranslationY();
// 创建X轴和Y轴的动画
ObjectAnimator animX = ObjectAnimator.ofFloat(this, "translationX", isR ? sw - getWidth() : 0f).setDuration(200);
// Y轴保持当前位置但确保在屏幕范围内
currentY = Math.max(0, Math.min(currentY, screenHeight - getHeight()));
ObjectAnimator animY = ObjectAnimator.ofFloat(this, "translationY", currentY).setDuration(200);
animX.start();
animY.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,286 @@
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.view.ViewGroup;
import android.widget.LinearLayout;
import com.blankj.utilcode.util.ScreenUtils;
import com.xscm.moduleutil.utils.BarUtils;
/**
* 这是红包入口悬浮框
*/
public class DropRedView extends LinearLayout {
private int rightMargin = 0;
private float lastX, lastY;
private int screenWidth;
private int screenHeight; // 添加屏幕高度变量
public DropRedView(Context context) {
super(context);
init();
}
public DropRedView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DropRedView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
void init() {
// 初始化屏幕尺寸
screenWidth = ScreenUtils.getScreenWidth();
screenHeight = ScreenUtils.getScreenHeight();
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.2f * sh) - getHeight();
// 确保Y坐标不会超出屏幕范围
y = Math.max(0, Math.min(y, sh - getHeight()));
// int x = sw - getWidth();//这是靠右边展示的
int x=20 ;//这里这只一小的数值,就是靠左展示的
setTranslationX(x);
setTranslationY(y);
}
});
updateSize();
mStatusBarHeight = BarUtils.getStatusBarHeight();
}
/**
* 更新屏幕尺寸信息
*/
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;
}
}
boolean starDrap = false;
float X1;
float X2;
float Y1;
float Y2;
// 记录视图初始位置
private float originalX;
private float originalY;
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (starDrap) return true;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
X1 = event.getRawX();
Y1 = event.getRawY();
// 记录视图当前位置
originalX = getTranslationX();
originalY = getTranslationY();
break;
case MotionEvent.ACTION_MOVE:
X2 = event.getRawX();
Y2 = event.getRawY();
Action(X1, X2, Y1, Y2);
break;
}
return starDrap;
}
String TAG = "DropHourlView";
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;
}
}
private float mOriginalRawX;
private float mOriginalRawY;
private float mOriginalX;
private float mOriginalY;
protected int mScreenWidth;
private int mScreenHeight;
private int mStatusBarHeight;
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);
}
private void changeOriginalTouchParams(MotionEvent event) {
mOriginalX = getX();//getX()相对于控件X坐标的距离
mOriginalY = getY();
mOriginalRawX = event.getRawX();//getRawX()指控件在屏幕上的X坐标
mOriginalRawY = event.getRawY();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event == null) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
changeOriginalTouchParams(event);
updateSize(); // 添加这行确保尺寸是最新的
// ... 其他现有代码 ...
break;
case MotionEvent.ACTION_MOVE:
updateViewPosition(event); // 使用更新后的带边界检查的方法
// setBackgroundResource(R.drawable.bg_home_drop_view);
// 使用屏幕绝对坐标计算新位置
// float newX = originalX + (event.getRawX() - X1);
// float newY = originalY + (event.getRawY() - Y1);
//
// // 限制X和Y坐标在屏幕范围内
// newX = Math.max(0, Math.min(newX, screenWidth - getWidth()));
// newY = Math.max(0, Math.min(newY, screenHeight - getHeight()));
//
// setTranslationX(newX);
// setTranslationY(newY);
// X2 = event.getRawX();
break;
case MotionEvent.ACTION_UP:
starDrap = false;
int sw = ScreenUtils.getScreenWidth();
Log.i(TAG, "onTouchEvent: " + sw + "," + X2);
boolean isR = getTranslationX() + getWidth() / 2 >= sw / 2;//贴边方向
// 获取当前Y坐标
float currentY = getTranslationY();
// 创建X轴和Y轴的动画
ObjectAnimator animX = ObjectAnimator.ofFloat(this, "translationX", isR ? sw - getWidth() : 0f).setDuration(200);
// Y轴保持当前位置但确保在屏幕范围内
currentY = Math.max(0, Math.min(currentY, screenHeight - getHeight()));
ObjectAnimator animY = ObjectAnimator.ofFloat(this, "translationY", currentY).setDuration(200);
animX.start();
animY.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,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,184 @@
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;
public class DropViewRoom extends LinearLayout {
private int rightMargin = 0;
private float lastX, lastY;
private int screenWidth;
public DropViewRoom(Context context) {
super(context);
init();
}
public DropViewRoom(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DropViewRoom(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()-100;
// 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()+10 : 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,51 @@
package com.xscm.moduleutil.widget;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
import androidx.recyclerview.widget.RecyclerView;
/**
* @Author lxj$
* @Time $ 2025-9-4 20:32:52$
* @Description 自定义的让在item平分屏幕宽度$
*/
public class EqualSpaceItemDecoration extends RecyclerView.ItemDecoration {
private int spanCount;
private int spacing;
public EqualSpaceItemDecoration(int spanCount, int spacing) {
this.spanCount = spanCount;
this.spacing = spacing;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
// 设置间距
outRect.left = spacing / 2;
outRect.right = spacing / 2;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
// 确保每个item宽度为屏幕的1/3
int childCount = parent.getChildCount();
int screenWidth = parent.getWidth();
int itemWidth = screenWidth / spanCount;
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
ViewGroup.LayoutParams params = child.getLayoutParams();
params.width = itemWidth;
child.setLayoutParams(params);
}
}
}

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,96 @@
package com.xscm.modulemain
import android.graphics.*
import android.graphics.drawable.Drawable
/**
* 这是设置图片设置拉伸区域
*/
class FakeNinePatchDrawable(
private val bitmap: Bitmap,
// 定义拉伸区域的坐标(相对图片的百分比,范围 0~1
private val left: Float, // 左边界(左侧不拉伸区域的宽度比例)
private val top: Float, // 上边界(上侧不拉伸区域的高度比例)
private val right: Float, // 右边界(右侧不拉伸区域的宽度比例)
private val bottom: Float // 下边界(下侧不拉伸区域的高度比例)
) : Drawable() {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val rect = Rect() // 绘制区域
override fun draw(canvas: Canvas) {
val bounds = bounds // 视图的实际尺寸(需要填充的区域)
val w = bitmap.width
val h = bitmap.height
// 计算分割点(像素值)
val splitLeft = (w * left).toInt()
val splitTop = (h * top).toInt()
val splitRight = (w * right).toInt()
val splitBottom = (h * bottom).toInt()
// 1. 绘制四个角(不拉伸)
// 左上
rect.set(0, 0, splitLeft, splitTop)
canvas.drawBitmap(bitmap, Rect(0, 0, splitLeft, splitTop), rect, paint)
// 右上
rect.set(bounds.width() - (w - splitRight), 0, bounds.width(), splitTop)
canvas.drawBitmap(bitmap, Rect(splitRight, 0, w, splitTop), rect, paint)
// 左下
rect.set(0, bounds.height() - (h - splitBottom), splitLeft, bounds.height())
canvas.drawBitmap(bitmap, Rect(0, splitBottom, splitLeft, h), rect, paint)
// 右下
rect.set(
bounds.width() - (w - splitRight),
bounds.height() - (h - splitBottom),
bounds.width(),
bounds.height()
)
canvas.drawBitmap(bitmap, Rect(splitRight, splitBottom, w, h), rect, paint)
// 2. 绘制四条边(单向拉伸)
// 上边(水平拉伸)
rect.set(splitLeft, 0, bounds.width() - (w - splitRight), splitTop)
canvas.drawBitmap(bitmap, Rect(splitLeft, 0, splitRight, splitTop), rect, paint)
// 下边(水平拉伸)
rect.set(
splitLeft,
bounds.height() - (h - splitBottom),
bounds.width() - (w - splitRight),
bounds.height()
)
canvas.drawBitmap(bitmap, Rect(splitLeft, splitBottom, splitRight, h), rect, paint)
// 左边(垂直拉伸)
rect.set(0, splitTop, splitLeft, bounds.height() - (h - splitBottom))
canvas.drawBitmap(bitmap, Rect(0, splitTop, splitLeft, splitBottom), rect, paint)
// 右边(垂直拉伸)
rect.set(
bounds.width() - (w - splitRight),
splitTop,
bounds.width(),
bounds.height() - (h - splitBottom)
)
canvas.drawBitmap(bitmap, Rect(splitRight, splitTop, w, splitBottom), rect, paint)
// 3. 绘制中间区域(双向拉伸)
rect.set(
splitLeft,
splitTop,
bounds.width() - (w - splitRight),
bounds.height() - (h - splitBottom)
)
canvas.drawBitmap(bitmap, Rect(splitLeft, splitTop, splitRight, splitBottom), rect, paint)
}
override fun setAlpha(alpha: Int) {
paint.alpha = alpha
invalidateSelf()
}
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
override fun getAlpha(): Int = paint.alpha
override fun setColorFilter(colorFilter: ColorFilter?) {
paint.colorFilter = colorFilter
invalidateSelf()
}
}

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,513 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.blankj.utilcode.util.LogUtils;
import com.opensource.svgaplayer.SVGACallback;
import com.opensource.svgaplayer.SVGADrawable;
import com.opensource.svgaplayer.SVGAImageView;
import com.opensource.svgaplayer.SVGAParser;
import com.opensource.svgaplayer.SVGAVideoEntity;
import com.tencent.qgame.animplayer.AnimConfig;
import com.tencent.qgame.animplayer.AnimView;
import com.tencent.qgame.animplayer.inter.IAnimListener;
import com.xscm.moduleutil.bean.GiftBean;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class GiftAnimView extends FrameLayout implements GiftSvgaView.OnAnimationListener {
private AnimView playerMp4View;
private GiftSvgaView svgaView;
private GiftBean playModel;
private boolean isLoadEffect = false;
private boolean isShow = true;//是否开启特效
private ReentrantLock lock = new ReentrantLock();
private List<String> giftArray = new ArrayList<>();
public ExecutorService queue = Executors.newSingleThreadExecutor();
private Context mContext;
private boolean isOnece;
// 添加带Context参数的构造函数
// 添加带Context和AttributeSet参数的构造函数解决XML inflate问题的关键
public GiftAnimView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
init();
}
// 添加带Context、AttributeSet和 defStyleAttr 参数的构造函数(更完整的实现)
public GiftAnimView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
init();
}
public void setQueue(ExecutorService queue) {
this.queue = queue;
}
public GiftAnimView(Context context) {
super(context);
this.mContext = context;
init();
}
private void init() {
isLoadEffect = false;
// 初始化SVGA视图
svgaView = new GiftSvgaView(getContext());
addView(svgaView);
playerMp4View = new AnimView(getContext());
addView(playerMp4View);
// 设置布局参数 - 在Android中通常使用LayoutParams
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
playerMp4View.setLoop(1);
playerMp4View.setLayoutParams(params);
playerMp4View.setAnimListener(new IAnimListener() {
@Override
public void onVideoDestroy() {
LogUtils.e("onVideoDestroy");
}
@Override
public void onVideoComplete() {
LogUtils.e("onVideoComplete");
post(() -> {
if (playerMp4View!=null) {
playerMp4View.setVisibility(View.GONE);
if (isOnece){
return;
}
// 通知播放完成
notifyPlaybackComplete();
loadStartSVGAPlayer();
}
});
}
@Override
public void onVideoRender(int i, @Nullable AnimConfig animConfig) {
// LogUtils.e("onVideoRender", i, animConfig);
}
@Override
public void onVideoStart() {
LogUtils.e("onVideoStart");
}
@Override
public boolean onVideoConfigReady(@NonNull AnimConfig animConfig) {
LogUtils.e("onVideoConfigReady", animConfig);
return true;
}
@Override
public void onFailed(int i, @Nullable String s) {
LogUtils.e("onFailed", s);
}
});
svgaView.setDidFinishedDisplay(this);
svgaView.setDidStartAnimation(this);
}
public void displayEffectView1(List<String> stringList){
queue.execute(new Runnable() {
@Override
public void run() {
/// 如果play_image不存在return
if (stringList == null || stringList.isEmpty()) {
return;
}
/// 锁住list
lock.lock();
try {
/// 添加礼物进list
giftArray.addAll(stringList);
/// 如果没有在加载则开始加载
if (!isLoadEffect) {
/// 更改加载状态标记
isLoadEffect = true;
loadStartSVGAPlayer();
}
} finally {
/// 解锁
lock.unlock();
}
}
});
}
/// 礼物特效进来 gift为礼物实体类
public void displayEffectView(final String gift) {
queue.execute(new Runnable() {
@Override
public void run() {
/// 如果play_image不存在return
if (gift == null || gift.isEmpty()) {
return;
}
/// 将play_image链接转为小写
String playImage = gift;
String pathExtension = getFileExtension(playImage).toLowerCase();
/// 判定礼物的后缀是否为svga或者mp4如果非这两种 则return
if (!("svga".equals(pathExtension) || "mp4".equals(pathExtension))) {
return;
}
/// 锁住list
lock.lock();
try {
/// 添加礼物进list
giftArray.add(gift);
/// 如果没有在加载则开始加载
if (!isLoadEffect) {
/// 更改加载状态标记
isLoadEffect = true;
loadStartSVGAPlayer();
}
} finally {
/// 解锁
lock.unlock();
}
}
});
}
public void previewEffectWith(String playImage){
this.isOnece=true;
if (playImage.endsWith("mp4")) {
downloadAndPlay(getContext(), playImage, new DownloadCallback() {
@Override
public void onSuccess(File file) {
post(() -> {
playerMp4View.setVisibility(View.VISIBLE);
svgaView.setVisibility(View.GONE);
playerMp4View.startPlay(file);
});
}
@Override
public void onFailure(Exception e) {
LogUtils.e("MP4下载或播放失败: " + e.getMessage());
// 处理失败情况,继续播放下一个
}
});
} else if (playImage.endsWith("svga")) {
// File file = downloadAndPlay(getContext(), playImage);
post(() -> {
playerMp4View.setVisibility(View.GONE);
svgaView.setVisibility(View.VISIBLE);
svgaView.loadSVGAPlayerWith(playImage, false);
});
}
}
public void openOrCloseEffectViewWith(boolean isShow) {
this.isShow = isShow;
removeSvgaQueueData();
playerMp4View.stopPlay();
playerMp4View.setVisibility(View.GONE);
setVisibility(isShow ? View.VISIBLE : View.GONE);
}
public void stopPlay() {
removeSvgaQueueData();
playerMp4View.stopPlay();
playerMp4View.setVisibility(View.GONE);
}
private void loadStartSVGAPlayer() {
if (!isShow) {
/// isshow 为是否开启特效 如果未开启则return
return;
}
String giftModel = null;
/// list加锁
lock.lock();
try {
/// 如果list长度大于0
if (!giftArray.isEmpty()) {
/// gift的实体类则赋值取list中的第一个数据
giftModel = giftArray.get(0);
/// 移除list的第一条数据
giftArray.remove(0);
isLoadEffect = true;
} else {
isLoadEffect = false;
}
} finally {
/// 解锁
lock.unlock();
}
if (isLoadEffect && giftModel != null && !TextUtils.isEmpty(giftModel)) {
String finalGiftModel = giftModel;
post(new Runnable() {
@Override
public void run() {
String playImage = finalGiftModel;
if (playImage.endsWith("mp4")) {
downloadAndPlay(getContext(), playImage, new DownloadCallback() {
@Override
public void onSuccess(File file) {
post(() -> {
playerMp4View.setVisibility(View.VISIBLE);
svgaView.setVisibility(View.GONE);
playerMp4View.startPlay(file);
});
}
@Override
public void onFailure(Exception e) {
LogUtils.e("MP4下载或播放失败: " + e.getMessage());
// 处理失败情况,继续播放下一个
post(() -> {
lock.lock();
try {
isLoadEffect = false;
} finally {
lock.unlock();
}
loadStartSVGAPlayer();
});
}
});
} else if (playImage.endsWith("svga")) {
// File file = downloadAndPlay(getContext(), playImage);
post(() -> {
playerMp4View.setVisibility(View.GONE);
svgaView.setVisibility(View.VISIBLE);
svgaView.loadSVGAPlayerWith(finalGiftModel, false);
});
} else {
lock.lock();
try {
isLoadEffect = false;
} finally {
lock.unlock();
}
loadStartSVGAPlayer();
// 直接播放缓存文件
}
}
});
}
}
public void downloadAndPlay(Context context, String playImage, DownloadCallback callback) {
String fileName = playImage.substring(playImage.lastIndexOf("/"));
String filePath = context.getCacheDir().getAbsolutePath() + fileName;
LogUtils.e("@@@@@filePath: " + filePath.toString());
File file = new File(filePath);
if (!file.exists()) {
LogUtils.e("无缓存");
// 使用OkHttp进行下载
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(playImage)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
LogUtils.e("MP4下载失败: " + e.toString());
// 在主线程中回调失败
post(() -> {
if (callback != null) {
callback.onFailure(e);
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
try (ResponseBody responseBody = response.body()) {
if (responseBody != null) {
File downloadedFile = new File(filePath);
FileOutputStream fos = new FileOutputStream(downloadedFile);
fos.write(responseBody.bytes());
fos.close();
// 在主线程中回调成功
post(() -> {
if (callback != null) {
callback.onSuccess(downloadedFile);
}
});
} else {
// 在主线程中回调失败
post(() -> {
if (callback != null) {
callback.onFailure(new IOException("Response body is null"));
}
});
}
} catch (Exception e) {
LogUtils.e("MP4文件保存失败: " + e.getMessage());
// 在主线程中回调失败
post(() -> {
if (callback != null) {
callback.onFailure(e);
}
});
}
} else {
LogUtils.e("MP4下载响应失败");
// 在主线程中回调失败
post(() -> {
if (callback != null) {
callback.onFailure(new IOException("Response not successful: " + response.code()));
}
});
}
}
});
} else {
// 文件已存在,直接回调成功
if (callback != null) {
callback.onSuccess(file);
}
}
}
// 添加回调接口
public interface DownloadCallback {
void onSuccess(File file);
void onFailure(Exception e);
}
public void destroyEffectView() {
removeSvgaQueueData();
svgaView.stopEffectSvgaPlay();
playerMp4View.stopPlay();
// 清理监听器
clearPlaybackCompleteListeners();
removeView(playerMp4View);
removeView(svgaView);
svgaView = null;
playerMp4View = null;
playModel = null;
if (queue != null) {
queue.shutdown();
queue = null;
}
}
private void removeSvgaQueueData() {
lock.lock();
try {
giftArray.clear();
isLoadEffect = false;
} finally {
lock.unlock();
}
}
private String getFileExtension(String url) {
if (url != null && url.contains(".")) {
return url.substring(url.lastIndexOf(".") + 1);
}
return "";
}
@Override
public void onStartAnimation(GiftSvgaView view) {
}
@Override
public void onFinishedDisplay(GiftSvgaView view) {
post(() -> {
if (svgaView!=null) {
svgaView.setVisibility(View.GONE);
}
if (isOnece){
return;
}
// 通知播放完成
notifyPlaybackComplete();
loadStartSVGAPlayer();
});
}
// 在 GiftAnimView 类中添加播放完成监听接口
public interface OnPlaybackCompleteListener {
void onPlaybackComplete();
}
// 在 GiftAnimView 类中添加成员变量
private List<OnPlaybackCompleteListener> playbackCompleteListeners = new ArrayList<>();
// 在 GiftAnimView 类中添加监听器管理方法
public void addOnPlaybackCompleteListener(OnPlaybackCompleteListener listener) {
if (listener != null && !playbackCompleteListeners.contains(listener)) {
playbackCompleteListeners.add(listener);
}
}
public void removeOnPlaybackCompleteListener(OnPlaybackCompleteListener listener) {
if (listener != null) {
playbackCompleteListeners.remove(listener);
}
}
public void clearPlaybackCompleteListeners() {
playbackCompleteListeners.clear();
}
// 触发播放完成回调的方法
private void notifyPlaybackComplete() {
for (OnPlaybackCompleteListener listener : new ArrayList<>(playbackCompleteListeners)) {
if (listener != null) {
listener.onPlaybackComplete();
}
}
}
}

View File

@@ -0,0 +1,306 @@
package com.xscm.moduleutil.widget;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import com.google.android.material.card.MaterialCardView;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.GiftBean;
import com.xscm.moduleutil.utils.ImageUtils;
/**
* 礼物卡片控件 - 支持选中效果和自定义样式
*/
public class GiftCardView extends FrameLayout {
private ImageView mIconImageView;
private TextView mNameTextView;
private TextView mCountTextView;
private TextView mResultTextView;
private boolean isSelected = false;
private Drawable selectedBackground;
private Drawable normalBackground;
// 添加GiftBean数据引用
private GiftBean giftBean;
private ObjectAnimator pulseAnimator;
public GiftCardView(Context context) {
this(context, null);
}
public GiftCardView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GiftCardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initViews();
initAttributes(attrs);
}
private void initViews() {
LayoutInflater.from(getContext()).inflate(R.layout.view_gift_card, this, true);
mIconImageView = findViewById(R.id.img_gift_icon);
mNameTextView = findViewById(R.id.tv_gift_name);
mCountTextView = findViewById(R.id.tv_gift_count);
mResultTextView= findViewById(R.id.result);
// 设置默认点击事件
setOnClickListener(v -> {
if (getOnGiftClickListener() != null) {
getOnGiftClickListener().onGiftClick(this);
}
});
}
private void initAttributes(AttributeSet attrs) {
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.GiftCardView);
try {
// 获取自定义属性
String name = typedArray.getString(R.styleable.GiftCardView_giftName);
String count = typedArray.getString(R.styleable.GiftCardView_giftCount);
int iconResId = typedArray.getResourceId(R.styleable.GiftCardView_giftIcon, 0);
// 设置默认值
if (name != null) {
setName(name);
}
if (count != null) {
setCount(count);
}
if (iconResId != 0) {
setIcon("");
}
// 获取选中状态相关属性
selectedBackground = typedArray.getDrawable(R.styleable.GiftCardView_selectedBackground);
normalBackground = typedArray.getDrawable(R.styleable.GiftCardView_normalBackground);
// 如果没有设置选中背景,则使用默认的选中效果
// if (selectedBackground == null) {
// selectedBackground = ContextCompat.getDrawable(getContext(), R.mipmap.tkzj_x);
// }
// if (normalBackground == null) {
// normalBackground = ContextCompat.getDrawable(getContext(), R.mipmap.tkzj_w);
// }
// 设置默认背景
if (normalBackground != null) {
setBackground(normalBackground);
}
} finally {
typedArray.recycle();
}
}
/**
* 绑定GiftBean数据
*/
public void bindGiftData(GiftBean giftBean) {
this.giftBean = giftBean;
if (giftBean != null) {
// 设置礼物图标
if (giftBean.getBase_image() != null && !giftBean.getBase_image().isEmpty()) {
setIcon(giftBean.getBase_image());
// 如果是资源ID
} else {
setIcon("");
}
// 设置礼物名称
setName(giftBean.getGift_name() != null ? giftBean.getGift_name() : "未知礼物");
// 设置礼物数量
setCount(giftBean.getGift_price() != null ? giftBean.getGift_price() : "0");
setmResultTextView(giftBean.getCount());
}
}
/**
* 获取绑定的GiftBean数据
*/
public GiftBean getGiftBean() {
return giftBean;
}
/**
* 设置礼物图标URL
*/
public void setIcon(String url) {
if (mIconImageView != null) {
if (url != null && !url.isEmpty()) {
ImageUtils.loadHeadCC(url, mIconImageView);
} else {
mIconImageView.setImageResource(R.mipmap.ic_launcher);
}
}
}
private android.animation.AnimatorSet animatorSet;
public void startPulseAnimationWithLayer() {
if (pulseAnimator != null && pulseAnimator.isRunning()) {
pulseAnimator.cancel();
}
pulseAnimator = ObjectAnimator.ofFloat(this, "scaleX", 0.9f, 1.1f);
pulseAnimator.setDuration(500);
pulseAnimator.setRepeatCount(ObjectAnimator.INFINITE);
pulseAnimator.setRepeatMode(ObjectAnimator.REVERSE);
ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(this, "scaleY", 0.9f, 1.1f);
scaleYAnimator.setDuration(500);
scaleYAnimator.setRepeatCount(ObjectAnimator.INFINITE);
scaleYAnimator.setRepeatMode(ObjectAnimator.REVERSE);
// 组合动画
animatorSet = new android.animation.AnimatorSet();
animatorSet.playTogether(pulseAnimator, scaleYAnimator);
animatorSet.start();
}
public void stopPulseAnimationWithLayer() {
if (pulseAnimator != null) {
pulseAnimator.cancel();
}
// 停止组合动画
if (animatorSet != null && animatorSet.isRunning()) {
animatorSet.cancel();
}
// 清除引用
animatorSet = null;
pulseAnimator = null;
// 重置缩放
this.setScaleX(1f);
this.setScaleY(1f);
// 强制重新绘制
this.invalidate();
}
/**
* 设置礼物名称
*/
public void setName(String name) {
if (mNameTextView != null) {
mNameTextView.setText(name);
}
}
/**
* 设置礼物数量
*/
public void setCount(String count) {
if (mCountTextView != null) {
mCountTextView.setText(count);
}
}
/**
* 设置选中状态
*/
public void setSelected(boolean selected) {
isSelected = selected;
if (selected) {
setBackground(selectedBackground);
// 可以添加额外的选中效果
// setElevation(8f); // 提高阴影
// 添加发光效果
// addGlowEffect();
} else {
setBackground(normalBackground);
// setElevation(4f); // 恢复默认阴影
// removeGlowEffect();
}
}
public void setmResultTextView(int num){
if (mResultTextView!=null) {
mResultTextView.setText("x" + num);
}
}
public void setVisibilitymResultTextView(boolean isVisible){
if (mResultTextView!=null) {
mResultTextView.setVisibility(isVisible?View.VISIBLE:View.GONE);
}
}
/**
* 添加发光效果
*/
private void addGlowEffect() {
// 使用LayerDrawable创建发光效果
GradientDrawable glow = new GradientDrawable();
glow.setColor(Color.TRANSPARENT);
glow.setStroke(8, Color.argb(150, 255, 255, 255));
glow.setCornerRadius(16f);
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{glow, getBackground()});
setBackground(layerDrawable);
}
/**
* 移除发光效果
*/
private void removeGlowEffect() {
setBackground(normalBackground);
}
/**
* 获取当前是否选中
*/
public boolean isSelected() {
return isSelected;
}
/**
* 切换选中状态
*/
public void toggleSelected() {
setSelected(!isSelected);
}
/**
* 设置点击监听器
*/
public void setOnGiftClickListener(OnGiftClickListener listener) {
this.onGiftClickListener = listener;
}
/**
* 获取点击监听器
*/
public OnGiftClickListener getOnGiftClickListener() {
return onGiftClickListener;
}
/**
* 点击监听器接口
*/
public interface OnGiftClickListener {
void onGiftClick(GiftCardView giftCardView);
}
private OnGiftClickListener onGiftClickListener;
}

View File

@@ -0,0 +1,285 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.opensource.svgaplayer.SVGACallback;
import com.opensource.svgaplayer.SVGADrawable;
import com.opensource.svgaplayer.SVGAImageView;
import com.opensource.svgaplayer.SVGAParser;
import com.opensource.svgaplayer.SVGAVideoEntity;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
public class GiftSvgaView extends FrameLayout implements SVGACallback {
private SVGAImageView player;
private SVGAParser parser;
private boolean isAutoPlay = true;
// 回调接口
public interface OnAnimationListener {
void onStartAnimation(GiftSvgaView view);
void onFinishedDisplay(GiftSvgaView view);
}
private OnAnimationListener didStartAnimation;
private OnAnimationListener didFinishedDisplay;
public GiftSvgaView(Context context) {
this(context, null);
}
public GiftSvgaView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GiftSvgaView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initializeData(true);
}
public GiftSvgaView(Context context, boolean isAutoPlay) {
super(context);
initializeData(isAutoPlay);
}
private void initializeData(boolean isAutoPlay) {
this.isAutoPlay = isAutoPlay;
setBackgroundColor(Color.TRANSPARENT); // clearColor
setClickable(false); // userInteractionEnabled = NO
initPlayer();
}
private void initPlayer() {
player = new SVGAImageView(getContext());
player.setBackgroundColor(Color.TRANSPARENT);
player.setScaleType(ImageView.ScaleType.CENTER_CROP); // UIViewContentModeScaleAspectFill
// 如果需要 ScaleAspectFit使用 ImageView.ScaleType.FIT_CENTER
// 设置布局参数 - 填满父视图
LayoutParams params = new LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT
);
addView(player, params);
// 初始化解析器
parser = new SVGAParser(getContext());
player.setCallback(this);
}
public void loadSVGAPlayerWith(String loadPath) {
loadSVGAPlayerWith(loadPath, false);
}
public void loadSVGAPlayerWith(String loadPath, boolean inBundle) {
loadSVGAPlayerWith(loadPath, inBundle, 1);
}
public void loadSVGAPlayerWith(String loadPath, boolean inBundle, int loop) {
if (loadPath == null || loadPath.isEmpty()) {
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(this);
}
return;
}
if (player == null) {
initPlayer();
}
stopEffectSvgaPlay();
player.setLoops(loop);
if (loadPath.startsWith("https:") || loadPath.startsWith("http:")) {
// URL加载
try {
parser.parse(new URL(loadPath), new SVGAParser.ParseCompletion() {
@Override
public void onComplete(@NotNull SVGAVideoEntity videoItem) {
SVGADrawable drawable = new SVGADrawable(videoItem);
player.setImageDrawable(drawable);
if (isAutoPlay) {
player.startAnimation();
if (didStartAnimation != null) {
didStartAnimation.onStartAnimation(GiftSvgaView.this);
}
}
}
@Override
public void onError() {
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(GiftSvgaView.this);
}
}
});
} catch (Exception e) {
e.printStackTrace();
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(this);
}
}
} else if (inBundle) {
// 从Assets加载
try {
parser.parse(loadPath, new SVGAParser.ParseCompletion() {
@Override
public void onComplete(@NotNull SVGAVideoEntity videoItem) {
SVGADrawable drawable = new SVGADrawable(videoItem);
player.setImageDrawable(drawable);
if (isAutoPlay) {
player.startAnimation();
if (didStartAnimation != null) {
didStartAnimation.onStartAnimation(GiftSvgaView.this);
}
}
}
@Override
public void onError() {
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(GiftSvgaView.this);
}
}
});
} catch (Exception e) {
e.printStackTrace();
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(this);
}
}
} else {
// 从文件路径加载
try {
File file = new File(loadPath);
if (!file.exists() || file.length() < 4) {
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(this);
}
return;
}
InputStream inputStream = new FileInputStream(file);
parser.parse(inputStream, loadPath, new SVGAParser.ParseCompletion() {
@Override
public void onComplete(@NotNull SVGAVideoEntity videoItem) {
SVGADrawable drawable = new SVGADrawable(videoItem);
player.setImageDrawable(drawable);
if (isAutoPlay) {
player.startAnimation();
if (didStartAnimation != null) {
didStartAnimation.onStartAnimation(GiftSvgaView.this);
}
}
}
@Override
public void onError() {
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(GiftSvgaView.this);
}
}
}, true);
} catch (IOException e) {
e.printStackTrace();
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(this);
}
}
}
}
// Public方法
public void startEffectSvgaPlay() {
if (player != null && player.getDrawable() != null) {
player.startAnimation();
}
}
public void pauseEffectSvgaPlay() {
if (player != null) {
player.pauseAnimation();
}
}
public void stopEffectSvgaPlay() {
if (player != null) {
player.stopAnimation();
player.clearAnimation();
player.setImageDrawable( null);
}
}
public void destroySvga() {
stopEffectSvgaPlay();
if (player != null) {
removeView(player);
player = null;
}
parser = null;
}
// SVGACallback接口实现
@Override
public void onPause() {
// 暂停回调
}
@Override
public void onFinished() {
if (didFinishedDisplay != null) {
didFinishedDisplay.onFinishedDisplay(this);
}
}
@Override
public void onRepeat() {
// 重复回调
}
@Override
public void onStep(int frame, double percentage) {
// 步骤回调
}
// Getter和Setter方法
public void setDidStartAnimation(OnAnimationListener listener) {
this.didStartAnimation = listener;
}
public void setDidFinishedDisplay(OnAnimationListener listener) {
this.didFinishedDisplay = listener;
if (player != null) {
player.setCallback(this);
}
}
public boolean isAutoPlay() {
return isAutoPlay;
}
public void setAutoPlay(boolean autoPlay) {
isAutoPlay = autoPlay;
}
public SVGAImageView getPlayer() {
return player;
}
public SVGAParser getParser() {
return parser;
}
}

View File

@@ -0,0 +1,42 @@
package com.xscm.moduleutil.widget;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import androidx.annotation.NonNull;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
public class GrayscaleTransformation extends BitmapTransformation {
private Float saturation;
public GrayscaleTransformation(float saturation) {
super();
this.saturation = saturation;
}
@Override
protected Bitmap transform(@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {
Bitmap result = pool.get(toTransform.getWidth(), toTransform.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(result);
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setSaturation(saturation); // 设置为灰度
Paint paint = new Paint();
paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
canvas.drawBitmap(toTransform, 0, 0, paint);
return result;
}
@Override
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
messageDigest.update("grayscale transformation".getBytes(StandardCharsets.UTF_8));
}
}

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,613 @@
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() {
// 添加视图是否已附加到窗口的检查
if (getWindowToken() == null) {
return true; // 视图尚未附加到窗口,稍后再试
}
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,130 @@
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.tencent.mm.opensdk.modelbiz.WXLaunchMiniProgram;
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 static void payWxMiniProgramWx(IWXAPI api, String path) {
if (!api.isWXAppInstalled()) {
ToastUtils.showShort("请安装微信");
return;
}
WXLaunchMiniProgram.Req req = new WXLaunchMiniProgram.Req();
req.userName = "gh_e64a1a89a0ad"; // 填小程序原始id
req.path = "pages/orderDetail/orderDetail?"+path; ////拉起小程序页面的可带参路径,不填默认拉起小程序首页,对于小游戏,可以只传入 query 部分,来实现传参效果,如:传入 "?foo=bar"。
// req.path="pages/orderDetail/orderDetail?appid=00344656&body=user_Recharge_to_chumeng_App&version=12&trxamt=1&notify_url=http://tapp.qxcms.com/api/payment/allinpayNotify&paytype=W06&randomstr=67e6671866f0e&cusid=5647950737575ZK&remark=1&reqsn=20250328529202885800&signtype=RSA&sign=O+DT6XMstf+0F8P3li/PM3lZGgam3fBqs2L6pKXUUVadd9kYiuIrbKxzrGVJ47uPWEIGV2W903nKJY+wqAnoKWvGTMMmuEsU+2gOWnDmp7X58Rw7eDDLjy0TGDxheIlMHwJmg0gOCiHGYoiJB61WQlyX4vvKZy9DOuepqnNPvC2ByeL2Vi8eRi1eqoA9PqmePzhFDz5l/EB1UOm8/XmXv4FKO7b/Im7M9cRNX3tXqbKURr5P7ohj42VF8DoYJYp7eAa3ql1GlYDJIbG+D7NGbPkp8AEKUrAShkMgNpLCnIcAGGlEhW1la8qn+/WVazH7CjUrJvqWcKMafSZcLxmMFA==";
req.miniprogramType = WXLaunchMiniProgram.Req.MINIPTOGRAM_TYPE_RELEASE;// 可选打开 开发版,体验版和正式版
api.sendReq(req);
}
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,273 @@
package com.xscm.moduleutil.widget;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.ImageView;
import android.widget.TextView;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.event.MqttBean;
import java.util.ArrayList;
import java.util.List;
public class QXGiftDriftView extends ViewGroup {
private static QXGiftDriftView instance;
private ImageView bgImageView;
private TextView titleLabel;
private ImageView giftImageView;
private TextView countLabel;
private boolean isPlaying=false;
private boolean isClose;
private List<MqttBean.ListBean> dataArray = new ArrayList<>();
private MqttBean.ListBean model;
private Context context;
private int screenWidth;
public static QXGiftDriftView getInstance(Context context) {
if (instance == null) {
instance = new QXGiftDriftView(context);
}
return instance;
}
private QXGiftDriftView(Context context) {
super(context);
this.context = context;
// 获取屏幕宽度
screenWidth = context.getResources().getDisplayMetrics().widthPixels;
// 初始化视图
initSubviews();
// 从SharedPreferences读取设置
SharedPreferences prefs = context.getSharedPreferences("AppPrefs", Context.MODE_PRIVATE);
isClose = prefs.getBoolean("kIsCloseDrifPop", false);
}
private void initSubviews() {
// 设置视图尺寸
int width = scaleWidth(316);
int height = scaleWidth(50);
setLayoutParams(new LayoutParams(width, height));
// 背景图片
bgImageView = new ImageView(context);
bgImageView.setImageResource(R.mipmap.gift_p_b);
addView(bgImageView);
// 标题标签
titleLabel = new TextView(context);
titleLabel.setTextSize(14);
titleLabel.setTextColor(Color.WHITE);
addView(titleLabel);
// 礼物图片
giftImageView = new ImageView(context);
giftImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
addView(giftImageView);
// 数量标签
countLabel = new TextView(context);
countLabel.setTextSize(14);
countLabel.setTextColor(Color.WHITE);
addView(countLabel);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// 布局子视图
int width = getWidth();
int height = getHeight();
// 背景图片填满整个视图
bgImageView.layout(0, 0, width, height);
// 标题标签居中偏左
int titleWidth = titleLabel.getMeasuredWidth();
int titleHeight = titleLabel.getMeasuredHeight();
int titleLeft = (width - titleWidth) / 2 - scaleWidth(30);
int titleTop = (height - titleHeight) / 2;
titleLabel.layout(titleLeft, titleTop, titleLeft + titleWidth, titleTop + titleHeight);
// 礼物图片在标题右侧
int giftSize = scaleWidth(20);
int giftLeft = titleLeft + titleWidth + scaleWidth(5);
int giftTop = titleTop + (titleHeight - giftSize) / 2;
giftImageView.layout(giftLeft, giftTop, giftLeft + giftSize, giftTop + giftSize);
// 数量标签在礼物图片右侧
int countWidth = countLabel.getMeasuredWidth();
int countLeft = giftLeft + giftSize;
countLabel.layout(countLeft, titleTop, countLeft + countWidth, titleTop + titleHeight);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = scaleWidth(316);
int height = scaleWidth(50);
setMeasuredDimension(width, height);
// 测量子视图
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
public void addGiftModel(MqttBean.ListBean model) {
if (isClose) {
return;
}
dataArray.add(model);
giftAction();
}
public void addGiftModelList(List<MqttBean.ListBean> list) {
dataArray.addAll(list);
giftAction();
}
private void giftAction() {
if (isPlaying) {
return;
}
if (dataArray.isEmpty()) {
return;
}
isPlaying = true;
model = dataArray.get(0);
// 添加到窗口(这里需要根据实际情况获取根布局)
ViewGroup rootView = (ViewGroup) ((Activity) context).getWindow().getDecorView();
rootView.addView(QXGiftDriftView.getInstance( context));
// 设置初始位置(屏幕右侧外)
setX(screenWidth);
// 进入动画
TranslateAnimation enterAnim = new TranslateAnimation(
Animation.ABSOLUTE, screenWidth,
Animation.ABSOLUTE, (screenWidth - scaleWidth(316)) / 2,
Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, 0
);
enterAnim.setDuration(1500);
enterAnim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
// 停留后退出
postDelayed(() -> {
TranslateAnimation exitAnim = new TranslateAnimation(
Animation.ABSOLUTE, (screenWidth - scaleWidth(316)) / 2,
Animation.ABSOLUTE, -screenWidth,
Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, 0
);
exitAnim.setDuration(2000);
exitAnim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
// 移除视图并处理下一个
ViewGroup rootView = (ViewGroup) getParent();
if (rootView != null) {
rootView.removeView(QXGiftDriftView.getInstance( context));
}
if (!dataArray.isEmpty()) {
dataArray.remove(0);
}
isPlaying = false;
if (!dataArray.isEmpty()) {
giftAction();
}
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
startAnimation(exitAnim);
}, 1000); // 停留1秒
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
startAnimation(enterAnim);
}
public void setModel(MqttBean.ListBean model) {
this.model = model;
String text = String.format("%s送给%s", model.getFromUserName(), model.getToUserName());
// 这里需要使用SpannableString来实现富文本效果
// 简化处理,直接设置文本
titleLabel.setText(text);
// 加载图片 - 使用图片加载库如Glide
// Glide.with(context).load(model.getGiftPicture()).into(giftImageView);
countLabel.setText(String.format("X%s", model.getNumber()));
}
public void drifPopIsClose(boolean isClose) {
this.isClose = isClose;
setVisibility(isClose ? View.GONE : View.VISIBLE);
SharedPreferences prefs = context.getSharedPreferences("AppPrefs", Context.MODE_PRIVATE);
prefs.edit().putBoolean("kIsCloseDrifPop", isClose).apply();
if (isClose) {
ViewGroup rootView = (ViewGroup) getParent();
if (rootView != null) {
rootView.removeView(this);
}
dataArray.clear();
isPlaying = false;
}
}
private int scaleWidth(int width) {
// 根据屏幕密度进行缩放
float density = context.getResources().getDisplayMetrics().density;
return (int) (width * density);
}
// QXGiftScrollModel 类(需要单独定义)
public static class QXGiftScrollModel {
private String fromUserName;
private String toUserName;
private String giftPicture;
private String number;
// getters and setters
public String getFromUserName() { return fromUserName; }
public void setFromUserName(String fromUserName) { this.fromUserName = fromUserName; }
public String getToUserName() { return toUserName; }
public void setToUserName(String toUserName) { this.toUserName = toUserName; }
public String getGiftPicture() { return giftPicture; }
public void setGiftPicture(String giftPicture) { this.giftPicture = giftPicture; }
public String getNumber() { return number; }
public void setNumber(String number) { this.number = number; }
}
}

View File

@@ -0,0 +1,144 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.xscm.moduleutil.bean.GiftBean;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class QXGiftPlayerManager {
private static QXGiftPlayerManager instance;
private View bgEffectView;
private GiftAnimView fullEffectView;
private GiftAnimView chatEffectView;
private Context context;
private QXGiftPlayerManager(Context context) {
this.context = context.getApplicationContext();
}
public static synchronized QXGiftPlayerManager getInstance(Context context) {
if (instance == null) {
instance = new QXGiftPlayerManager(context);
}
return instance;
}
public View getDefaultBgEffectView() {
if (bgEffectView == null) {
initBgEffectView();
}
return bgEffectView;
}
public GiftAnimView getDefaultFullEffectView() {
if (fullEffectView == null) {
initFullEffectView();
}
return fullEffectView;
}
public GiftAnimView getDefaultChatEffectView() {
if (chatEffectView == null) {
initChatEffectView();
}
return chatEffectView;
}
private void initBgEffectView() {
bgEffectView = new FrameLayout(context);
bgEffectView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
));
bgEffectView.setBackgroundColor(0x00000000);
bgEffectView.setClickable(false); // userInteractionEnabled = NO
// 添加全屏特效视图和聊天特效视图
((ViewGroup) bgEffectView).addView(getDefaultFullEffectView());
((ViewGroup) bgEffectView).addView(getDefaultChatEffectView());
}
public void displayFullEffectView(String gift) {
getDefaultFullEffectView().displayEffectView(gift);
}
public void displayFullEffectView1(List<String> stringList){
getDefaultFullEffectView().displayEffectView1(stringList);
}
public void displayChatEffectView(String gift) {
getDefaultChatEffectView().displayEffectView(gift);
}
public void openOrCloseEffectViewWith(boolean isShow) {
getDefaultFullEffectView().openOrCloseEffectViewWith(isShow);
getDefaultChatEffectView().openOrCloseEffectViewWith(isShow);
}
public void destroyEffectSvga() {
if (fullEffectView != null) {
fullEffectView.destroyEffectView();
if (bgEffectView != null) {
((ViewGroup) bgEffectView).removeView(fullEffectView);
}
fullEffectView = null;
}
if (chatEffectView != null) {
chatEffectView.destroyEffectView();
if (bgEffectView != null) {
((ViewGroup) bgEffectView).removeView(chatEffectView);
}
chatEffectView = null;
}
if (bgEffectView != null) {
bgEffectView = null;
}
}
public void stopPlay() {
if (fullEffectView != null) {
fullEffectView.stopPlay();
}
if (chatEffectView != null) {
chatEffectView.stopPlay();
}
}
private void initFullEffectView() {
fullEffectView = new GiftAnimView(context);
fullEffectView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
));
// 创建专用线程池替代GCD队列
ExecutorService queue = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
Executors.defaultThreadFactory());
fullEffectView.setQueue(queue);
}
private void initChatEffectView() {
chatEffectView = new GiftAnimView(context);
chatEffectView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
));
// 创建专用线程池替代GCD队列
ExecutorService queue = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
Executors.defaultThreadFactory());
chatEffectView.setQueue(queue);
}
}

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,270 @@
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(true);
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(), 3);
// 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( true);
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);
}
} else {
tv_time_pk.setVisibility(GONE);
mCharmView.setVisibility(VISIBLE);
}
updatePkState(bean);
}
private void updatePkState(RoomPitBean bean) {
if (bean.is_pk()) {
String userId = bean.getUser_id();
if (userId != null && !userId.equals("0") && !userId.isEmpty()) {
tv_time_pk.setVisibility(VISIBLE);
setSex(bean.getCharm(), false);
mCharmView.setVisibility(GONE);
} else {
tv_time_pk.setVisibility(GONE);
}
} else {
tv_time_pk.setVisibility(GONE);
if (isOn()) {
mCharmView.setVisibility(VISIBLE);
}else {
mCharmView.setVisibility(GONE);
}
}
}
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));
} else {
tv_time_pk.setText(value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void setTv_time_pk(boolean show){
tv_time_pk.setVisibility(show?VISIBLE:GONE);
}
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( true);
} else {
if (pitBean.getUser_id().equals(SpUtil.getUserId()) && closePhone) {
mIvRipple.stopAnimation( true);
} else {
mIvRipple.post(() -> {
mIvRipple.setVisibility(VISIBLE);
if (!mIvRipple.isAnimating()) {
mIvRipple.startAnimation();
}
});
}
}
}
@Override
public void userJoined(int userId, int elapsd) {
}
@Override
public void userOffline(int userId, int reason) {
}
@Override
public void unRegister(Object obj) {
}
}

View File

@@ -0,0 +1,343 @@
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);
}
}
});
}
mCharmView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mOnCharmClickListener != null && pitBean != null){
mOnCharmClickListener.onCharmClick(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(true);
mIvRipple.setVisibility(VISIBLE);
mTvName.setText(bean.getNickname());
ImageUtils.loadHeadCC(bean.getAvatar(), mRiv);
if (TextUtils.isEmpty(pitBean.getDress())) {
mIvFrame.stopAll();
mIvFrame.setVisibility(INVISIBLE);
} else {
mIvFrame.setVisibility(VISIBLE);
mIvFrame.stopAll();
mIvFrame.setSource(pitBean.getDress(), 3);
}
if (showBoss && WHEAT_BOSS.equals(pitNumber)) {
mIvTagBoss.setVisibility(GONE);
}
mCharmView.setVisibility(VISIBLE);
} 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.stopAll();
mIvFrame.setVisibility(INVISIBLE);
mIvFace.remove();
//停止声浪
mIvRipple.stopAnimation( true);
mIvRipple.setVisibility(GONE);
mCharmView.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 setmCharmView(String charm){
// if (mCharmView!=null){
// mCharmView.setSex(pitBean.getSex(),pitBean.getUser_id(), charm,false);
// }
// }
/**
* 开启计时
*/
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;
}
private onCharmClickListener mOnCharmClickListener;
public void setmOnCharmClickListener(onCharmClickListener mOnCharmClickListener) {
this.mOnCharmClickListener = mOnCharmClickListener;
}
@Override
public void unRegister(Object obj) {
}
public interface OnZhulClickListener {
void onZhulClick(RoomFriendshipWheatView view, RoomPitBean pitBean);
}
public interface onCharmClickListener{
void onCharmClick(RoomFriendshipWheatView view, RoomPitBean pitBean);
}
}

View File

@@ -0,0 +1,277 @@
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);
}
private onCharmClickListener mOnCharmClickListener;
public void setmOnCharmClickListener(onCharmClickListener mOnCharmClickListener) {
this.mOnCharmClickListener = mOnCharmClickListener;
}
public interface onCharmClickListener{
void onCharmClick(RoomKtvWheatView view, RoomPitBean pitBean);
}
@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);
mCharmView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mOnCharmClickListener != null && pitBean != null){
mOnCharmClickListener.onCharmClick(RoomKtvWheatView.this, pitBean);
}
}
});
}
@Override
protected int getLayoutId() {
return R.layout.room_view_ktv_wheat;
}
@Override
protected void setPitData(RoomPitBean bean) {
sex = bean.getSex();
if (isOn()) {
//开启声浪
mIvRipple.stopAnimation(true);
mIvRipple.setVisibility(VISIBLE);
mTvName.setText(bean.getNickname());
ImageUtils.loadHeadCC(bean.getAvatar(), mRiv);
if (TextUtils.isEmpty(pitBean.getDress())) {
mIvFrame.stopAll();
mIvFrame.setVisibility(INVISIBLE);
} else {
mIvFrame.setVisibility(VISIBLE);
mIvFrame.setSource(pitBean.getDress(), 3);
// 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.stopAll();
mIvFrame.setVisibility(INVISIBLE);
mIvFace.remove();
//停止声浪
mIvRipple.stopAnimation(true);
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(true);
} else {
if (pitBean.getUser_id().equals(SpUtil.getUserId()) && closePhone) {
mIvRipple.stopAnimation(true);
}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);
}
}
}
@Override
public void unRegister(Object obj) {
}
}

View File

@@ -0,0 +1,278 @@
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;
import com.xscm.moduleutil.utils.StringUtil;
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);
if (mTvTime != null) {
mTvTime.setOnClickListener(v -> {
// 处理魅力值点击
if (mOnCharmClickListener != null && pitBean != null) {
mOnCharmClickListener.onCharmClick(this, pitBean);
}
});
}
}
// @Override
// public void setOnClickListener(OnClickListener l) {
// // 保存外部设置的点击监听器
// this.onWholeViewClickListener = l;
// super.setOnClickListener(l);
// }
// 为子组件设置点击监听器时,确保不会拦截父容器的点击事件
public void setupChildClickListeners() {
if (mRiv != null) {
mRiv.setOnClickListener(v -> {
// 处理头像点击
if (onWholeViewClickListener != null) {
onWholeViewClickListener.onClick(this);
}
});
}
}
private OnClickListener onWholeViewClickListener;
@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(true);
mIvRipple.setVisibility(VISIBLE);
mTvName.setText(bean.getNickname());
ImageUtils.loadHeadCC(bean.getAvatar(), mRiv);
if (TextUtils.isEmpty(pitBean.getDress())) {
mIvFrame.stopAll();
mIvFrame.setVisibility(INVISIBLE);
} else {
mIvFrame.setVisibility(VISIBLE);
mIvFrame.setSource(pitBean.getDress(), 3);
}
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.stopAll();
mIvFrame.setVisibility(INVISIBLE);
mIvFace.remove();
//停止声浪
mIvRipple.stopAnimation(true);
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():"拍卖者");
mTvName.setBackgroundResource(com.xscm.moduleutil.R.mipmap.za_maiw_b);
}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(StringUtil.toWan2(pitBean.getCharm(),1));
}else if (pitBean.getPit_number().equals("000")){
iv_tag_type.setVisibility(GONE);
mCharmView.setVisibility(GONE);
}
// mCharmView.setOnClickListener(new OnClickListener() {
//
// @Override
// public void onClick(View v) {
// if (mOnCharmClickListener != null && pitBean != null){
// mOnCharmClickListener.onCharmClick(RoomMakeWheatView.this, pitBean);
// }
// }
// });
}
public void setmTvTime(String charm){
mTvTime.setText(StringUtil.toWan2(pitBean.getCharm(),1));
}
private onCharmClickListener mOnCharmClickListener;
//
public void setmOnCharmClickListener(onCharmClickListener mOnCharmClickListener) {
this.mOnCharmClickListener = mOnCharmClickListener;
}
//
public interface onCharmClickListener{
void onCharmClick(RoomMakeWheatView view, RoomPitBean pitBean);
}
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);
}
}
}
}
@Override
public void unRegister(Object obj) {
}
}

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,297 @@
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 RoomSingSongWheat2View extends BaseWheatView {
public ImageView mIvTagBoss;
public TextView mTvTime;
public TextView tv_time_pk;
public boolean isClickListenerSet = false;
private boolean showBoss;//显示老板标识
public RoomSingSongWheat2View(Context context) {
this(context, null, 0);
}
public RoomSingSongWheat2View(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoomSingSongWheat2View(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void initPit(Context context, AttributeSet attrs) {
TypedArray typedArray = null;
try {
typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoomDefaultWheatView);
pitNumber = typedArray.getString(R.styleable.RoomDefaultWheatView_room_wheat_number);
} finally {
if (typedArray != null) {
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_2;
}
@Override
protected void setPitData(RoomPitBean bean) {
if (bean == null) return;
sex = bean.getSex();
pitBean = bean; // 统一使用参数 bean
if (isOn()) {
handleOnState(bean);
} else {
handleOffState(bean);
}
updateSexIcon();
// updateCharmViewVisibility(bean);
updatePkState(bean);
iv_on_line.setVisibility(GONE);
}
private void handleOnState(RoomPitBean bean) {
mTvName.setText(bean.getNickname());
ImageUtils.loadHeadCC(bean.getAvatar(), mRiv);
mCharmView.setVisibility(VISIBLE);
if (mIvFrame != null) {
if (TextUtils.isEmpty(bean.getDress())) {
mIvFrame.stopAll();
mIvFrame.setVisibility(INVISIBLE);
} else {
mIvFrame.setVisibility(VISIBLE);
mIvFrame.setSource(bean.getDress(), 3);
}
}
if (showBoss && TextUtils.equals(WHEAT_BOSS, pitNumber)) {
mIvTagBoss.setVisibility(GONE);
}
}
private void handleOffState(RoomPitBean bean) {
mTvName.setText(getPitNumberText());
mCharmView.setVisibility(GONE);
if (showBoss && TextUtils.equals(WHEAT_BOSS, pitNumber)) {
mIvTagBoss.setVisibility(VISIBLE);
int resId = bean.getIs_lock() == 1 ? R.mipmap.index_img_room_mask : R.drawable.room_ic_wheat_default;
mRiv.setImageResource(resId);
} else {
mRiv.setImageResource(bean.getIs_lock() == 1 ? R.mipmap.index_img_room_mask : R.drawable.room_ic_wheat_default);
}
if (isMute()) {
ImageUtils.loadRes(R.mipmap.room_microphone_off, mIvSex);
}
if (mIvFrame != null) {
mIvFrame.stopAll();
mIvFrame.setVisibility(INVISIBLE);
}
mIvFace.remove();
}
private String getPitNumberText() {
if ("-1".equals(pitNumber)) return "";
if ("9".equals(pitNumber)) return "主持位";
if ("10".equals(pitNumber)) return "嘉宾位";
return pitNumber + "号麦位";
}
private void updateSexIcon() {
if (showSexIcon) {
checkSex();
}
}
private void updateCharmViewVisibility(RoomPitBean bean) {
boolean isEmptyNickname = bean.getNickname() == null || bean.getNickname().isEmpty();
mCharmView.setVisibility(isEmptyNickname ? GONE : VISIBLE);
}
private void updatePkState(RoomPitBean bean) {
if (bean.is_pk()) {
String userId = bean.getUser_id();
if (userId != null && !userId.equals("0") && !userId.isEmpty()) {
tv_time_pk.setVisibility(VISIBLE);
setSex(bean.getCharm(), false);
mCharmView.setVisibility(GONE);
} else {
tv_time_pk.setVisibility(GONE);
}
} else {
tv_time_pk.setVisibility(GONE);
if (isOn()) {
mCharmView.setVisibility(VISIBLE);
} else {
mCharmView.setVisibility(GONE);
}
}
}
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));
} else {
tv_time_pk.setText(value);
}
} catch (Exception e) {
e.printStackTrace();
tv_time_pk.setText("0"); // 设置默认值防止UI异常
}
}
}
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);
}
// 添加内存释放方法
public void releaseResources() {
// 清理头像加载
if (mRiv != null) {
mRiv.setImageBitmap(null);
}
// 清理框架视图
if (mIvFrame != null) {
mIvFrame.release(); // 清理SVGA资源
}
// 清理表情视图
if (mIvFace != null) {
mIvFace.remove();
}
// 清理其他图片资源
if (mIvSex != null) {
mIvSex.setImageBitmap(null);
}
if (mIvTagBoss != null) {
mIvTagBoss.setImageBitmap(null);
}
}
@Override
protected void onDetachedFromWindow() {
// 视图从窗口分离时释放资源
releaseResources();
super.onDetachedFromWindow();
}
@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) {
// 暂无实现
}
@Override
public void unRegister(Object obj) {
}
}

View File

@@ -0,0 +1,277 @@
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 = null;
try {
typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoomDefaultWheatView);
pitNumber = typedArray.getString(R.styleable.RoomDefaultWheatView_room_wheat_number);
} finally {
if (typedArray != null) {
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) {
if (bean == null) return;
sex = bean.getSex();
pitBean = bean; // 统一使用参数 bean
if (isOn()) {
handleOnState(bean);
} else {
handleOffState(bean);
}
updateSexIcon();
updatePkState(bean);
iv_on_line.setVisibility(GONE);
}
private void handleOnState(RoomPitBean bean) {
mIvRipple.setVisibility(VISIBLE);
mTvName.setText(bean.getNickname());
ImageUtils.loadHeadCC(bean.getAvatar(), mRiv);
mCharmView.setVisibility(VISIBLE);
if (TextUtils.isEmpty(bean.getDress())) {
mIvFrame.stopAll();
mIvFrame.setVisibility(INVISIBLE);
} else {
mIvFrame.setVisibility(VISIBLE);
mIvFrame.setSource(bean.getDress(), 3);
}
if (showBoss && TextUtils.equals(WHEAT_BOSS, pitNumber)) {
mIvTagBoss.setVisibility(GONE);
}
}
private void handleOffState(RoomPitBean bean) {
mTvName.setText(getPitNumberText());
mCharmView.setVisibility(GONE);
if (showBoss && TextUtils.equals(WHEAT_BOSS, pitNumber)) {
mIvTagBoss.setVisibility(VISIBLE);
int resId = bean.getIs_lock() == 1 ? R.mipmap.index_img_room_mask : R.drawable.room_ic_wheat_default;
mRiv.setImageResource(resId);
} else {
mRiv.setImageResource(bean.getIs_lock() == 1 ? R.mipmap.index_img_room_mask : R.drawable.room_ic_wheat_default);
}
if (isMute()) {
ImageUtils.loadRes(R.mipmap.room_microphone_off, mIvSex);
}
mIvFrame.stopAll();
mIvFrame.setVisibility(INVISIBLE);
mIvFace.remove();
mIvRipple.setVisibility(GONE);
}
private String getPitNumberText() {
if ("-1".equals(pitNumber)) return "";
if ("9".equals(pitNumber)) return "主持位";
if ("10".equals(pitNumber)) return "嘉宾位";
return pitNumber + "号位";
}
private void updateSexIcon() {
if (showSexIcon) {
checkSex();
}
}
private void updateCharmViewVisibility(RoomPitBean bean) {
boolean isEmptyNickname = bean.getNickname() == null || bean.getNickname().isEmpty();
mCharmView.setVisibility(isEmptyNickname ? GONE : VISIBLE);
}
private void updatePkState(RoomPitBean bean) {
if (bean.is_pk()) {
String userId = bean.getUser_id();
if (userId != null && !userId.equals("0") && !userId.isEmpty()) {
tv_time_pk.setVisibility(VISIBLE);
setSex(bean.getCharm(), false);
mCharmView.setVisibility(GONE);
} else {
tv_time_pk.setVisibility(GONE);
}
} else {
tv_time_pk.setVisibility(GONE);
if (isOn()) {
mCharmView.setVisibility(VISIBLE);
} else {
mCharmView.setVisibility(GONE);
}
}
}
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));
} else {
tv_time_pk.setText(value);
}
} catch (Exception e) {
e.printStackTrace();
tv_time_pk.setText("0"); // 设置默认值防止UI异常
}
}
}
private boolean showSexIcon = false;
private String sex;
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 releaseResources() {
// 清理头像加载
if (mRiv != null) {
mRiv.setImageBitmap(null);
}
// 清理框架视图
if (mIvFrame != null) {
mIvFrame.release(); // 清理SVGA资源
}
// 清理表情视图
if (mIvFace != null) {
mIvFace.remove();
}
// 清理其他图片资源
if (mIvSex != null) {
mIvSex.setImageBitmap(null);
}
if (mIvTagBoss != null) {
mIvTagBoss.setImageBitmap(null);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
}
@Override
public void unRegister(Object obj) {
}
@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) {
}
}

View File

@@ -0,0 +1,169 @@
package com.xscm.moduleutil.widget;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.opensource.svgaplayer.SVGAImageView;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.room.RoomPitBean;
import com.xscm.moduleutil.utils.ImageUtils;
public class RoomSingWheatView extends LinearLayout {
public ImageView mRiv;
public ImageView mIvGift;
public WheatCharmView mCharmView;
public TextView mTvName;
public ImageView mIvSex;
public AvatarFrameView mIvFrame;
public AvatarFrameView 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
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 RoomSingWheatView(@NonNull Context context) {
this(context, null);
}
public RoomSingWheatView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RoomSingWheatView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context) {
// 确保布局被正确加载
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);
// 设置初始位置
if (mIvGift != null) {
oX = mIvGift.getX();
oY = mIvGift.getY();
}
}
protected int getLayoutId() {
return R.layout.room_view_sing_wheat;
}
public void setData(RoomPitBean bean) {
this.pitBean = bean;
if (bean == null) return;
// 添加空值检查防止NPE
if (mTvName == null) {
// 可能布局未正确加载,尝试重新初始化
initView(getContext());
if (mTvName == null) {
// 如果仍然为null记录日志并返回
android.util.Log.e("RoomSingWheatView", "mTvName is still null after re-initialization");
return;
}
}
if (isOn()) {
//开启声浪
mIvRipple.startLoopingSvga("ripple3695.svga");
mIvRipple.setVisibility(VISIBLE);
mTvName.setText(bean.getNickname());
ImageUtils.loadHeadCC(bean.getAvatar(), mRiv);
if (TextUtils.isEmpty(pitBean.getDress())) {
if (mIvFrame != null) mIvFrame.setVisibility(INVISIBLE);
} else {
if (mIvFrame != null) {
mIvFrame.setVisibility(VISIBLE);
mIvFrame.setSource(pitBean.getDress(), 3);
}
}
} else {
String pitText = "-1".equals(pitNumber) ? "" :
"9".equals(pitNumber) ? "主持位" :
"10".equals(pitNumber) ? "嘉宾位" :
pitNumber + "号麦位";
mTvName.setText(pitText);
if (mIvFrame != null) mIvFrame.setVisibility(INVISIBLE);
if (mIvFace != null) mIvFace.remove();
//停止声浪
mIvRipple.stopSvga();
mIvRipple.setVisibility(GONE);
}
// 更新魅力值视图
if (mCharmView != null) {
if (pitBean.getNickname() == null || pitBean.getNickname().isEmpty()) {
mCharmView.setVisibility(GONE);
} else {
mCharmView.setVisibility(VISIBLE);
}
}
// 更新PK状态
if (tv_time_pk != null) {
if (pitBean.is_pk() && pitBean.getUser_id() != null &&
!pitBean.getUser_id().equals("0") && !pitBean.getUser_id().isEmpty()) {
tv_time_pk.setVisibility(VISIBLE);
if (mCharmView != null) mCharmView.setVisibility(GONE);
} else {
tv_time_pk.setVisibility(GONE);
if (mCharmView != null) mCharmView.setVisibility(VISIBLE);
}
}
}
private boolean isOn() {
return pitBean != null && !TextUtils.isEmpty(pitBean.getUser_id()) && !"0".equals(pitBean.getUser_id());
}
}

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,70 @@
package com.xscm.moduleutil.widget;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.xscm.moduleutil.bean.room.RoomInfoResp;
import com.xscm.moduleutil.enumType.QXRoomSeatViewType;
// 在 common 模块中或相应模块中
public class SharedViewModel extends ViewModel {
// 给roomFragment传递数据
private final MutableLiveData<RoomInfoResp> dataForFragment = new MutableLiveData<>();
private final MutableLiveData<Boolean> fragmentReady = new MutableLiveData<>(false);
private MutableLiveData<QXRoomSeatViewType> seatViewTypeData = new MutableLiveData<>();
//给子fragment传递数据
private MutableLiveData<RoomInfoResp> childFragmentData = new MutableLiveData<>();
// 为子Fragment设置数据的方法
public void setChildFragmentData(RoomInfoResp data) {
childFragmentData.setValue(data);
}
// 获取子Fragment数据的LiveData
public LiveData<RoomInfoResp> getChildFragmentData() {
return childFragmentData;
}
public void setSeatViewType(QXRoomSeatViewType type) {
seatViewTypeData.setValue(type);
}
public LiveData<QXRoomSeatViewType> getSeatViewType() {
return seatViewTypeData;
}
public LiveData<RoomInfoResp> getDataForFragment() {
return dataForFragment;
}
public void setDataForFragment(RoomInfoResp data) {
dataForFragment.setValue(data);
}
public LiveData<Boolean> getFragmentReady() {
return fragmentReady;
}
public void setFragmentReady(boolean ready) {
fragmentReady.setValue(ready);
}
// 清除数据,避免重复接收
// 清理所有数据的方法
public void clearAllData() {
dataForFragment.setValue(null);
childFragmentData.setValue(null);
seatViewTypeData.setValue(null);
fragmentReady.setValue(false);
}
// 清理子Fragment数据
public void clearChildFragmentData() {
childFragmentData.setValue(null);
}
// 清理主Fragment数据
public void clearFragmentData() {
dataForFragment.setValue(null);
}
}

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,353 @@
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.RoomMessageEvent;
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;
// 麦位索引映射9,10,1~8
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) {
if (pitList == null || pitList.size() < 10) return;
this.pitList = pitList;
restoreMultiWheat();
}
public void setWheatDataPk(List<RoomPitBean> pitList, int layoutType) {
if (pitList == null || pitList.size() < 10) return;
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;
if (pitNumber < 1 || pitNumber > 10 || pitList == null || pitList.size() < 10) return;
container.removeAllViews();
RoomPitBean bean = pitList.get(pitNumber - 1);
singleWheatView = new RoomDefaultWheatView(context);
singleWheatView.pitNumber = String.valueOf(pitNumber);
singleWheatView.setData(bean);
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;
LinearLayout row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL);
for (int i = 0; i < 10; i++) {
int pitNumber = pitIndexMap[i];
RoomDefaultWheatView wheatView = createWheatView(pitNumber);
LinearLayout.LayoutParams params;
if (i == 0 || i == 1) {
int fixedHeightInPx = dpToPx(110);
params = new LinearLayout.LayoutParams(itemWidth, fixedHeightInPx);
if (i == 0) params.rightMargin = dpToPx(50);
} else {
int fixedHeightInPx = dpToPx(90);
params = new LinearLayout.LayoutParams(itemWidth - 30, fixedHeightInPx + 30);
params.setMargins(0, 0, 0, 0);
}
wheatView.setLayoutParams(params);
wheatView.setOnClickListener(v -> {
if (wheatClickListener != null) {
wheatClickListener.onWheatClick(wheatView, Integer.parseInt(wheatView.pitNumber));
}
});
row.addView(wheatView);
if (i == 1 || (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;
}
public void restoreMultiWheatPk(int layoutType) {
container.removeAllViews();
int screenWidth = getScreenWidth();
int itemWidth = screenWidth / 8;
LinearLayout row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL);
int firstPitNumber = layoutType == 1 ? 10 : 9;
int secondPitNumber = layoutType == 1 ? 9 : 10;
addWheatViewItem(row, firstPitNumber, (int)(itemWidth * 1.7), layoutType);
addWheatViewItem(row, secondPitNumber,(int)(itemWidth * 1.7), layoutType);
container.addView(row);
row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL);
for (int i = 2; i < 10; i++) {
int pitNumber = pitIndexMap[i];
addWheatViewItem(row, pitNumber, itemWidth, layoutType);
if ((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 = createWheatView(pitNumber);
if (layoutType==2){
wheatView.setTv_time_pk(false);
}
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 , fixedHeightInPx);
if (layoutType == 1) {
params.rightMargin = context.getResources().getDimensionPixelSize(R.dimen.dp_1);
params.setMargins(20, -20, -20, 0);
} else {
params.leftMargin = context.getResources().getDimensionPixelSize(R.dimen.dp_1);
params.setMargins(-30, -20, 0, 0);
}
} else {
params = new LinearLayout.LayoutParams(itemWidth , fixedHeightInPx);
if (layoutType == 1) {
params.setMargins(-10, 10, 0, 0);
} else {
params.setMargins(0, 10, -10, 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 int dpToPx(int dp) {
return Math.round(dp * context.getResources().getDisplayMetrics().density);
}
private int getScreenWidth() {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return metrics.widthPixels;
}
public void setUpData(RoomMessageEvent event,int layoutType){
String fromPit=event.getText().getFrom_pit_number()!=null?event.getText().getFrom_pit_number():"";
String toPitNumber=event.getText().getTo_pit_number()!=null?event.getText().getTo_pit_number():"";
if (fromPit.equals("") || toPitNumber.equals("")){
return;
}
RoomDefaultWheatView fromWheatView = findWheatViewByPitNumber(Integer.parseInt(fromPit));
RoomDefaultWheatView toWheatView = findWheatViewByPitNumber(Integer.parseInt(toPitNumber));
RoomPitBean fromPitBean=fromWheatView.pitBean;
RoomPitBean toPitBean=toWheatView.pitBean;
String tmpNumber=fromPitBean.getPit_number();
fromPitBean.setPit_number(toPitBean.getPit_number());
toPitBean.setPit_number(tmpNumber);
toWheatView.setData(fromPitBean);
fromWheatView.setData(toPitBean);
if (layoutType==2){
fromWheatView.setTv_time_pk(false);
toWheatView.setTv_time_pk(false);
}else if (layoutType==1){
fromWheatView.setTv_time_pk(true);
toWheatView.setTv_time_pk(true);
}
}
public void updateSingleWheat(RoomPitBean pitBean, int pitNumber,int layoutType) {
if (pitList == null || pitList.isEmpty() || pitNumber < 1 || pitNumber > 10) return;
if (isSingleMode && this.currentSinglePit != pitNumber) return;
RoomDefaultWheatView wheatView = findWheatViewByPitNumber(pitNumber);
if (wheatView != null) {
pitBean.set_pk(true);
wheatView.setData(pitBean);
if (layoutType==2){
wheatView.setTv_time_pk(false);
}else if (layoutType==1){
wheatView.setTv_time_pk(true);
}
}
}
public void updateSingleCharm(RoomPitBean pitBean, int pitNumber,int layoutType) {
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=wheatView.pitBean;
bean=pitBean ;
if (layoutType==2){
wheatView.setTv_time_pk(false);
}else if (layoutType==1){
wheatView.setTv_time_pk(true);
}
wheatView.setSex(bean.getCharm(),false);
}
}
@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(pitList.get(pitNumber - 1), pitNumber,1);
}
}
public void updateSingleOnlineWheat(UserOnlineStatusBean bean) {
if (pitList == null || pitList.isEmpty() || bean == null) return;
for (RoomPitBean pitBean : pitList) {
int pitNumber = Integer.parseInt(pitBean.getPit_number());
RoomDefaultWheatView wheatView = findWheatViewByPitNumber(pitNumber);
if (wheatView != null) {
wheatView.setOnlineStatus();
}
}
}
public void clear() {
container.removeAllViews();
pitList = null;
singleWheatView = null;
isSingleMode = false;
currentSinglePit = -1;
}
public void clearData() {
pitList = null;
singleWheatView = null;
isSingleMode = false;
currentSinglePit = -1;
}
}

View File

@@ -0,0 +1,497 @@
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.google.android.material.snackbar.Snackbar;
import com.hjq.toast.ToastUtils;
import com.xscm.moduleutil.R;
import com.xscm.moduleutil.bean.RoomMessageEvent;
import com.xscm.moduleutil.bean.UserOnlineStatusBean;
import com.xscm.moduleutil.bean.room.RoomPitBean;
import java.util.ArrayList;
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 List<RoomSingSongWheatView> multiWheatViews=new ArrayList<>();
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);
void onMeilingClick(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, boolean isFirst) {
// 检查容器状态
if (container == null) {
return;
}
this.pitList = pitList;
restoreMultiWheat(isFirst);
}
public void setOnWheatClickListener(@Nullable OnWheatClickListener listener) {
this.wheatClickListener = listener;
}
public void restoreMultiWheat(boolean isFirst) {
// 检查容器状态
if (container == null) {
return;
}
try {
if (multiWheatViews != null && multiWheatViews.size() != 10) {
container.removeAllViews();
}
} catch (Exception e) {
// 忽略异常,继续执行
return;
}
if (pitList == null || pitList.size() < 10) {
return;
}
// multiWheatViews.clear();
int screenWidth = getScreenWidth();
int itemWidth = screenWidth / 4; // 每个控件宽度为屏幕宽度的 1/4
LinearLayout row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL);
if (multiWheatViews.size() == 10){
for (int i = 0; i < multiWheatViews.size(); i++){
multiWheatViews.get(i).setData(pitList.get(pitIndexMap[i] - 1));
}
}else {
for (int i = 0; i < 10; i++) {
int pitNumber = pitIndexMap[i];
if (isFirst){
RoomSingSongWheat2View wheatView = new RoomSingSongWheat2View( 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);
// 其他控件保持原有逻辑
params.setMargins(0, 0, 0, 0); // 不设右边距,由 row padding 控制
}
wheatView.setLayoutParams(params);
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);
}
}else {
RoomSingSongWheatView wheatView = new RoomSingSongWheatView(context);
wheatView.pitNumber = String.valueOf(pitNumber);
wheatView.setData(pitList.get(pitNumber - 1));
multiWheatViews.add(wheatView);
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);
// 其他控件保持原有逻辑
params.setMargins(0, 0, 0, 0); // 不设右边距,由 row padding 控制
}
wheatView.setLayoutParams(params);
GifAvatarOvalView avatarView = (GifAvatarOvalView) wheatView.mRiv;
avatarView.setOnClickListener(v -> {
if (wheatClickListener != null) {
wheatClickListener.onWheatClick(wheatView, pitNumber);
}
});
WheatCharmView charmView = wheatView.mCharmView;
charmView.setOnClickListener(v -> {
if (wheatClickListener != null) {
ToastUtils.show("点击了麦位");
wheatClickListener.onMeilingClick(wheatView, pitNumber);
}
});
wheatView.setOnClickListener(v -> {
if (wheatClickListener != null) {
wheatClickListener.onMeilingClick(wheatView, 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 (container == null || !isContainerValid()) {
return;
}
if (layoutType == 1) {
try {
container.removeAllViews();
} catch (Exception e) {
// 忽略异常,继续执行
return;
}
}
if (pitList == null || pitList.size() < 10) {
return;
}
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) {
// 检查容器状态
if (!isContainerValid()) {
return;
}
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.setMargins(-30, 10, 0, 0);
} else if (layoutType == 2) {
// 10号在右边右边距15dp
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;
}
public void setUpData(RoomMessageEvent event){
String fromPit=event.getText().getFrom_pit_number()!=null?event.getText().getFrom_pit_number():"";
String toPitNumber=event.getText().getTo_pit_number()!=null?event.getText().getTo_pit_number():"";
if (fromPit.equals("") || toPitNumber.equals("")){
return;
}
RoomSingSongWheatView fromWheatView = findWheatViewByPitNumber(Integer.parseInt(fromPit));
RoomSingSongWheatView toWheatView = findWheatViewByPitNumber(Integer.parseInt(toPitNumber));
RoomPitBean fromPitBean=fromWheatView.pitBean;
RoomPitBean toPitBean=toWheatView.pitBean;
String tmpNumber=fromPitBean.getPit_number();
fromPitBean.setPit_number(toPitBean.getPit_number());
toPitBean.setPit_number(tmpNumber);
toWheatView.setData(fromPitBean);
fromWheatView.setData(toPitBean);
for (RoomSingSongWheatView view : multiWheatViews){
if (view.pitBean.getUser_id().equals(event.getText().getFromUserInfo().getUser_id()+"")){
if (!view.pitBean.getPit_number().equals(toPitNumber)){
RoomPitBean bean=view.pitBean;
bean.setCharm("");
bean.setUser_id("");
bean.setDress("");
bean.setAvatar("");
bean.setNickname("");
bean.setSex("");
bean.setUser_code("");
bean.setDress_picture("");
view.setData(bean);
}
}
}
}
/**
* 更新指定 pitNumber 的麦位信息(用于局部刷新)
*/
public void updateSingleWheat(RoomPitBean pitBean, int pitNumber) {
// 检查容器状态
if (container == null || !isContainerValid()) {
return;
}
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 = pitBean;
wheatView.setData(bean); // 刷新数据
}
}
public void upDataCharm(RoomPitBean pitBean, int pitNumber){
// 检查容器状态
if (container == null || !isContainerValid()) {
return;
}
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 = pitBean;
wheatView.setCharm(bean.getCharm()); // 刷新数据
}
}
@Nullable
private RoomSingSongWheatView findWheatViewByPitNumber(int pitNumber) {
// 检查容器状态
if (container == null || !isContainerValid()) {
return null;
}
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;
}
// 添加容器状态检查方法
private boolean isContainerValid() {
try {
// 检查容器是否已附加到窗口
return container != null && container.isAttachedToWindow();
} catch (Exception e) {
return false;
}
}
// 添加资源清理方法
public void release() {
try {
if (multiWheatViews!=null){
for (RoomSingSongWheatView wheatView : multiWheatViews) {
wheatView.releaseResources();
}
}
if (container != null) {
container.removeAllViews();
}
} catch (Exception e) {
// 忽略异常
}
// 清理引用
pitList = null;
singleWheatView = null;
wheatClickListener = null;
}
}

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,205 @@
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.GiftPackListCount;
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) {
}
@Override
public void getGiftPack(String s) {
}
@Override
public void getGiftPackListCount(GiftPackListCount giftPackListCount) {
}
}

View File

@@ -0,0 +1,395 @@
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 androidx.viewpager.widget.ViewPager;
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.GiftPackListCount;
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.event.GiftUserRefreshEvent;
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.utils.SpUtil;
import com.xscm.moduleutil.widget.GifAvatarOvalView;
import com.xscm.moduleutil.widget.floatingView.IFloatingView;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
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(),"0","0");
}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);
}
}
private List<GiftLabelBean> giftLabelBeanList;
@Override
public void getGiftLabel(List<GiftLabelBean> giftLabelBeans) {
if (giftLabelBeans == null) return;
if (SpUtil.getShelf()==1){
for (GiftLabelBean giftLabelBean1 : giftLabelBeans){
if (giftLabelBean1.getId().equals("2")){
giftLabelBeans.remove(giftLabelBean1);
break;
}
}
}
mBinding.viewPager.setAdapter(new MyFragmentPagerAdapter(getChildFragmentManager(), giftLabelBeans,fragmentList,""));
mBinding.slidingTabLayout.setViewPager(mBinding.viewPager);
mBinding.slidingTabLayout.setCurrentTab(0);
refreshCurrentGiftFragment(giftLabelBeans.get(0).getId(),2,"");
mBinding.viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
// 当页面切换时,控制 tv_bb_qs 按钮的显示
refreshCurrentGiftFragment(giftLabelBeans.get(position).getId(),2,"");
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
private void refreshCurrentGiftFragment(String id,int type,String roomId) {
if (getCurrentGiftFragment()!=null){
getCurrentGiftFragment().loadDataIfNeeded(id,type,roomId);
}
}
private GiftTwoDetailsFragment getCurrentGiftFragment() {
int currentPosition = mBinding.viewPager.getCurrentItem();
// 使用 ViewPager 的 adapter 获取当前 fragment
MyFragmentPagerAdapter adapter = (MyFragmentPagerAdapter) mBinding.viewPager.getAdapter();
if (adapter != null) {
// 直接从 adapter 获取 fragment
Fragment fragment = adapter.getItem(currentPosition);
if (fragment instanceof GiftTwoDetailsFragment) {
return (GiftTwoDetailsFragment) fragment;
}
}
return null;
}
private int getSelectedGift() {
int currentItem = mBinding.viewPager.getCurrentItem();
return currentItem;
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void userRefresh(GiftUserRefreshEvent event) {
if (event.addSelf) {
roonGiftModel = event.gift;
}
}
private void giveGift(String num) {
getSelectedGift();
if (roonGiftModel == null) {
ToastUtils.show("请选择礼物");
return;
}
if (TextUtils.isEmpty(num)) {
ToastUtils.show("请选择打赏礼物数量");
return;
}
if (Integer.valueOf(num) <= 0) {
ToastUtils.show("请选择打赏礼物数量");
return;
}
//礼物打赏
giftNumber = num;
MvpPre.reward_zone(circle_id, roonGiftModel.getGift_id(), num, "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,circle_id));
dismiss();
}
@Override
public void roomAuctionJoin(RoomAuction.AuctionListBean auctionListBean) {
}
@Override
public void giftPack(List<GiftPackBean> giftPackBean) {
}
@Override
public void getGiftPack(String s) {
}
@Override
public void getGiftPackListCount(GiftPackListCount giftPackListCount) {
}
@Override
public void onDestroyView() {
super.onDestroyView();
}
@Override
public void onDestroy() {
super.onDestroy();
}
private class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {
private List<GiftLabelBean> list;
private List<Fragment> fragmentList;
private String roomId;
public MyFragmentPagerAdapter(FragmentManager fm, List<GiftLabelBean> list, List<Fragment> fragmentList, String roomId) {
super(fm);
this.list = list != null ? list : new ArrayList<>();
// 不直接使用传入的 fragmentList而是创建一个新的列表
this.fragmentList = new ArrayList<>();
// 初始化 fragmentList 的大小,用 null 填充
for (int i = 0; i < this.list.size(); i++) {
this.fragmentList.add(null);
}
this.roomId = roomId;
}
@Override
public Fragment getItem(int position) {
// 边界检查
if (position < 0 || list == null || position >= list.size()) {
return null;
}
// 检查该位置是否已经有 Fragment 实例
if (position < fragmentList.size() && fragmentList.get(position) != null) {
return fragmentList.get(position);
}
// 创建新的 Fragment
GiftLabelBean model = list.get(position);
Fragment fragment = GiftTwoDetailsFragment.newInstance(model.getId(), 1, roomId);
// 确保 fragmentList 有足够的空间
while (fragmentList.size() <= position) {
fragmentList.add(null);
}
// 在指定位置设置 Fragment 实例
fragmentList.set(position, fragment);
return fragment;
}
@Override
public int getCount() {
return list.size();
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
if (list == null || position < 0 || position >= list.size()) {
return null;
}
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,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,137 @@
package com.xscm.moduleutil.widget.img;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* Created by xscm on 2021/5/27.
* 创建类似.9图效果的拉伸背景
*/
public class BubbleBackgroundHelper {
/**
* 创建类似.9图效果的拉伸背景
*/
public static Drawable createStretchableBubble(Context context, Bitmap originalBitmap,
int leftBorder, int topBorder,
int rightBorder, int bottomBorder) {
try {
// 将 Bitmap 转换为 NinePatchDrawable
byte[] chunk = createNinePatchChunk(
originalBitmap.getWidth(),
originalBitmap.getHeight(),
leftBorder, topBorder, rightBorder, bottomBorder
);
NinePatchDrawable drawable = new NinePatchDrawable(
context.getResources(),
originalBitmap,
chunk,
new Rect(),
null
);
return drawable;
} catch (Exception e) {
e.printStackTrace();
// 如果失败,返回原始 BitmapDrawable
return new BitmapDrawable(context.getResources(), originalBitmap);
}
}
/**
* 创建 NinePatch 的 chunk 数据
*/
private static byte[] createNinePatchChunk(int bitmapWidth, int bitmapHeight,
int leftBorder, int topBorder,
int rightBorder, int bottomBorder) {
ByteBuffer buffer = ByteBuffer.allocate(84).order(ByteOrder.nativeOrder());
// 写入魔数
buffer.put((byte) 0x01);
buffer.put((byte) 0x02);
buffer.put((byte) 0x02);
buffer.put((byte) 0x02);
// 水平拉伸区域数量
buffer.putInt(1);
// 垂直拉伸区域数量
buffer.putInt(1);
// 颜色数量
buffer.putInt(0);
// 跳过填充
buffer.putInt(0);
buffer.putInt(0);
// 水平拉伸区域
buffer.putInt(leftBorder);
buffer.putInt(bitmapWidth - rightBorder);
// 垂直拉伸区域
buffer.putInt(topBorder);
buffer.putInt(bitmapHeight - bottomBorder);
// 填充剩余部分
for (int i = 0; i < 9; i++) {
buffer.putInt(0);
}
return buffer.array();
}
/**
* 加载网络图片并创建拉伸背景
*/
public static void loadBubbleBackground(Context context, String imageUrl,
OnBubbleBackgroundLoadedListener listener) {
Glide.with(context)
.asBitmap()
.load(imageUrl)
.into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource,
@Nullable Transition<? super Bitmap> transition) {
// 假设气泡的四个角都是 30px根据你的图片调整
int borderSize = 10;
Drawable bubbleDrawable = createStretchableBubble(
context, resource,
borderSize, borderSize, borderSize, borderSize
);
if (listener != null) {
listener.onBackgroundLoaded(bubbleDrawable);
}
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
// 清理资源
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
if (listener != null) {
listener.onLoadFailed();
}
}
});
}
public interface OnBubbleBackgroundLoadedListener {
void onBackgroundLoaded(Drawable bubbleDrawable);
void onLoadFailed();
}
}

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);
}
}

Some files were not shown because too many files have changed in this diff Show More