自定义view, 图片右上角显示数字

先上效果图

自定义view

复制代码
arduino 复制代码
/**
 * Description : java类作用描述
 *

 * @since : 2026/1/7
 */


import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetrics;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.ImageView;

/**
 * 实现在图片右上角显示消息条数(无需设置padding)
 */
@SuppressLint("AppCompatCustomView")
public class PointImageView extends ImageView {

    /**
     * 默认模式
     */
    private int pointMode = NUMBER_POINT;

    // 1.不显示红点
    public static final int NO_POINT = 1;
    // 2.只显示一个红点,表示有新消息
    public static final int ONLY_POINT = 2;
    // 3.显示一个红点,红点中间还有消息的数量
    public static final int NUMBER_POINT = 3;

    // 消息的数量
    private int number = 0;

    // 记录当前是否有新消息(自动根据number判定)
    private boolean isHaveMessage = false;

    /**
     * 画圆的画笔
     */
    private Paint paint;

    /**
     * 画消息条数的画笔
     */
    private TextPaint paintText;

    // 角标半径(固定15dp,无需依赖padding)
    private float badgeRadius;
    // 文字大小(固定12sp)
    private float textSize;

    public PointImageView(Context context) {
        super(context);
        init(context);
    }

    public PointImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    /**
     * 初始化数据(适配dp/sp,移除padding依赖)
     */
    private void init(Context context) {
        // 转换尺寸:dp转px、sp转px(适配不同设备)
        badgeRadius = dp2px(context, 30); // 角标半径15dp(核心:固定大小,不依赖padding)
        textSize = sp2px(context, 30);    // 文字大小12sp

        // 初始化红点画笔
        paint = new Paint();
        paint.setStyle(Paint.Style.FILL); // 实心
        paint.setColor(0xffff0000);       // 红色
        paint.setAntiAlias(true);         // 抗锯齿

        // 初始化文字画笔
        paintText = new TextPaint();
        paintText.setColor(0xffffffff);   // 白色
        paintText.setTextSize(textSize);  // 设置文字大小
        paintText.setAntiAlias(true);
        paintText.setTextAlign(Paint.Align.CENTER); // 文字水平居中(简化计算)
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 无消息则不绘制
        if (!isHaveMessage) {
            return;
        }

        // 计算角标中心坐标:View右上角向内偏移radius(避免超出View)
        float badgeCenterX = getWidth() - badgeRadius;  // 右边缘 - 半径
        float badgeCenterY = badgeRadius;               // 上边缘 + 半径

        switch (pointMode) {
            case NO_POINT: // 不显示红点
                break;
            case ONLY_POINT: // 只显示红点
                canvas.drawCircle(badgeCenterX, badgeCenterY, badgeRadius, paint);
                break;
            case NUMBER_POINT: // 显示红点且带消息条数
                // 1. 绘制红色圆点
                canvas.drawCircle(badgeCenterX, badgeCenterY, badgeRadius, paint);
                // 2. 处理显示的文字
                String showText;
                if (number > 0 && number < 100) {
                    showText = String.valueOf(number);
                } else if (number >= 100) {
                    showText = "99+"; // 优化显示:99+ 更符合通用设计
                } else {
                    showText = "";
                }
                // 3. 计算文字垂直居中的基准线(核心:避免文字偏移)
                FontMetrics fm = paintText.getFontMetrics();
                // 垂直居中公式:中心Y坐标 - (字体上边界+下边界)/2
                float textBaseline = badgeCenterY - (fm.top + fm.bottom) / 2;
                // 4. 绘制文字(水平居中+垂直居中)
                canvas.drawText(showText, badgeCenterX, textBaseline, paintText);
                break;
        }
    }

    /**
     * 设置消息条数(自动判定是否有消息,无需手动调用setHaveMessage)
     */
    public void setMessageNum(int number) {
        this.number = number;
        // 自动更新:数字>0则显示红点/数字,否则不显示
        this.isHaveMessage = number > 0;
        invalidate(); // 触发重绘
    }

    /**
     * 手动控制是否显示(可选,优先用setMessageNum自动控制)
     */
    public void setHaveMessage(boolean isHaveMessage) {
        this.isHaveMessage = isHaveMessage;
        invalidate();
    }

    /**
     * 设置显示模式
     */
    public void setPointMode(int mode) {
        if (mode > 0 && mode <= 3) {
            pointMode = mode;
        } else {
            throw new RuntimeException("设置的模式有误,仅支持1-3");
        }
        invalidate(); // 模式变化后重绘
    }

    // 工具方法:dp转px
    private float dp2px(Context context, float dp) {
        return TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP,
                dp,
                context.getResources().getDisplayMetrics()
        );
    }

    // 工具方法:sp转px
    private float sp2px(Context context, float sp) {
        return TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_SP,
                sp,
                context.getResources().getDisplayMetrics()
        );
    }

    // 兼容旧方法名(避免调用方报错)
    @Deprecated
    public void setHaveMesage(boolean isHaveMesage) {
        setHaveMessage(isHaveMesage);
    }
}

使用

ini 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <com.amap.apis.cluster.testview.PointImageView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@drawable/test"
        android:id="@+id/imageView" />

</LinearLayout>

在Activity 中加载

scss 复制代码
private PointImageView mPointImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_customer_view_main);
    mPointImageView = findViewById(R.id.imageView);
    mPointImageView.setImageResource(R.drawable.test);
    mPointImageView.setMessageNum(10);
    mPointImageView.setPointMode(3);

    int sizeDp = 200;
    int sizePx = dp2px(sizeDp); // dp 转 px
    ViewGroup.LayoutParams params = new LinearLayout.LayoutParams(sizePx, sizePx);
    mPointImageView.setLayoutParams(params);
    mPointImageView.setPadding(50,50,50,50);

}
private int dp2px(float dp) {
    float density = getResources().getDisplayMetrics().density;
    return (int) (dp * density + 0.5f);
}
相关推荐
TheNextByte11 天前
将照片从Mac传输到Android 7 种可行方法
android·macos·gitee
青莲8431 天前
Java并发编程基础与进阶(线程·锁·原子类·通信)
android·前端·面试
2509_940880221 天前
MySQL Workbench菜单汉化为中文
android·数据库·mysql
_李小白1 天前
【Android FrameWork】延伸阅读: Android 进程管理
android
fatiaozhang95271 天前
万能通刷包_非高安版_海思MV300H/MV310_原机安卓4升级安卓9_全分区烧录包支持多无线及遥控_带adb权限(2026)
android·adb·电视盒子·刷机固件·机顶盒刷机·海思安卓4升级安卓9
梁正雄1 天前
linux服务-MariaDB 10.6 Galera Cluster 部署
android·数据库·mariadb
低调小一1 天前
Google A2UI 协议深度解析:AI 生成 UI 的机遇与实践(客户端视角,Android/iOS 都能落地)
android·人工智能·ui