diff --git a/moduleUtil/src/main/java/com/xscm/moduleutil/view/QXMeetUserView.java b/moduleUtil/src/main/java/com/xscm/moduleutil/view/QXMeetUserView.java index a5e8ab5..1094c58 100644 --- a/moduleUtil/src/main/java/com/xscm/moduleutil/view/QXMeetUserView.java +++ b/moduleUtil/src/main/java/com/xscm/moduleutil/view/QXMeetUserView.java @@ -1,252 +1,317 @@ package com.xscm.moduleutil.view; import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; -import android.graphics.RectF; import android.graphics.drawable.GradientDrawable; import android.util.AttributeSet; +import android.view.Gravity; import android.view.View; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatImageView; -import com.bumptech.glide.Glide; import com.xscm.moduleutil.R; -import com.xscm.moduleutil.bean.UserInfo; import com.xscm.moduleutil.bean.blindboxwheel.BlindBoxBean; import com.xscm.moduleutil.utils.ImageUtils; import com.xscm.moduleutil.widget.GifAvatarOvalView; +/** + * 用户信息展示View(修复装饰图不显示 + 标签调整至头像下方-10dp) + */ public class QXMeetUserView extends RelativeLayout { + private static final String TAG = "QXMeetUserView"; + // 1. 装饰图与头像配置(确保装饰图露出,修复不显示问题) + private static final int DRESS_EXCEED_AVATAR_DP = 4; // 装饰图四周露4dp,保留装饰效果 + private static final int MIN_DRESS_SIZE_DP = 50; // 装饰图最小尺寸,避免过小导致不显示 + private static final int MAX_DRESS_SIZE_DP = 100; // 限制最大尺寸,防止布局失衡 + // 2. 标签与名称配置(核心:标签偏移改为-10dp,在头像下方) + private static final int TAG_NAME_SPACING_DP = 3; // 标签与名称间距,防止重叠 + private static final int TAG_HEIGHT_DP = 16; // 标签固定高度 + private static final int NAME_BOTTOM_MARGIN_DP = 8; // 名称与父布局底部间距 + private static final int TAG_BOTTOM_OFFSET_DP = -10; // 标签相对头像底部向下偏移10dp(核心调整) - private GifAvatarOvalView headerImageView; - private ImageView dressImageView; - private TextView tagLabel; - private TextView nameLabel; + private GifAvatarOvalView ivAvatar; // 头像(中层) + private ImageView ivAvatarDress; // 装饰图(底层,确保最下方显示) + private TextView tvTag; // 标签(顶层,在头像下方) + private TextView tvName; // 名称(顶层,在标签下方) private boolean isLuckUser; - private Object model; // 这里用 Object 代替 QXUserModel + private BlindBoxBean.xlhUser userModel; + // 构造方法 public QXMeetUserView(Context context) { super(context); - initSubviews(context); + initViewHierarchy(context); } public QXMeetUserView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); - initSubviews(context); + initViewHierarchy(context); } public QXMeetUserView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - initSubviews(context); + initViewHierarchy(context); } - private void initSubviews(Context context) { - setClipChildren(false); + /** + * 初始化视图层级(核心:严格按“底层→中层→顶层”顺序,确保装饰图在最下) + */ + private void initViewHierarchy(Context context) { + setClipChildren(false); // 允许子View超出父布局,避免装饰图边缘被裁剪 setClipToPadding(false); + setWillNotDraw(false); + initAvatar(context); // 2. 再加头像,覆盖装饰图中间 + // 层级顺序:1.装饰图(底层)→2.头像(中层)→3.名称→4.标签(顶层) + initAvatarDress(context); // 1. 先加装饰图,确保在最底层 + initName(context); // 3. 加名称(用于标签定位) + initTag(context); // 4. 最后加标签,确保在顶层 - - // 创建头像图片视图 - headerImageView = new GifAvatarOvalView(context); - headerImageView.setScaleType(ImageView.ScaleType.FIT_CENTER); // 添加这一行 -// headerImageView.setScaleType(ImageView.ScaleType.FIT_CENTER); -// headerImageView.setImageResource(R.mipmap.default_avatar); - - int headerSize = getMeasuredWidth() - dpToPx(18); // self.width-9*2 - LayoutParams headerParams = new LayoutParams(headerSize, headerSize); - headerParams.setMargins(0, 10, 0, 0); - headerParams.addRule(CENTER_IN_PARENT); - // 将头像添加到装饰视图之上 - addView(headerImageView, headerParams); - - // 创建装饰图片视图 - dressImageView = new ImageView(context); - dressImageView.setScaleType(ImageView.ScaleType.FIT_CENTER); - dressImageView.setImageResource(R.mipmap.xlh_image); - - LayoutParams dressParams = new LayoutParams( - LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT - ); - addView(dressImageView, dressParams); - - // 创建标签标签 - tagLabel = new TextView(context); - tagLabel.setTextColor(0xFFFFE554); // RGB16(0xFFE554) - tagLabel.setTextSize(12); - tagLabel.setGravity(android.view.Gravity.CENTER); - tagLabel.setBackground(getRoundedRectBackground(0xFF8D6F28, dpToPx(8))); // 默认房主背景色 - - LayoutParams tagParams = new LayoutParams(dpToPx(45), dpToPx(16)); - tagParams.addRule(CENTER_HORIZONTAL); - // 需要在测量完成后设置底部位置 - addView(tagLabel, tagParams); - - // 创建名称标签 - nameLabel = new TextView(context); - nameLabel.setTextColor(0xFFFFFFFF); // RGB16(0xffffff) - nameLabel.setTextSize(12); - nameLabel.setText("虚位以待"); - nameLabel.setGravity(android.view.Gravity.CENTER); - - LayoutParams nameParams = new LayoutParams( - LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT - ); - nameParams.addRule(CENTER_HORIZONTAL); - nameParams.addRule(ALIGN_PARENT_BOTTOM); - addView(nameLabel, nameParams); + // 强制层级确认(避免渲染异常,装饰图不调用bringChildToFront,保持最底层) + bringChildToFront(ivAvatar); // 中层:头像在装饰图之上 + bringChildToFront(tvName); // 顶层下层:名称在头像之上 + bringChildToFront(tvTag); // 顶层上层:标签在名称之上 } + /** + * 初始化装饰图(修复不显示:优化ScaleType+确保尺寸足够) + */ + private void initAvatarDress(Context context) { + ivAvatarDress = new ImageView(context); + // 修复:若装饰图是边框/框架类资源(如圆形外框),用CENTER(不缩放,完整显示) + ivAvatarDress.setScaleType(ImageView.ScaleType.CENTER); + ivAvatarDress.setImageResource(R.mipmap.xlh_image); // 确保资源引用正确 + + // 初始尺寸:基于装饰图最小尺寸,避免初始过小导致不显示 + int initDressSize = dp2px(MIN_DRESS_SIZE_DP); + LayoutParams dressParams = new LayoutParams(initDressSize, initDressSize); + dressParams.addRule(CENTER_HORIZONTAL); // 与头像水平居中对齐 + dressParams.addRule(CENTER_VERTICAL); // 暂用垂直居中,后续onLayout精准调整 + addView(ivAvatarDress, dressParams); + } + + /** + * 初始化头像(中层:尺寸=装饰图尺寸-2*超出尺寸,确保装饰图露出) + */ + private void initAvatar(Context context) { + ivAvatar = new GifAvatarOvalView(context); + ivAvatar.setId(View.generateViewId()); // 生成唯一ID,用于标签定位 + ivAvatar.setScaleType(AppCompatImageView.ScaleType.CENTER_CROP); // 头像填满容器,无变形 + ivAvatar.setImageResource(R.mipmap.default_avatar); + + // 初始尺寸:装饰图尺寸 - 2*超出尺寸(确保四周露4dp装饰图) + int initDressSize = dp2px(MIN_DRESS_SIZE_DP); + int initAvatarSize = initDressSize - dp2px(DRESS_EXCEED_AVATAR_DP * 2); + LayoutParams avatarParams = new LayoutParams(initAvatarSize, initAvatarSize); + avatarParams.addRule(CENTER_HORIZONTAL); // 与装饰图水平对齐 + avatarParams.addRule(CENTER_VERTICAL); // 暂用垂直居中,后续onLayout精准调整 + addView(ivAvatar, avatarParams); + } + + /** + * 初始化名称(固定底部定位,给标签预留足够空间) + */ + private void initName(Context context) { + tvName = new TextView(context); + tvName.setId(View.generateViewId()); // 生成ID,用于标签ABOVE约束 + tvName.setTextColor(0xFFFFFFFF); + tvName.setTextSize(12); + tvName.setText("虚位以待"); + tvName.setGravity(Gravity.CENTER); + tvName.setMaxWidth(dp2px(100)); // 限制最大宽度,避免名称过长换行 + tvName.setSingleLine(true); // 强制单行,避免高度异常 + + LayoutParams nameParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + nameParams.addRule(CENTER_HORIZONTAL); // 与头像/装饰图水平对齐 + nameParams.addRule(ALIGN_PARENT_BOTTOM); // 固定在父布局底部 + nameParams.bottomMargin = dp2px(NAME_BOTTOM_MARGIN_DP); // 与底部保持间距 + addView(tvName, nameParams); + } + + /** + * 初始化标签(核心:调整至头像下方-10dp,水平居中) + */ + private void initTag(Context context) { + tvTag = new TextView(context); + tvTag.setTextColor(0xFFFFE554); + tvTag.setTextSize(12); + tvTag.setGravity(Gravity.CENTER); + tvTag.setText("房主"); + tvTag.setHeight(dp2px(TAG_HEIGHT_DP)); // 固定标签高度,避免挤压名称 + tvTag.setBackground(getRoundedBg(0xFF8D6F28, dp2px(8))); + + LayoutParams tagParams = new LayoutParams( + dp2px(45), // 标签固定宽度 + dp2px(TAG_HEIGHT_DP) // 标签固定高度 + ); + tagParams.addRule(CENTER_HORIZONTAL); // 与头像水平对齐 + tagParams.addRule(ALIGN_BOTTOM, ivAvatar.getId()); // 基于头像底部定位 + tagParams.bottomMargin = dp2px(TAG_BOTTOM_OFFSET_DP); // 向下偏移10dp(在头像下方) + tagParams.addRule(ABOVE, tvName.getId()); // 标签在名称上方,防止重叠 + tagParams.setMargins(0, 0, 0, dp2px(TAG_NAME_SPACING_DP)); // 与名称保持3dp间距 + addView(tvTag, tagParams); + } + + /** + * 测量阶段(确保装饰图优先获取尺寸,避免被挤压不显示) + */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // 先测量子View,获取名称、标签基础尺寸 super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - // 确保在测量时设置正确的头像尺寸 - if (headerImageView != null) { - int headerSize = getMeasuredWidth() - dpToPx(18); // width - 9dp * 2 - LayoutParams headerParams = (LayoutParams) headerImageView.getLayoutParams(); - if (headerParams != null) { - headerParams.width = headerSize; - headerParams.height = headerSize; - headerParams.leftMargin = dpToPx(9); - headerParams.topMargin = dpToPx(9); - headerImageView.setLayoutParams(headerParams); - } - } + + if (ivAvatar == null || ivAvatarDress == null || tvTag == null || tvName == null) return; + + // 1. 计算装饰图最终尺寸(优先级:父布局宽度限制→最大尺寸→最小尺寸) + int parentWidth = MeasureSpec.getSize(widthMeasureSpec); + int maxDressSize = dp2px(MAX_DRESS_SIZE_DP); + int minDressSize = dp2px(MIN_DRESS_SIZE_DP); + int finalDressSize = Math.min( + Math.max(parentWidth * 8 / 10, minDressSize), // 父布局宽度80%,避免撑满 + maxDressSize + ); + + // 2. 计算头像最终尺寸(严格依赖装饰图,确保装饰图露出) + int dressExceed = dp2px(DRESS_EXCEED_AVATAR_DP); + int finalAvatarSize = finalDressSize - (dressExceed * 2); + finalAvatarSize = Math.max(finalAvatarSize, dp2px(10)); // 兜底最小尺寸,避免过小 + + // 3. 更新装饰图和头像尺寸(先更装饰图,确保其有足够空间) + updateViewSize(ivAvatarDress, finalDressSize, finalDressSize); + updateViewSize(ivAvatar, finalAvatarSize, finalAvatarSize); + + // 4. 计算父布局最小高度(容纳所有元素,避免装饰图/标签被裁) + int tagHeight = dp2px(TAG_HEIGHT_DP); + int nameHeight = tvName.getMeasuredHeight(); + int tagNameSpacing = dp2px(TAG_NAME_SPACING_DP); + int nameBottomMargin = dp2px(NAME_BOTTOM_MARGIN_DP); + int minParentHeight = finalDressSize + tagHeight + tagNameSpacing + nameHeight + nameBottomMargin; + + // 确定父布局最终尺寸(高度取测量值与最小高度最大值,避免挤压) + int finalParentWidth = parentWidth; + int finalParentHeight = Math.max(MeasureSpec.getSize(heightMeasureSpec), minParentHeight); + setMeasuredDimension(finalParentWidth, finalParentHeight); + + // 重新测量子View,确保所有尺寸生效 + measureChildren( + MeasureSpec.makeMeasureSpec(finalParentWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(finalParentHeight, MeasureSpec.EXACTLY) + ); } + /** + * 布局阶段(精准定位,确保装饰图完整显示、标签在头像下方-10dp) + */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); - // 在布局完成后设置标签的位置(在头像底部-8的位置) - if (changed && headerImageView != null && tagLabel != null) { - int tagTop = headerImageView.getBottom() - dpToPx(8); - int tagLeft = (getWidth() - tagLabel.getWidth()) / 2; - tagLabel.layout(tagLeft, tagTop, tagLeft + tagLabel.getWidth(), tagTop + tagLabel.getHeight()); + if (!changed || ivAvatar == null || ivAvatarDress == null || tvTag == null || tvName == null) { + return; + } + + // 1. 布局装饰图(底层,水平居中,垂直预留标签/名称空间) + int dressWidth = ivAvatarDress.getMeasuredWidth(); + int dressHeight = ivAvatarDress.getMeasuredHeight(); + int parentWidth = getMeasuredWidth(); + int parentHeight = getMeasuredHeight(); + int dressLeft = (parentWidth - dressWidth) / 2; // 水平居中,避免左右偏移 + // 垂直位置:父布局上半部分,预留标签+名称空间,避免装饰图靠下被裁 + int dressTop = (parentHeight - (dressHeight + dp2px(TAG_HEIGHT_DP + TAG_NAME_SPACING_DP))) / 2; + dressTop = Math.max(dressTop, 0); // 兜底,避免装饰图顶部超出父布局 + ivAvatarDress.layout(dressLeft, dressTop, dressLeft + dressWidth, dressTop + dressHeight); + + // 2. 布局头像(中层,在装饰图正中间,四周露4dp装饰图) + int avatarWidth = ivAvatar.getMeasuredWidth(); + int avatarHeight = ivAvatar.getMeasuredHeight(); + int avatarLeft = dressLeft + dressExceed; + int avatarTop = dressTop + dressExceed; + ivAvatar.layout(avatarLeft, avatarTop, avatarLeft + avatarWidth, avatarTop + avatarHeight); + + // 3. 布局标签(顶层,在头像下方-10dp,水平居中) + int tagWidth = tvTag.getMeasuredWidth(); + int tagHeight = tvTag.getMeasuredHeight(); + int tagLeft = (parentWidth - tagWidth) / 2; // 与头像水平对齐 + // 垂直位置:头像底部 + 向下偏移10dp(TAG_BOTTOM_OFFSET_DP=-10) + int tagTop = avatarTop + avatarHeight + dp2px(TAG_BOTTOM_OFFSET_DP); + tvTag.layout(tagLeft, tagTop, tagLeft + tagWidth, tagTop + tagHeight); + + // 4. 布局名称(顶层,在标签下方,保持3dp间距) + int nameWidth = tvName.getMeasuredWidth(); + int nameHeight = tvName.getMeasuredHeight(); + int nameLeft = (parentWidth - nameWidth) / 2; // 与标签水平对齐 + int nameTop = tagTop + tagHeight + dp2px(TAG_NAME_SPACING_DP); // 与标签保持间距 + // 兜底:避免名称底部超出父布局 + nameTop = Math.min(nameTop, parentHeight - nameHeight - dp2px(NAME_BOTTOM_MARGIN_DP)); + tvName.layout(nameLeft, nameTop, nameLeft + nameWidth, nameTop + nameHeight); + } + + /** + * 辅助方法:更新View尺寸(避免重复代码) + */ + private void updateViewSize(View view, int width, int height) { + LayoutParams params = (LayoutParams) view.getLayoutParams(); + if (params != null) { + params.width = width; + params.height = height; + view.setLayoutParams(params); } } + // ====================== 原有业务逻辑(保持不变) ====================== public void setIsLuckUser(boolean isLuckUser) { this.isLuckUser = isLuckUser; - if (isLuckUser) { - tagLabel.setTextColor(0xFFFFFFFF); // RGB16(0xffffff) - tagLabel.setBackground(getRoundedRectBackground(0xFF6C49E4, dpToPx(8))); // RGB16(0x6C49E4) -// tagLabel.setBackgroundColor(getResources().getColor(R.color.color_FF6C49E4)); // RGB16(0x6C49E4) - tagLabel.setText("幸运者"); + tvTag.setTextColor(0xFFFFFFFF); + tvTag.setBackground(getRoundedBg(0xFF6C49E4, dp2px(8))); + tvTag.setText("幸运者"); } else { - tagLabel.setTextColor(0xFFFFE554); // RGB16(0xFFE554) - tagLabel.setBackground(getRoundedRectBackground(0xFF8D6F28, dpToPx(8))); // RGB16(0x8D6F28) -// tagLabel.setBackgroundColor(getResources().getColor(R.color.color_FF8D6F28)); // RGB16(0x6C49E4) - - tagLabel.setText("房主"); + tvTag.setTextColor(0xFFFFE554); + tvTag.setBackground(getRoundedBg(0xFF8D6F28, dp2px(8))); + tvTag.setText("房主"); } } public void setModel(BlindBoxBean.xlhUser model) { - this.model = model; - // 这里需要根据您的 QXUserModel 类来实现具体逻辑 - if (model instanceof BlindBoxBean.xlhUser) { - BlindBoxBean.xlhUser userModel = (BlindBoxBean.xlhUser) model; - - // 使用图片加载库加载头像 -// Glide.with(getContext()).load(userModel.getAvatar()).into(headerImageView); - ImageUtils.loadHeadCC(userModel.getAvatar(), headerImageView); - nameLabel.setText(userModel.getNickname()); + this.userModel = model; + if (model != null) { + ImageUtils.loadHeadCC(model.getAvatar(), ivAvatar); + tvName.setText(model.getNickname() != null ? model.getNickname() : "虚位以待"); + } else { + resetView(); } } public void resetView() { - headerImageView.setImageResource(R.mipmap.default_avatar); - nameLabel.setText("虚位以待"); + if (ivAvatar != null) { + ivAvatar.setImageResource(R.mipmap.default_avatar); + } + if (tvName != null) { + tvName.setText("虚位以待"); + } + if (tvTag != null) { + tvTag.setText("房主"); + tvTag.setTextColor(0xFFFFE554); + tvTag.setBackground(getRoundedBg(0xFF8D6F28, dp2px(8))); + } } - // 创建圆角矩形背景 - private android.graphics.drawable.Drawable getRoundedRectBackground(int color, float radius) { + // ====================== 工具方法(保持不变) ====================== + private GradientDrawable getRoundedBg(int color, float radius) { GradientDrawable drawable = new GradientDrawable(); drawable.setColor(color); drawable.setCornerRadius(radius); + drawable.setPadding(dp2px(4), 0, dp2px(4), 0); return drawable; } - // 辅助方法:dp 转 px - private int dpToPx(int dp) { - float density = getResources().getDisplayMetrics().density; - return Math.round(dp * density); + private int dp2px(int dp) { + return Math.round(dp * getResources().getDisplayMetrics().density); } - // Getter 方法 - public ImageView getHeaderImageView() { - return headerImageView; - } - - public ImageView getDressImageView() { - return dressImageView; - } - - public TextView getTagLabel() { - return tagLabel; - } - - public TextView getNameLabel() { - return nameLabel; - } - - public boolean isLuckUser() { - return isLuckUser; - } - - public Object getModel() { - return model; - } - - // 自定义圆形 ImageView - private static class RoundImageView extends androidx.appcompat.widget.AppCompatImageView { - public RoundImageView(Context context) { - super(context); - } - - public RoundImageView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } - - public RoundImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @Override - protected void onDraw(Canvas canvas) { - // 创建圆形裁剪区域 - int diameter = Math.min(getWidth(), getHeight()); - Bitmap bitmap = Bitmap.createBitmap(diameter, diameter, Bitmap.Config.ARGB_8888); - Canvas tempCanvas = new Canvas(bitmap); - - // 绘制圆形 - Paint paint = new Paint(); - paint.setAntiAlias(true); - tempCanvas.drawCircle(diameter / 2f, diameter / 2f, diameter / 2f, paint); - - // 设置混合模式 - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); - - // 绘制原始图片 - super.onDraw(tempCanvas); - - // 将处理后的图片绘制到实际canvas - canvas.drawBitmap(bitmap, 0, 0, null); - } - } + // 临时变量:避免onLayout中重复计算dp值 + private int dressExceed = dp2px(DRESS_EXCEED_AVATAR_DP); } \ No newline at end of file diff --git a/moduleUtil/src/main/res/layout/fragment_tour_club_dialog.xml b/moduleUtil/src/main/res/layout/fragment_tour_club_dialog.xml index d88696a..18d2a25 100644 --- a/moduleUtil/src/main/res/layout/fragment_tour_club_dialog.xml +++ b/moduleUtil/src/main/res/layout/fragment_tour_club_dialog.xml @@ -97,8 +97,7 @@ + + \ No newline at end of file