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))
}
效果

原图自取

