安卓自定义颜色选择器

复制代码
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))
}

效果

原图自取

相关推荐
alexhilton1 小时前
藏在设备上的秘密,终究藏不住
android·kotlin·android jetpack
hai_android7 小时前
Kotlin / Android 常用函数使用示例手册
android·java·kotlin
hai_android9 小时前
Android MeasureSpec 详解
android·java·kotlin
又见情义10 小时前
RK3568 Android 13 本地 U 盘 OTA 升级实战:基于 RKUpdateService 的完整流程
android
Godikov11 小时前
Android10后台弹窗与APK自动更新
android
又见情义12 小时前
RK3568 Android 13 USB OTG/Host 切换调试经验分享
android
终端安全笔记13 小时前
iOS 27 之后「策略空转」:设备升级不报错,但旧策略不再管它
android·网络·安全·ios·智能手机
JMchen14 小时前
实战案例:实现120fps流畅的渐变进度条
android·kotlin·canvas
敲代码的瓦龙14 小时前
Jetpack?DataBinding!!!
android·java·开发语言·mysql·android-studio
Android-Flutter16 小时前
Compose CompositionLocal 详解
android·compose