安卓自定义颜色选择器

复制代码
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.PointF;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import com.example.uvideomanager.R;

public class ColorPickerView extends View {
    private static final String TAG = "ColorPickerView";
    private Context mContext;
    private Paint mBitmapPaint;
    // 色盘原图
    private Bitmap mPaletteBitmap;
    // 选择器圆点图片
    private Bitmap mSelectorBitmap;
    // 选择器半径
    private int mSelectorRadius;
    // 色盘圆心、色盘半径
    private int mPaletteCenterX, mPaletteCenterY;
    private float mPaletteRadius;
    // 当前选中坐标(画布坐标)
    private final PointF mSelectPoint = new PointF();
    // 色盘原始宽高
    private int mPaletteOriginW, mPaletteOriginH;
    // 十六进制颜色
    public String colorStr = "#FFFFFF";
    private OnColorBackListener mColorListener;

    public ColorPickerView(Context context) {
        this(context, null);
    }

    public ColorPickerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        initPaint();
        initBitmap();
        initDefaultSelectPos();
    }

    private void initPaint() {
        mBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mBitmapPaint.setFilterBitmap(true); // 图片平滑缩放
    }

    // 加载图片资源,增加空值判断
    private void initBitmap() {
        // 色盘 pcick
        BitmapFactory.Options opt = new BitmapFactory.Options();
        opt.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(getResources(), R.mipmap.pcick, opt);
        mPaletteOriginW = opt.outWidth;
        mPaletteOriginH = opt.outHeight;
        opt.inJustDecodeBounds = false;
        mPaletteBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.pcick, opt);

        // 选择器 poc
        mSelectorBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.poc);
        if (mSelectorBitmap != null) {
            mSelectorRadius = mSelectorBitmap.getWidth() / 2;
        }

        // 色盘圆心、半径
        mPaletteCenterX = mPaletteOriginW / 2;
        mPaletteCenterY = mPaletteOriginH / 2;
        mPaletteRadius = Math.min(mPaletteCenterX, mPaletteCenterY) - mSelectorRadius / 2f;
    }

    // 默认选中圆心
    private void initDefaultSelectPos() {
        mSelectPoint.set(mPaletteCenterX, mPaletteCenterY);
        getRGB();
    }

    public void setOnColorBackListener(OnColorBackListener listener) {
        mColorListener = listener;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 固定尺寸为色盘原图大小,避免拉伸导致坐标错位(核心修复点)
        setMeasuredDimension(mPaletteOriginW, mPaletteOriginH);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mPaletteBitmap == null || mPaletteBitmap.isRecycled()) return;
        // 原图1:1绘制,不拉伸,画布坐标=Bitmap像素坐标
        canvas.drawBitmap(mPaletteBitmap, 0, 0, mBitmapPaint);

        // 绘制选择圆点
        if (mSelectorBitmap != null && !mSelectorBitmap.isRecycled()) {
            float drawX = mSelectPoint.x - mSelectorRadius;
            float drawY = mSelectPoint.y - mSelectorRadius;
            canvas.drawBitmap(mSelectorBitmap, drawX, drawY, mBitmapPaint);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        // 边界校正,限制在色盘圆形内
        clampPointToPaletteCircle(x, y);
        invalidate();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_UP:
                getRGB();
                break;
        }
        return true;
    }

    // 【重构】向量归一化校正坐标,替代原来错误的三角函数象限算法
    private void clampPointToPaletteCircle(float touchX, float touchY) {
        // 圆心到触摸点向量
        float dx = touchX - mPaletteCenterX;
        float dy = touchY - mPaletteCenterY;
        // 两点距离
        float dist = (float) Math.hypot(dx, dy);

        if (dist <= mPaletteRadius) {
            // 在圆内,直接使用触摸坐标
            mSelectPoint.set(touchX, touchY);
        } else {
            // 在圆外,归一化向量贴到圆环边缘
            float scale = mPaletteRadius / dist;
            float finalX = mPaletteCenterX + dx * scale;
            float finalY = mPaletteCenterY + dy * scale;
            mSelectPoint.set(finalX, finalY);
        }
    }

    // 取色:画布坐标=Bitmap像素坐标,不再越界
    private void getRGB() {
        if (mPaletteBitmap == null || mPaletteBitmap.isRecycled()) return;
        int px = (int) mSelectPoint.x;
        int py = (int) mSelectPoint.y;
        // 坐标边界保护,防止越界
        px = Math.max(0, Math.min(px, mPaletteOriginW - 1));
        py = Math.max(0, Math.min(py, mPaletteOriginH - 1));

        int pixel = mPaletteBitmap.getPixel(px, py);
        int a = Color.alpha(pixel);
        int r = Color.red(pixel);
        int g = Color.green(pixel);
        int b = Color.blue(pixel);

        colorStr = "#" + toHex2(r) + toHex2(g) + toHex2(b);
        if (mColorListener != null) {
            mColorListener.onColorBack(a, r, g, b);
        }
    }

    // 转两位十六进制
    private String toHex2(int num) {
        String s = Integer.toHexString(num & 0xFF).toUpperCase();
        return s.length() == 1 ? "0" + s : s;
    }

    // 回收资源
    @Override
    protected void onDetachedFromWindow() {
        recycleBitmap(mPaletteBitmap);
        recycleBitmap(mSelectorBitmap);
        super.onDetachedFromWindow();
    }

    private void recycleBitmap(Bitmap bitmap) {
        if (bitmap != null && !bitmap.isRecycled()) {
            bitmap.recycle();
        }
    }

    public String getColorStr() {
        return colorStr;
    }

    public void setColorStr(String colorStr) {
        this.colorStr = colorStr;
    }

    public interface OnColorBackListener {
        void onColorBack(int a, int r, int g, int b);
    }
}

//布局
<ImageView
   android:id="@+id/img_sett"
   android:layout_width="match_parent"
   android:layout_height="50dp" />

<com.example.uvideomanager.view.ColorPickerView
    android:id="@+id/colorPickerView"
    android:layout_width="wrap_content"
    android:layout_gravity="center"
    android:layout_height="wrap_content" />



// 颜色选择回调使用
colorPickerView.setOnColorBackListener { a, r, g, b ->
    img_sett.setBackgroundColor(Color.argb(a, r, g, b))
}

效果

原图自取

相关推荐
大圣编蚕7 小时前
初探 NDK 的世界:从零开始理解 Android 原生开发
android
云贝贝贝7 小时前
MySQL 慢查询定位与索引优化实战
android
骑着蜗牛撵大象3278 小时前
服务下线不再炸:分层关闭、TCP 存活探测与负载均衡排水的落地套路
android·tcp/ip·负载均衡·tcp·高可用·健康检查·优雅关闭
三少爷的鞋10 小时前
别再这么写协程了!Marcin Moskała 剖析的 几个常见协程误区与重构
android
mmsx11 小时前
Android 防二次打包第一道锁:签名校验与完整性校验
android·kotlin
mmsx12 小时前
Android 混淆不等于安全:二次打包链路完整走一遍
android·kotlin
小宋102113 小时前
Agent轨迹级评测实战:工具选择、预算超限与回归门禁
android·网络·人工智能·回归
墨天梦15 小时前
D05_ViewModel与单向数据流
android·kotlin
蒸鱼Yuzheng17 小时前
Android 构建可复现性:APK 指纹、文件级差异与供应链审计
android·apk·devops·软件供应链·可复现构建
墨天梦19 小时前
D03_Compose列表与稳定身份
android·gitee·kotlin