Android 自定义View取色盘、颜色选择器、可根据颜色值反向定位坐标 - Go语言中文社区

Android 自定义View取色盘、颜色选择器、可根据颜色值反向定位坐标


 

前言:

前段时间项目中需要用到色盘取色的功能,百度了许多相关的颜色选择器,发现这方面自定义View例子比较少,有用图片代替色盘的通过bitmap取色,但是只能取色,无法通过颜色值去定位在色盘上的坐标,由于没有找到适用的,所以自己根据需求花了一些时间写了个,本着不重复造轮子的原则,于是便将原来的删除一些多余的代码,在这里分享一下。

效果图:

 

首先是绘制中间的圆形色盘,因为需要不仅能通过触摸的屏幕坐标获取选中的颜色,还需要通过指定的int型color值来获取到颜色在色盘中所处的坐标,即通过颜色值反向定位坐标。所以在这里使用到了HVS颜色空间模型。

关于HVS颜色模型:

这个模型中颜色的参数分别是:色调(H),饱和度(S),明度(V)。

而在我们的色盘中这里则分别是:角度(H),半径(S),明度(V)不需要用上,有兴趣的可以去网上这个模型的相关资料。

 

通过HSV模型绘制中间的色盘:

//创建色盘Bitmap
    private Bitmap createColorWheelBitmap(int width, int height) {
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        int colorCount = 12;
        int colorAngleStep = 360 / colorCount;
        int colors[] = new int[colorCount + 1];
        float hsv[] = new float[]{0f, 1f, 1f};
        for (int i = 0; i < colors.length; i++) {
            hsv[0] = 360 - (i * colorAngleStep) % 360;
            colors[i] = Color.HSVToColor(hsv);
        }
        colors[colorCount] = colors[0];
        SweepGradient sweepGradient = new SweepGradient(width / 2, height / 2, colors, null);
        RadialGradient radialGradient = new RadialGradient(width / 2, height / 2, colorWheelRadius, 0xFFFFFFFF, 0x00FFFFFF, Shader.TileMode.CLAMP);
        ComposeShader composeShader = new ComposeShader(sweepGradient, radialGradient, PorterDuff.Mode.SRC_OVER);
        colorWheelPaint.setShader(composeShader);
        Canvas canvas = new Canvas(bitmap);
        canvas.drawCircle(width / 2, height / 2, colorWheelRadius, colorWheelPaint);

        //默认取圆心颜色,给一个默认颜色用于显示点标记
        currentColor = getColorAtPoint(markerPoint.x, markerPoint.y);
        return bitmap;
    }

 绘制色盘后白色圆形

private void drawWhiteWheel(Canvas canvas) {
        //绘制白色圆圈
        whiteWheelPaint.setColor(Color.WHITE);
        canvas.drawCircle(centerWheelX, centerWheelY, whiteWheelRadius, whiteWheelPaint);
    }

 

绘制:

  //绘制白色圆圈
  drawWhiteWheel(canvas);

  colorWheelBitmap = createColorWheelBitmap(colorWheelRadius * 2, colorWheelRadius * 2);
  //绘制色盘
  canvas.drawBitmap(colorWheelBitmap, mColorWheelRect.left, mColorWheelRect.top, null);

接下来绘制外圈色环:

和绘制色盘的方式一样,只不过将绘制圆形的画笔样式改为描边,并设置画笔宽度。

 //设置画笔为描边 
 colorRingPaint.setStyle(Paint.Style.STROKE);

 colorRingBitmap = createColorRingBitmap(colorRingRadius * 2 + ringWidth, colorRingRadius * 2 + ringWidth);
  //绘制外圈彩色圆环
  canvas.drawBitmap(colorRingBitmap, mColorRingRect.left, mColorRingRect.top, null);
//创建彩色圆环bitmap
    private Bitmap createColorRingBitmap(int width, int height) {
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        int colorCount = 12;
        int colorAngleStep = 360 / colorCount;
        int colors[] = new int[colorCount + 1];
        float hsv[] = new float[]{0f, 1f, 1f};
        for (int i = 0; i < colors.length; i++) {
            hsv[0] = 360 - (i * colorAngleStep) % 360;
            colors[i] = Color.HSVToColor(hsv);
        }
        colors[colorCount] = colors[0];
        SweepGradient sweepGradient = new SweepGradient(width / 2, height / 2, colors, null);
        RadialGradient radialGradient = new RadialGradient(width / 2, height / 2, colorRingRadius, 0xFFFFFFFF, 0x00FFFFFF, Shader.TileMode.CLAMP);
        ComposeShader composeShader = new ComposeShader(sweepGradient, radialGradient, PorterDuff.Mode.SRC_OVER);
        colorRingPaint.setShader(composeShader);
        Canvas canvas = new Canvas(bitmap);
        canvas.drawCircle(width / 2, height / 2, colorRingRadius, colorRingPaint);
        return bitmap;
    }

然后绘制色盘上的颜色点标记:

  //绘制点标记
 drawMarker(canvas);

private void drawMarker(Canvas canvas) {
        float markerWidth = markerBitmap.getWidth();
        float markerHeight = markerBitmap.getHeight();
        // 指定图片在屏幕上显示的区域(原图大小)
        float left = (markerPoint.x - (markerWidth / 2));//markerPoint 为当前所触摸的点
        float top = (markerPoint.y - markerHeight) + markerHeight * 1 / 10;
        RectF dst = new RectF(left, top, left + markerWidth, top + markerHeight);
        //点标记上的颜色显示
        float markerRadius = markerWidth / 3;
        markerPaint.setColor(currentColor);//设置颜色为当前颜色
        canvas.drawBitmap(markerBitmap, null, dst, null);//绘制点标记图片
        canvas.drawCircle(left + markerWidth / 2, top + markerWidth / 2 - 4, markerRadius, markerPaint);//绘制点标记中的变色小圆
    }

 

还需要绘制外圈彩色圆圆环上的滑动按钮,这里使用了Matrix矩阵将按钮图片平移到了彩色圆环上,使用Matrix目的是为了通过旋转的方式去改变滑动按钮的位置,使箭头方向准确

 private void drawColorRingBtn(Canvas canvas) {
        int colorRingBtnWidth = colorRingBtnBitmap.getWidth();
        int colorRingBtnHeight = colorRingBtnBitmap.getHeight();
        int left = centerWheelX - colorRingBtnWidth;
        int top = centerWheelY - colorRingRadius - colorRingBtnHeight;
        // colorRingBtnRect = new RectF(left, top, left + colorRingBtnWidth, top + colorRingBtnHeight);
        colorRingBtnPoint.x = left + colorRingBtnWidth / 2;
        colorRingBtnPoint.y = top + colorRingBtnHeight / 2;
        colorRingMatrix.preTranslate(colorRingBtnPoint.x, colorRingBtnPoint.y);
        // canvas.drawBitmap(colorRingBtnBitmap, null, colorRingBtnRect, null);
        canvas.drawBitmap(colorRingBtnBitmap, colorRingMatrix, null);
        colorRingMatrix.reset();
    }

效果图:

 

最后需要的便是获取色盘以及彩色圆环上的触摸事件,让它们可移动起来并实时获取颜色值。

重写onTouchEvent方法:

 private static int colorTmp;///用于判断颜色是否发生改变
    private PointF downPointF = new PointF();//按下的位置
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getActionMasked();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                colorTmp = currentColor;
                downPointF.x = event.getX();
                downPointF.y = event.getY();
            case MotionEvent.ACTION_MOVE:
                update(event);
                return true;
            case MotionEvent.ACTION_UP:
                if (colorTmp != currentColor) {
                    onColorPickerChanger();
                }
                break;
            default:
                return true;
        }
        return super.onTouchEvent(event);
    }
 private void update(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        updateSelector(x, y);
        updateRingSelector(x, y);
    }

private void onColorPickerChanger() {
        if (onColorPickerChangerListener != null) {
            int red = (currentColor & 0xff0000) >> 16;
            int green = (currentColor & 0x00ff00) >> 8;
            int blue = (currentColor & 0x0000ff);
            onColorPickerChangerListener.onColorPickerChanger(currentColor, red, green, blue);
        }

    }

色盘选择颜色:

  /**
     * 刷新s色盘所选择的颜色
     * @param eventX
     * @param eventY
     */
    private void updateSelector(float eventX, float eventY) {
        float x = eventX - centerWheelX;
        float y = eventY - centerWheelY;
        double r = Math.sqrt(x * x + y * y);
        //判断是否在圆内
        if (r > colorWheelRadius) {
            //不在圆形范围内
            return;
        }
        //同时旋转外圈滑动按钮
        colorRingMatrix.preRotate(getRotationBetweenLines(centerWheelX, centerWheelY, eventX, eventY), centerWheelX, centerWheelY);
        currentPoint.x = x + centerWheelX;
        currentPoint.y = y + centerWheelY;
        markerPoint.x = currentPoint.x;//改变点标记位置
        markerPoint.y = currentPoint.y;
        currentColor = getColorAtPoint(eventX, eventY);//获取到的颜色
        invalidate();
    }


    /**
     * 获取两条线的夹角
     *
     * @param centerX
     * @param centerY
     * @param xInView
     * @param yInView
     * @return
     */
    public static int getRotationBetweenLines(float centerX, float centerY, float xInView, float yInView) {
        double rotation = 0;
        double k1 = (double) (centerY - centerY) / (centerX * 2 - centerX);
        double k2 = (double) (yInView - centerY) / (xInView - centerX);
        double tmpDegree = Math.atan((Math.abs(k1 - k2)) / (1 + k1 * k2)) / Math.PI * 180;

        if (xInView > centerX && yInView < centerY) {  //第一象限
            rotation = 90 - tmpDegree;
        } else if (xInView > centerX && yInView > centerY) //第二象限
        {
            rotation = 90 + tmpDegree;
        } else if (xInView < centerX && yInView > centerY) { //第三象限
            rotation = 270 - tmpDegree;
        } else if (xInView < centerX && yInView < centerY) { //第四象限
            rotation = 270 + tmpDegree;
        } else if (xInView == centerX && yInView < centerY) {
            rotation = 0;
        } else if (xInView == centerX && yInView > centerY) {
            rotation = 180;
        }

        return (int) rotation;
    }

    /**
     *  根据坐标获取颜色
     * @param eventX
     * @param eventY
     * @return
     */
    private int getColorAtPoint(float eventX, float eventY) {
        float x = eventX - centerWheelX;
        float y = eventY - centerWheelY;
        double r = Math.sqrt(x * x + y * y);
        float[] hsv = {0, 0, 1};
        hsv[0] = (float) (Math.atan2(y, -x) / Math.PI * 180f) + 180;
        hsv[1] = Math.max(0f, Math.min(1f, (float) (r / colorWheelRadius)));
        return Color.HSVToColor(hsv);
    }

色环颜色选择:

/**
     * 刷新色环选择
     *
     * @param eventX
     * @param eventY
     */
    private void updateRingSelector(float eventX, float eventY) {
        float x = downPointF.x - centerWheelX;
        float y = downPointF.y - centerWheelY;
        double r = Math.sqrt(x * x + y * y);//按下位置的半径
        //判断是否在圆内,或者色环上
        if ((r < colorRingRadius + ringWidth && r > colorRingRadius - ringWidth)) {
            colorRingMatrix.preRotate(getRotationBetweenLines(centerWheelX, centerWheelY, eventX, eventY), centerWheelX, centerWheelY);
            currentColor = getColorAtPoint(eventX, eventY);//int值颜色
            float[] hsv = getHSVColorAtPoint(eventX, eventY);//hsv值颜色
            float h = hsv[0];//hsv色盘色点角度
            float s = hsv[1];//hsv色盘色点相对于半径的比值
            float colorDotRadius = colorWheelRadius * s;//色点半径
            //根据角度和半径获取坐标
            float radian = (float) Math.toRadians(-h);
            float colorDotX = (float) (centerWheelX + Math.cos(radian) * colorDotRadius);
            float colorDotY = (float) (centerWheelY + Math.sin(radian) * colorDotRadius);
            markerPoint.x = colorDotX;同时改变色盘上点标记的位置
            markerPoint.y = colorDotY;
            invalidate();
        }
    }


 /**
     * 根据坐标获取HSV颜色值
     * @param eventX
     * @param eventY
     * @return
     */
    private float[] getHSVColorAtPoint(float eventX, float eventY) {
        float x = eventX - centerWheelX;
        float y = eventY - centerWheelY;
        double r = Math.sqrt(x * x + y * y);
        float[] hsv = {0, 0, 1};
        hsv[0] = (float) (Math.atan2(y, -x) / Math.PI * 180f) + 180;
        hsv[1] = Math.max(0f, Math.min(1f, (float) (r / colorWheelRadius)));
        return hsv;

    }

最后贴一下设置颜色改变滑动按钮和点标记的方法,和上面的代码是一样的。

    public void setColor(int color) {
        float[] hsv = {0, 0, 1};
        Color.colorToHSV(color, hsv);
        //根据hsv角度及半径获取坐标
        //根据角度和半径获取坐标
        float radian = (float) Math.toRadians(-hsv[0]);
        float colorDotRadius = hsv[1] * colorWheelRadius;
        float colorDotX = (float) (centerWheelX + Math.cos(radian) * colorDotRadius);
        float colorDotY = (float) (centerWheelY + Math.sin(radian) * colorDotRadius);
        //设置marker位置
        markerPoint.x = colorDotX;
        markerPoint.y = colorDotY;
        currentColor = getColorAtPoint(markerPoint.x, markerPoint.y);//设置当前颜色
        //设置色环按钮位置
        colorRingMatrix.preRotate(getRotationBetweenLines(centerWheelX, centerWheelY, markerPoint.x, markerPoint.y), centerWheelX, centerWheelY);
        invalidate();
        // paint.setColor(Color.rgb(red, green, blue)); 
    }

总结:

主要的实现原理其实就是通过HSV颜色模型绘制色盘,然后通过HSV颜色空间的坐标对应到色盘上,以圆心为原点H(0-360)对应角度,S(0-255)对应半径,V(明度)用不上,通过坐标系的方式取HSV模型上的颜色,这样取到的颜色值会更加的准确。反过来又根据HSV颜色值计算在模型上的坐标,对应计算出该颜色值在色盘上的坐标,即可实现通过颜色值反向定位坐标位置。

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/tg_0804/article/details/98965911
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-07 22:14:40
  • 阅读 ( 1178 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢