1.定位的问题

This commit is contained in:
2025-09-26 01:18:18 +08:00
committed by 梁小江
parent a883aa86e5
commit 393c59dd1b
13 changed files with 787 additions and 9 deletions

View File

@@ -0,0 +1,119 @@
package com.xscm.moduleutil.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import androidx.appcompat.widget.AppCompatImageView;
import com.xscm.moduleutil.R;
public class AvatarWithDecoration extends AppCompatImageView {
// 挂件图片
private Drawable decoration;
// 挂件位置偏移量
private int decorationOffsetX = 0;
private int decorationOffsetY = 0;
// 挂件大小比例(相对于头像)
private float decorationScale = 0.3f;
public AvatarWithDecoration(Context context) {
super(context);
init();
}
public AvatarWithDecoration(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public AvatarWithDecoration(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AvatarWithDecoration);
decoration = a.getDrawable(R.styleable.AvatarWithDecoration_decoration);
decorationScale = a.getFloat(R.styleable.AvatarWithDecoration_decorationScale, 0.3f);
decorationOffsetX = a.getInt(R.styleable.AvatarWithDecoration_decorationOffsetX, 0);
decorationOffsetY = a.getInt(R.styleable.AvatarWithDecoration_decorationOffsetY, 0);
a.recycle();
}
private void init() {
// 可以在这里设置默认的挂件
decoration = getResources().getDrawable(R.mipmap.xlh_image);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 如果有挂件,则绘制挂件
if (decoration != null) {
drawDecoration(canvas);
}
}
private void drawDecoration(Canvas canvas) {
// 获取头像的宽高
int avatarWidth = getWidth();
int avatarHeight = getHeight();
// 计算挂件的大小
int decorationWidth = (int) (avatarWidth * decorationScale);
int decorationHeight = (int) (avatarHeight * decorationScale);
// 计算挂件的位置(右下角)
int left = avatarWidth - decorationWidth + decorationOffsetX;
int top = avatarHeight - decorationHeight + decorationOffsetY;
int right = left + decorationWidth;
int bottom = top + decorationHeight;
// 设置挂件的绘制边界
decoration.setBounds(left, top, right, bottom);
// 绘制挂件
decoration.draw(canvas);
}
/**
* 设置挂件图片
*/
public void setDecoration(Drawable decoration) {
this.decoration = decoration;
invalidate();
}
/**
* 设置挂件图片通过Bitmap
*/
public void setDecoration(Bitmap bitmap) {
if (bitmap != null) {
this.decoration = new BitmapDrawable(getResources(), bitmap);
invalidate();
}
}
/**
* 设置挂件位置偏移量
*/
public void setDecorationOffset(int offsetX, int offsetY) {
this.decorationOffsetX = offsetX;
this.decorationOffsetY = offsetY;
invalidate();
}
/**
* 设置挂件大小比例
*/
public void setDecorationScale(float scale) {
if (scale > 0 && scale <= 1) {
this.decorationScale = scale;
invalidate();
}
}
}

View File

@@ -0,0 +1,373 @@
package com.xscm.moduleutil.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import androidx.annotation.Nullable;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.SimpleTarget;
import com.bumptech.glide.request.transition.Transition;
import com.xscm.moduleutil.R;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class FashionAvatarView extends View {
// 头像相关属性
private Bitmap mAvatarBitmap;
private String mAvatarUrl;
private Drawable mPlaceholderAvatar;
private int mAvatarRadius;
private int mAvatarBorderWidth;
private int mAvatarBorderColor;
// 顶部标签属性
private String mTagText;
private int mTagTextColor;
private float mTagTextSize;
private int mTagBackgroundColor;
private float mTagCornerRadius;
private int mTagPadding;
private int mTagOffsetY; // 标签Y轴偏移量
// 底部文字属性
private String mBottomText;
private int mBottomTextColor;
private float mBottomTextSize;
private int mBottomTextOffsetY; // 底部文字Y轴偏移量
// 爱心装饰属性
private Drawable mHeartIcon;
private int mHeartCount;
private int mHeartSize;
private List<PointF> mHeartPositions = new ArrayList<>();
private Random mRandom = new Random();
// 画笔
private Paint mAvatarPaint;
private Paint mTextPaint;
private Paint mTagBgPaint;
public FashionAvatarView(Context context) {
this(context, null);
}
public FashionAvatarView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public FashionAvatarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context, attrs);
initPaints();
}
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.FashionAvatarView);
// 头像属性
mAvatarUrl = ta.getString(R.styleable.FashionAvatarView_avatarUrl);
mPlaceholderAvatar = ta.getDrawable(R.styleable.FashionAvatarView_placeholderAvatar);
mAvatarRadius = ta.getDimensionPixelSize(R.styleable.FashionAvatarView_avatarRadius, dp2px(60));
mAvatarBorderWidth = ta.getDimensionPixelSize(R.styleable.FashionAvatarView_avatarBorderWidth, dp2px(2));
mAvatarBorderColor = ta.getColor(R.styleable.FashionAvatarView_avatarBorderColor, Color.parseColor("#FFD700"));
// 顶部标签属性
mTagText = ta.getString(R.styleable.FashionAvatarView_tagText);
mTagTextColor = ta.getColor(R.styleable.FashionAvatarView_tagTextColor, Color.WHITE);
mTagTextSize = ta.getDimension(R.styleable.FashionAvatarView_tagTextSize, sp2px(12));
mTagBackgroundColor = ta.getColor(R.styleable.FashionAvatarView_tagBackgroundColor, Color.parseColor("#FFA500"));
mTagCornerRadius = ta.getDimension(R.styleable.FashionAvatarView_tagCornerRadius, dp2px(4));
mTagPadding = ta.getDimensionPixelSize(R.styleable.FashionAvatarView_tagPadding, dp2px(4));
mTagOffsetY = ta.getDimensionPixelSize(R.styleable.FashionAvatarView_tagOffsetY, 0);
// 底部文字属性
mBottomText = ta.getString(R.styleable.FashionAvatarView_bottomText);
mBottomTextColor = ta.getColor(R.styleable.FashionAvatarView_bottomTextColor, Color.WHITE);
mBottomTextSize = ta.getDimension(R.styleable.FashionAvatarView_bottomTextSize, sp2px(14));
mBottomTextOffsetY = ta.getDimensionPixelSize(R.styleable.FashionAvatarView_bottomTextOffsetY, dp2px(10));
// 爱心装饰属性
mHeartIcon = ta.getDrawable(R.styleable.FashionAvatarView_heartIcon);
if (mHeartIcon == null) {
try {
mHeartIcon = context.getResources().getDrawable(R.mipmap.xlh_image);
} catch (Exception e) {
e.printStackTrace();
}
}
mHeartCount = ta.getInt(R.styleable.FashionAvatarView_heartCount, 6);
mHeartSize = ta.getDimensionPixelSize(R.styleable.FashionAvatarView_heartSize, dp2px(16));
ta.recycle();
// 加载头像
if (mAvatarUrl != null && !mAvatarUrl.isEmpty()) {
loadAvatarFromNetwork();
} else if (mPlaceholderAvatar != null) {
mAvatarBitmap = drawableToBitmap(mPlaceholderAvatar);
}
}
private void initPaints() {
// 头像画笔
mAvatarPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mAvatarPaint.setDither(true);
// 文字画笔
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setTextAlign(Paint.Align.CENTER);
// 标签背景画笔
mTagBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTagBgPaint.setColor(mTagBackgroundColor);
}
private void loadAvatarFromNetwork() {
Glide.with(this)
.asBitmap()
.load(mAvatarUrl)
.into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(Bitmap resource, Transition<? super Bitmap> transition) {
mAvatarBitmap = resource;
invalidate();
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
super.onLoadFailed(errorDrawable);
if (mPlaceholderAvatar != null) {
mAvatarBitmap = drawableToBitmap(mPlaceholderAvatar);
}
invalidate();
}
});
}
private Bitmap drawableToBitmap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
if (drawable == null) {
return null;
}
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
if (width <= 0) width = mAvatarRadius * 2;
if (height <= 0) height = mAvatarRadius * 2;
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
private void initHeartPositions() {
mHeartPositions.clear();
if (mHeartIcon == null || mHeartCount <= 0 || mHeartSize <= 0) {
return;
}
int centerX = getWidth() / 2;
int centerY = getAvatarCenterY();
// 爱心围绕的半径,比头像大一些
int radius = mAvatarRadius + mAvatarBorderWidth + mHeartSize / 2;
for (int i = 0; i < mHeartCount; i++) {
// 随机分布在头像周围
double angle = 2 * Math.PI * mRandom.nextDouble();
// 稍微随机调整距离,让分布更自然
float distanceFactor = 0.8f + mRandom.nextFloat() * 0.4f;
float x = (float) (centerX + radius * distanceFactor * Math.cos(angle));
float y = (float) (centerY + radius * distanceFactor * Math.sin(angle));
mHeartPositions.add(new PointF(x, y));
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 计算宽度:直径 + 左右可能的爱心空间
int width = 2 * (mAvatarRadius + mAvatarBorderWidth + mHeartSize / 2);
// 计算高度:头像直径 + 标签高度 + 底部文字高度 + 间距
int tagHeight = (int) (mTagTextSize + mTagPadding * 2);
int bottomTextHeight = (int) mBottomTextSize;
int height = 2 * (mAvatarRadius + mAvatarBorderWidth)
+ tagHeight / 2 // 标签一半在头像内
+ bottomTextHeight + mBottomTextOffsetY
+ dp2px(10);
setMeasuredDimension(resolveSize(width, widthMeasureSpec),
resolveSize(height, heightMeasureSpec));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 视图大小确定后计算爱心位置
initHeartPositions();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (getWidth() == 0 || getHeight() == 0) {
return;
}
int centerX = getWidth() / 2;
int avatarCenterY = getAvatarCenterY();
// 1. 绘制爱心装饰
drawHearts(canvas);
// 2. 绘制头像边框
mAvatarPaint.setColor(mAvatarBorderColor);
mAvatarPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(centerX, avatarCenterY, mAvatarRadius + mAvatarBorderWidth, mAvatarPaint);
// 3. 绘制头像
if (mAvatarBitmap != null) {
mAvatarPaint.setShader(new BitmapShader(mAvatarBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
canvas.drawCircle(centerX, avatarCenterY, mAvatarRadius, mAvatarPaint);
mAvatarPaint.setShader(null);
}
// 4. 绘制顶部标签
drawTag(canvas, centerX, avatarCenterY);
// 5. 绘制底部文字
drawBottomText(canvas, centerX);
}
private int getAvatarCenterY() {
// 计算头像中心Y坐标考虑标签的高度
int tagHeight = (int) (mTagTextSize + mTagPadding * 2);
return mAvatarRadius + mAvatarBorderWidth + tagHeight / 2 + mTagOffsetY;
}
private void drawHearts(Canvas canvas) {
if (mHeartIcon == null || mHeartPositions.isEmpty()) {
return;
}
canvas.save();
for (PointF point : mHeartPositions) {
int left = (int) (point.x - mHeartSize / 2);
int top = (int) (point.y - mHeartSize / 2);
int right = left + mHeartSize;
int bottom = top + mHeartSize;
// 只绘制在视图范围内的爱心
if (right > 0 && bottom > 0 && left < getWidth() && top < getHeight()) {
mHeartIcon.setBounds(left, top, right, bottom);
mHeartIcon.draw(canvas);
}
}
canvas.restore();
}
private void drawTag(Canvas canvas, int centerX, int avatarCenterY) {
if (mTagText == null || mTagText.isEmpty()) {
return;
}
// 计算标签文字宽度
mTextPaint.setTextSize(mTagTextSize);
float textWidth = mTextPaint.measureText(mTagText);
// 计算标签背景矩形
float tagLeft = centerX - textWidth / 2 - mTagPadding;
float tagRight = centerX + textWidth / 2 + mTagPadding;
// 标签底部与头像顶部对齐
float tagBottom = avatarCenterY - mAvatarRadius - mAvatarBorderWidth;
float tagTop = tagBottom - mTagTextSize - mTagPadding * 2;
RectF tagRect = new RectF(tagLeft, tagTop, tagRight, tagBottom);
// 绘制标签背景
canvas.drawRoundRect(tagRect, mTagCornerRadius, mTagCornerRadius, mTagBgPaint);
// 绘制标签文字
mTextPaint.setColor(mTagTextColor);
Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
float baseLineY = tagBottom - mTagPadding - fontMetrics.bottom;
canvas.drawText(mTagText, centerX, baseLineY, mTextPaint);
}
private void drawBottomText(Canvas canvas, int centerX) {
if (mBottomText == null || mBottomText.isEmpty()) {
return;
}
// 计算文字位置:头像底部下方
int textY = getAvatarCenterY() + mAvatarRadius + mAvatarBorderWidth + mBottomTextOffsetY;
mTextPaint.setColor(mBottomTextColor);
mTextPaint.setTextSize(mBottomTextSize);
Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
float baseLineY = textY - fontMetrics.top;
canvas.drawText(mBottomText, centerX, baseLineY, mTextPaint);
}
// dp转px
private int dp2px(float dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
getResources().getDisplayMetrics());
}
// sp转px
private float sp2px(float sp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp,
getResources().getDisplayMetrics());
}
// 设置器方法
public void setAvatarUrl(String url) {
this.mAvatarUrl = url;
loadAvatarFromNetwork();
}
public void setTagText(String text) {
this.mTagText = text;
invalidate();
}
public void setBottomText(String text) {
this.mBottomText = text;
invalidate();
}
public void setHeartIcon(Drawable heartIcon) {
this.mHeartIcon = heartIcon;
initHeartPositions();
invalidate();
}
}