美文网首页Android技术知识杂文精选我们就爱程序媛
【Android自定义View】仿Photoshop取色器Col

【Android自定义View】仿Photoshop取色器Col

作者: 李景三 | 来源:发表于2017-08-17 22:45 被阅读119次

    ColorPicker

    一款仿Photoshop取色器的Android版取色器。

    github地址:https://github.com/relish-wang/ColorPicker

    前言

    上一篇已经简单介绍了ColorPicker的项目结构以及两种颜色空间,接下来我们详细解析一下ColorPicker的核心自定义控件ColorPickerView

    ColorPickerView

    在阅读代码之前,我们先看一下ColorPicker的布局以及一些标注的数值在代码里的变量名称。


    介绍 变量名

    变量名标注的的大图

    阅读一个自定义View的代码,只需记住四步走:构造方法onMeasureonLayoutonDraw

    构造方法

    public ColorPickerView(Context context) {
        this(context, null);
    }
    
    public ColorPickerView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    
    public ColorPickerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }
    

    可见,前2个构造器最后都调用了第三个构造器。而第三个构造器里的主要内容都在一个名为init()的方法。

    private void init() {
        mDensity = getContext().getResources().getDisplayMetrics().density;//获取屏幕密度
        mSVTrackerRadius *= mDensity;//灰度饱和度指示器的半径
        mRectOffset *= mDensity;//H、SV矩形与父布局的边距
        mHuePanelWidth *= mDensity;//H矩形的宽度
        mPanelSpacing *= mDensity;//H、SV矩形间的间距
        mPreferredHeight *= mDensity;//当mode为MeasureSpec.UNSPECIFIED时的首选高度
        mPreferredWidth *= mDensity;//当mode为MeasureSpec.UNSPECIFIED时的首选宽度
    
        mDrawingOffset = calculateRequiredOffset();//计算所需位移
    
        initPaintTools();//初始化画笔、画布
    
        setFocusable(true);//设置可获取焦点
        setFocusableInTouchMode(true);//设置在被触摸时会获取焦点
    }
    

    init方法里有很多变量,它们的含义如下图所示:

    变量名

    onMeasure

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthAllowed = MeasureSpec.getSize(widthMeasureSpec);
        int heightAllowed = MeasureSpec.getSize(heightMeasureSpec);
    
        widthAllowed = isUnspecified(widthMode) ? (int) mPreferredWidth : widthAllowed;
        heightAllowed = isUnspecified(heightMode) ? (int) mPreferredHeight : heightAllowed;
    
        int width = widthAllowed;
        int height = (int) (widthAllowed - mPanelSpacing - mHuePanelWidth);
        //当根据宽度计算出来的高度大于可允许的最大高度时 或 当前是横屏
        if (height > heightAllowed || "landscape".equals(getTag())) {
            height = heightAllowed;
            width = (int) (height + mPanelSpacing + mHuePanelWidth);
        }
        setMeasuredDimension(width, height);
    }
    
    private static boolean isUnspecified(int mode) {
        return !(mode == MeasureSpec.EXACTLY || mode == MeasureSpec.AT_MOST);
    }
    

    测量模式(MeasureSpecMode)分为EXACTLYAT_MOSTUNSPECIFIED三种类型。前两种类型可以通过MeasureSpec.getSize(int measureSpec)获得具体的值;当测量模式为UNSPECIFIED时,需要我们自己确定自定义View的高宽。这里我设定了一些默认值,使ColorPicker看起来更舒服。

    onLayout

    这里使用了父类的onLayout方法。

    onDraw

    绘制ColorPickerView分为两个部分:饱和度灰度调色板mSatValRect+色相调色板mHueRect

    @Override
    protected void onDraw(Canvas canvas) {
        if (mDrawingRect.width() <= 0 || mDrawingRect.height() <= 0) return;
        drawSatValPanel(canvas);//绘制SV选择区域
        drawHuePanel(canvas);//绘制右侧H选择区域
    }
    

    开始了解具体绘制细节之前,希望读者已经了解过Shader(着色器)的相关知识。

    绘制饱和度灰度调色板

    1 先画一个大矩形,再在上面画我们的饱和度灰度调色板,这样看起来有一个描边的效果。
    2 绘制饱和度灰度指示器。根据当前选择的颜色定位到具体的坐标,在这个坐标上做画。先画一个半径为mSVTrackerRadius、边线粗细为mDensity的空心大圆,再在上面画一个半径为mSVTrackerRadius-mDensity、边线粗细为mDensity的空心小圆。

    /**
     * 绘制S、V选择区域(矩形)
     *
     * @param canvas 画布
     */
    private void drawSatValPanel(Canvas canvas) {
        //描边(先画一个大矩形, 再在内部画一个小矩形,就可以显示出描边的效果)
        mBorderPaint.setColor(mBorderColor);
        canvas.drawRect(
                mDrawingRect.left,
                mDrawingRect.top,
                mSatValRect.right + BORDER_WIDTH,
                mSatValRect.bottom + BORDER_WIDTH,
                mBorderPaint);
    
        //组合着色器 = 明度线性着色器 + 饱和度线性着色器
        ComposeShader mShader = generateSVShader();
        mSatValPaint.setShader(mShader);
        canvas.drawRect(mSatValRect, mSatValPaint);
    
        //初始化选择器的位置
        Point p = satValToPoint(mSat, mVal);
        //绘制显示SV值的选择器
        mSatValTrackerPaint.setColor(0xff000000);
        canvas.drawCircle(p.x, p.y, mSVTrackerRadius - 1f * mDensity, mSatValTrackerPaint);
        //绘制外圆
        mSatValTrackerPaint.setColor(0xffdddddd);
        canvas.drawCircle(p.x, p.y, mSVTrackerRadius, mSatValTrackerPaint);
    }
    
    /**
     * 创建SV着色器(明度线性着色器 + 饱和度线性着色器)
     *
     * @return 着色器
     */
    private ComposeShader generateSVShader() {
        //明度线性着色器
        if (mValShader == null) {
            mValShader = new LinearGradient(mSatValRect.left, mSatValRect.top, mSatValRect.left, mSatValRect.bottom,
                    0xffffffff, 0xff000000, TileMode.CLAMP);
        }
        //HSV转化为RGB
        int rgb = Color.HSVToColor(new float[]{mHue, 1f, 1f});
        //饱和线性着色器
        Shader satShader = new LinearGradient(mSatValRect.left, mSatValRect.top, mSatValRect.right, mSatValRect.top,
                0xffffffff, rgb, TileMode.CLAMP);
        //组合着色器 = 明度线性着色器 + 饱和度线性着色器
        return new ComposeShader(mValShader, satShader, PorterDuff.Mode.MULTIPLY);
    }
    

    绘制色相调色板

    1 同理绘制矩形调色板
    2 同理绘制空心圆角矩形

    /**
     * 绘制右侧H选择区域
     *
     * @param canvas 画布
     */
    private void drawHuePanel(Canvas canvas) {
        final RectF rect = mHueRect;
    
        mBorderPaint.setColor(mBorderColor);
        canvas.drawRect(rect.left - BORDER_WIDTH,
                rect.top - BORDER_WIDTH,
                rect.right + BORDER_WIDTH,
                rect.bottom + BORDER_WIDTH,
                mBorderPaint);
        //初始化H线性着色器
        if (mHueShader == null) {
            int[] hue = new int[361];
            int count = 0;
            for (int i = hue.length - 1; i >= 0; i--, count++) {
                hue[count] = Color.HSVToColor(new float[]{i, 1f, 1f});
            }
            mHueShader = new LinearGradient(
                    rect.left,
                    rect.top,
                    rect.left,
                    rect.bottom,
                    hue,
                    null,
                    TileMode.CLAMP);
            mHuePaint.setShader(mHueShader);
        }
    
        canvas.drawRect(rect, mHuePaint);
    
         float halfHTrackerHeight= mHTrackerHeight / 2;
        //初始化H选择器选择条位置
        Point p = hueToPoint(mHue);
    
        RectF r = new RectF();
        r.left = rect.left - mRectOffset;
        r.right = rect.right + mRectOffset;
        r.top = p.y - halfHTrackerHeight;
        r.bottom = p.y + halfHTrackerHeight;
    
        //绘制选择条
        canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint);
    }
    

    这样一来,整个ColorPickerView的绘制流程就都在这里了。
    下一篇主要讲解ColorPickerView的初始化颜色传入和颜色改变监听回调:【Android自定义View】仿Photoshop取色器ColorPicker(三)

    相关文章

      网友评论

        本文标题:【Android自定义View】仿Photoshop取色器Col

        本文链接:https://www.haomeiwen.com/subject/lsforxtx.html