转载爱哥自定义View系列--Paint详解

上图是paint中的各种set方法

这些属性大多我们都可以见名知意,很好理解,即便如此,哥还是带大家过一遍逐个剖析其用法,其中会不定穿插各种绘图类比如Canvas、Xfermode、ColorFilter等等的用法。

set(Paint src)

顾名思义为当前画笔设置一个画笔,说白了就是把另一个画笔的属性设置Copy给我们的画笔,不累赘了

setARGB(int a, int r, int g, int b)

不扯了,别跟我说不懂

setAlpha(int a)

同上

setAntiAlias(boolean aa)

这个上一节我们用到了,打开抗锯齿,不过我要说明一点,抗锯齿是依赖于算法的,算法决定抗锯齿的效率,在我们绘制棱角分明的图像时,比如一个矩形、一张位图,我们不需要打开抗锯齿。

setColor(int color)

不扯

setColorFilter(ColorFilter filter)

设置颜色过滤,什么意思呢?就像拿个筛子把颜色“滤”一遍获取我们想要的色彩结果,感觉像是扯蛋白说一样是不是?没事我们慢慢说你一定会懂,这个方法需要我们传入一个ColorFilter参数同样也会返回一个ColorFilter实例,那么ColorFilter类是什么呢?追踪源码进去你会发现其里面很简单几乎没有:

ColorMatrixColorFilter、LightingColorFilter和PorterDuffColorFilter,也就是说我们在setColorFilter(ColorFilter filter)的时候可以直接传入这三个子类对象作为参数,那么这三个子类又是什么东西呢?首先我们来看看

ColorMatrixColorFilter

中文直译为色彩矩阵颜色过滤器,要明白这玩意你得先了解什么是色彩矩阵。在Android中图片是以RGBA像素点的形式加载到内存中的,修改这些像素信息需要一个叫做ColorMatrix类的支持,其定义了一个4x5的float[]类型的矩阵:

ColorMatrix colorMatrix = new ColorMatrix(new float[]{
        1, 0, 0, 0, 0,
        0, 1, 0, 0, 0,
        0, 0, 1, 0, 0,
        0, 0, 0, 1, 0,
});

其中,第一行表示的R(红色)的向量,第二行表示的G(绿色)的向量,第三行表示的B(蓝色)的向量,最后一行表示A(透明度)的向量,这一顺序必须要正确不能混淆!这个矩阵不同的位置表示的RGBA值,其范围在0.0F至2.0F之间,1为保持原图的RGB值。每一行的第五列数字表示偏移值,何为偏移值?顾名思义当我们想让颜色更倾向于红色的时候就增大R向量中的偏移值,想让颜色更倾向于蓝色的时候就增大B向量中的偏移值,这是最最朴素的理解,但是事实上色彩偏移的概念是基于白平衡来理解的,什么是白平衡呢?说得简单点就是白色是什么颜色!如果大家是个单反爱好者或者会些PS就会很容易理解这个概念,在单反的设置参数中有个色彩偏移,其定义的就是白平衡的色彩偏移值,就是当你去拍一张照片的时候白色是什么颜色的,在正常情况下白色是(255, 255, 255, 255)但是现实世界中我们是无法找到这样的纯白物体的,所以在我们用单反拍照之前就会拿一个我们认为是白色的物体让相机记录这个物体的颜色作为白色,然后拍摄时整张照片的颜色都会依据这个定义的白色来偏移!而这个我们定义的“白色”(比如:255, 253, 251, 247)和纯白(255, 255, 255, 255)之间的偏移值(0, 2, 4, 8)我们称之为白平衡的色彩偏移。

那么说了这么多,这玩意到底有啥用呢?我们来做个test!还是接着昨天那个圆环,不过我们今天把它改成绘制一个圆并且去掉线程动画的效果因为我们不需要:

public class CustomView extends View {
    private Paint mPaint;// 画笔
    private Context mContext;// 上下文环境引用

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

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;

        // 初始化画笔
        initPaint();
    }

    /**
     * 初始化画笔
     */
    private void initPaint() {
        // 实例化画笔并打开抗锯齿
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        /*
         * 设置画笔样式为描边,圆环嘛……当然不能填充不然就么意思了
         *
         * 画笔样式分三种:
         * 1.Paint.Style.STROKE:描边
         * 2.Paint.Style.FILL_AND_STROKE:描边并填充
         * 3.Paint.Style.FILL:填充
         */
        mPaint.setStyle(Paint.Style.FILL);

        // 设置画笔颜色为自定义颜色
        mPaint.setColor(Color.argb(255, 255, 128, 103));

        /*
         * 设置描边的粗细,单位:像素px 注意:当setStrokeWidth(0)的时候描边宽度并不为0而是只占一个像素
         */
        mPaint.setStrokeWidth(10);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 绘制圆形
        canvas.drawCircle(MeasureUtil.getScreenSize((Activity) mContext)[0] / 2, MeasureUtil.getScreenSize((Activity) mContext)[1] / 2, 200, mPaint);
    }
}

运行下是一个橙红色的圆~~是不是有点萝卜头国旗帜的感脚?

下面我们为Paint设置一个色彩矩阵:

// 生成色彩矩阵
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
        1, 0, 0, 0, 0,
        0, 1, 0, 0, 0,
        0, 0, 1, 0, 0,
        0, 0, 0, 1, 0,
});
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));

再次运行发现没变化啊!!!草!!!是不是感觉被我坑了?如果你真的那么认为我只能说你压根就没认真看上面的文字,我说过什么?值为1时表示什么?表示不改变原色彩的值!!这时我们改变色彩矩阵:

// 生成色彩矩阵
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
        0.5F, 0, 0, 0, 0,
        0, 0.5F, 0, 0, 0,
        0, 0, 0.5F, 0, 0,
        0, 0, 0, 1, 0,
});
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));

再次运行:

是不是明显不一样了?颜色变深了便淳厚了!我们通过色彩矩阵与原色彩的计算得出的色彩就是这样的。那它们是如何计算的呢?其实说白了就是矩阵之间的运算乘积:

矩阵ColorMatrix的一行乘以矩阵MyColor的一列作为矩阵Result的一行,这里MyColor的RGBA值我们需要转换为[0, 1]。那么我们依据此公式来计算下我们得到的RGBA值是否跟我们计算得出来的圆的RGBA值一样:

我们计算得出最后的RGBA值应该为:0.5, 0.25, 0.2, 1;

有兴趣的童鞋可以去PS之类的绘图软件里试试看正不正确对不对~~~这里就不演示了!看完这里有朋友又会说了,这玩意有毛线用啊!改个颜色还这么复杂!劳资直接setColor多爽!!没错,你这样想是对的,因为毕竟我们只是一个颜色,可是如果是一张图片呢????一张图片可有还几十万色彩呢!!!你麻痹你跟我说setColor?那么我们换张图片来试试呗!看看是什么样的效果:

public class CustomView extends View {
    private Paint mPaint;// 画笔
    private Context mContext;// 上下文环境引用
    private Bitmap bitmap;// 位图

    private int x,y;// 位图绘制时左上角的起点坐标

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

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;

        // 初始化画笔
        initPaint();

        //初始化资源
        initRes(context);
    }

    /**
     * 初始化画笔
     */
    private void initPaint() {
        // 实例化画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    }

    /**
     * 初始化资源
     */
    private void initRes(Context context) {
        // 获取位图
        bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.a);

        /*
         * 计算位图绘制时左上角的坐标使其位于屏幕中心
         * 屏幕坐标x轴向左偏移位图一半的宽度
         * 屏幕坐标y轴向上偏移位图一半的高度
         */
        x = MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 - bitmap.getWidth() / 2;
        y = MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 - bitmap.getHeight() / 2;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 绘制位图
        canvas.drawBitmap(bitmap, x, y, mPaint);
    }
}

如代码所示我们清除了所有的画笔属性设置因为没必要,从资源获取一个Bitmap绘制在画布上:

一张灰常漂亮的风景图,好!现在我们来为我们的画笔添加一个颜色过滤:

// 生成色彩矩阵
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
        0.5F, 0, 0, 0, 0,
        0, 0.5F, 0, 0, 0,
        0, 0, 0.5F, 0, 0,
        0, 0, 0, 1, 0,
});
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));

大家看到还是刚才那个色彩矩阵,运行下看看什么效果呢:

变暗了对吧!没意思,我们来点更刺激的,改下ColorMatrix矩阵:

ColorMatrix colorMatrix = new ColorMatrix(new float[]{
        0.33F, 0.59F, 0.11F, 0, 0,
        0.33F, 0.59F, 0.11F, 0, 0,
        0.33F, 0.59F, 0.11F, 0, 0,
        0, 0, 0, 1, 0,
});


噢!变灰了!还是没意思!继续改:

ColorMatrix colorMatrix = new ColorMatrix(new float[]{
        -1, 0, 0, 1, 1,
        0, -1, 0, 1, 1,
        0, 0, -1, 1, 1,
        0, 0, 0, 1, 0,
});

哟呵!!是不是有点类似PS里反相的效果?我们常看到的图片都是RGB的,颠覆一下思维,看看BGR的试试:

ColorMatrix colorMatrix = new ColorMatrix(new float[]{
        0, 0, 1, 0, 0,
        0, 1, 0, 0, 0,
        1, 0, 0, 0, 0,
        0, 0, 0, 1, 0,
});


这样红色的变成了蓝色而蓝色的就变成了红色,继续改:

ColorMatrix colorMatrix = new ColorMatrix(new float[]{
        0.393F, 0.769F, 0.189F, 0, 0,
        0.349F, 0.686F, 0.168F, 0, 0,
        0.272F, 0.534F, 0.131F, 0, 0,
        0, 0, 0, 1, 0,
});


是不是有点类似于老旧照片的感脚?继续:

ColorMatrix colorMatrix = new ColorMatrix(new float[]{
        1.5F, 1.5F, 1.5F, 0, -1,
        1.5F, 1.5F, 1.5F, 0, -1,
        1.5F, 1.5F, 1.5F, 0, -1,
        0, 0, 0, 1, 0,
});


类似去色后高对比度的效果,继续:

ColorMatrix colorMatrix = new ColorMatrix(new float[]{
        1.438F, -0.122F, -0.016F, 0, -0.03F,
        -0.062F, 1.378F, -0.016F, 0, 0.05F,
        -0.062F, -0.122F, 1.483F, 0, -0.02F,
        0, 0, 0, 1, 0,
});


饱和度对比度加强,好了不演示了……累死我了!截图粘贴上传!!

这些各种各样的图像效果在哪见过?PS?对的!还有各种拍照软件拍摄后的特效处理!大致原理都是这么来的!有人会问爱哥你傻逼么!这么多参数怎么玩!谁记得!而且TMD用参数调颜色?我映像中都是直接在各种绘图软件(比如PS)里拖进度条的!这怎么玩!淡定!如我所说很多时候你压根不需要了解太多原理,只需站在巨人的丁丁上即可,所以稍安勿躁!再下一个系列教程“设计色彩”中爱哥教你玩转色彩并且让设计和开发无缝结合!

ColorMatrixColorFilter和ColorMatrix就是这么个东西,ColorMatrix类里面也提供了一些实在的方法,比如setSaturation(float sat)设置饱和度,而且ColorMatrix每个方法都用了阵列的计算,如果大家感兴趣可以自己去深挖来看不过我是真心不推荐的~~~

下面我们来看看ColorFilter的另一个子类

LightingColorFilter

顾名思义光照颜色过滤,这肯定是跟光照是有关的了~~该类有且只有一个构造方法:

LightingColorFilter (int mul, int add)

这个方法非常非常地简单!mul全称是colorMultiply意为色彩倍增,而add全称是colorAdd意为色彩添加,这两个值都是16进制的色彩值0xAARRGGBB。这个方法使用也是非常的简单。还是拿上面那张图片来说吧,比如我们想要去掉绿色:

public class CustomView extends View {
    private Paint mPaint;// 画笔
    private Context mContext;// 上下文环境引用
    private Bitmap bitmap;// 位图

    private int x, y;// 位图绘制时左上角的起点坐标

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

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;

        // 初始化画笔
        initPaint();

        // 初始化资源
        initRes(context);
    }

    /**
     * 初始化画笔
     */
    private void initPaint() {
        // 实例化画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        // 设置颜色过滤
        mPaint.setColorFilter(new LightingColorFilter(0xFFFF00FF, 0x00000000));
    }

    /**
     * 初始化资源
     */
    private void initRes(Context context) {
        // 获取位图
        bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.a);

        /*
         * 计算位图绘制时左上角的坐标使其位于屏幕中心
         * 屏幕坐标x轴向左偏移位图一半的宽度
         * 屏幕坐标y轴向上偏移位图一半的高度
         */
        x = MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 - bitmap.getWidth() / 2;
        y = MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 - bitmap.getHeight() / 2;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 绘制位图
        canvas.drawBitmap(bitmap, x, y, mPaint);
    }
}

运行后你会发现绿色确实是没了但是原来偏绿的部分现在居然成了红色,为毛!敬请关注下一系列设计色彩文章!!哈哈哈!!当LightingColorFilter(0xFFFFFFFF, 0x00000000)的时候原图是不会有任何改变的,如果我们想增加红色的值,那么LightingColorFilter(0xFFFFFFFF, 0x00XX0000)就好,其中XX取值为00至FF。那么这个方法有什么存在的意义呢?存在必定合理,这个方法存在一定是有它可用之处的,前些天有个盆友在群里问点击一个图片如何直接改变它的颜色而不是为他多准备另一张点击效果的图片,这种情况下该方法就派上用场了!如下图一个灰色的星星,我们点击后让它变成黄色


代码如下,注释很清楚我就不再多说了:

public class CustomView extends View {
    private Paint mPaint;// 画笔
    private Context mContext;// 上下文环境引用
    private Bitmap bitmap;// 位图

    private int x, y;// 位图绘制时左上角的起点坐标
    private boolean isClick;// 用来标识控件是否被点击过

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

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;

        // 初始化画笔
        initPaint();

        // 初始化资源
        initRes(context);

        setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                /*
                 * 判断控件是否被点击过
                 */
                if (isClick) {
                    // 如果已经被点击了则点击时设置颜色过滤为空还原本色
                    mPaint.setColorFilter(null);
                    isClick = false;
                } else {
                    // 如果未被点击则点击时设置颜色过滤后为黄色
                    mPaint.setColorFilter(new LightingColorFilter(0xFFFFFFFF, 0X00FFFF00));
                    isClick = true;
                }

                // 记得重绘
                invalidate();
            }
        });
    }

    /**
     * 初始化画笔
     */
    private void initPaint() {
        // 实例化画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    }

    /**
     * 初始化资源
     */
    private void initRes(Context context) {
        // 获取位图
        bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.a2);

        /*
         * 计算位图绘制时左上角的坐标使其位于屏幕中心
         * 屏幕坐标x轴向左偏移位图一半的宽度
         * 屏幕坐标y轴向上偏移位图一半的高度
         */
        x = MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 - bitmap.getWidth() / 2;
        y = MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 - bitmap.getHeight() / 2;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 绘制位图
        canvas.drawBitmap(bitmap, x, y, mPaint);
    }
}

运行后点击星星即可变成黄色再点击变回灰色,当我们不想要颜色过滤的效果时,setColorFilter(null)并重绘视图即可!那么为什么要叫光照颜色过滤呢?原因很简单,因为它所呈现的效果就像有色光照在物体上染色一样~~~哎,不说这方法了,看下一个也是最后一个ColorFilter的子类。

PorterDuffColorFilter

PorterDuffColorFilter跟LightingColorFilter一样,只有一个构造方法

PorterDuffColorFilter(int color, PorterDuff.Mode mode)

这个构造方法也接受两个值,一个是16进制表示的颜色值这个很好理解,而另一个是PorterDuff内部类Mode中的一个常量值,这个值表示混合模式。那么什么是混合模式呢?混合混合必定是有两种东西混才行,第一种就是我们设置的color值而第二种当然就是我们画布上的元素了!,比如这里我们把Color的值设为红色,而模式设为PorterDuff.Mode.DARKEN变暗:

public class CustomView extends View {
    private Paint mPaint;// 画笔
    private Context mContext;// 上下文环境引用
    private Bitmap bitmap;// 位图

    private int x, y;// 位图绘制时左上角的起点坐标

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

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;

        // 初始化画笔
        initPaint();

        // 初始化资源
        initRes(context);
    }

    /**
     * 初始化画笔
     */
    private void initPaint() {
        // 实例化画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        // 设置颜色过滤
        mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.DARKEN));
    }

    /**
     * 初始化资源
     */
    private void initRes(Context context) {
        // 获取位图
        bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.a);

        /*
         * 计算位图绘制时左上角的坐标使其位于屏幕中心
         * 屏幕坐标x轴向左偏移位图一半的宽度
         * 屏幕坐标y轴向上偏移位图一半的高度
         */
        x = MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 - bitmap.getWidth() / 2;
        y = MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 - bitmap.getHeight() / 2;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 绘制位图
        canvas.drawBitmap(bitmap, x, y, mPaint);
    }
}

我们尝试在画布上Draw刚才的那张图片看看:

变暗了……也变红了……这就是PorterDuff.Mode.DARKEN模式给我们的效果,当然PorterDuff.Mode还有其他很多的混合模式,大家可以尝试,但是这里要注意一点,PorterDuff.Mode中的模式不仅仅是应用于图像色彩混合,还应用于图形混合,比如PorterDuff.Mode.DST_OUT就表示裁剪混合图,如果我们在PorterDuffColorFilter中强行设置这些图形混合的模式将不会看到任何对应的效果,关于图形混合我们将在下面详解。

setXfermode(Xfermode xfermode)

[只对图像有效,drawBitmapXXX()]

Xfermode国外有大神称之为过渡模式,这种翻译比较贴切但恐怕不易理解,大家也可以直接称之为图像混合模式,因为所谓的“过渡”其实就是图像混合的一种,这个方法跟我们上面讲到的setColorFilter蛮相似的

同理可得其必然有一定的子类去实现一些方法供我们使用,查看API文档发现其果然有三个子类:AvoidXfermode, PixelXorXfermode和PorterDuffXfermode,这三个子类实现的功能要比setColorFilter的三个子类复杂得多,主要是是涉及到图像处理的一些知识可能对大家来说会比较难以理解,不过我会尽量以通俗的方式阐述它们的作用,那好先来看看我们的第一个子类

AvoidXfermode

首先我要告诉大家的是这个API因为不支持硬件加速在API 16已经过时了(大家可以在HardwareAccel查看那些方法不支持硬件加速)……如果想在高于API 16的机子上测试这玩意,必须现在应用或手机设置中关闭硬件加速,在应用中我们可以通过在AndroidManifest.xml文件中设置application节点下的android:hardwareAccelerated属性为false来关闭硬件加速:

<application
    android:allowBackup="true"
    android:hardwareAccelerated="false"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" >
    <activity
        android:name="com.aigestudio.customviewdemo.activities.MainActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

AvoidXfermode只有一个含参的构造方法AvoidXfermode(int opColor, int tolerance, AvoidXfermode.Mode mode),其具体实现和ColorFilter一样都被封装在C/C++内,它怎么实现我们不管我们只要知道这玩意怎么用就行对吧。AvoidXfermode有三个参数,第一个opColor表示一个16进制的可以带透明通道的颜色值例如0x12345678,第二个参数tolerance表示容差值,那么什么是容差呢?你可以理解为一个可以标识“精确”或“模糊”的东西,待会我们细讲,最后一个参数表示AvoidXfermode的具体模式,其可选值只有两个:AvoidXfermode.Mode.AVOID或者AvoidXfermode.Mode.TARGET,两者的意思也非常简单,我们先来看

AvoidXfermode.Mode.TARGET

在该模式下Android会判断画布上的颜色是否会有跟opColor不一样的颜色,比如我opColor是红色,那么在TARGET模式下就会去判断我们的画布上是否有存在红色的地方,如果有,则把该区域“染”上一层我们画笔定义的颜色,否则不“染”色,而tolerance容差值则表示画布上的像素和我们定义的红色之间的差别该是多少的时候才去“染”的,比如当前画布有一个像素的色值是(200, 20, 13),而我们的红色值为(255, 0, 0),当tolerance容差值为255时,即便(200, 20, 13)并不等于红色值也会被“染”色,容差值越大“染”色范围越广反之则反,空说无凭我们来看看具体的实现和效果:

public class CustomView extends View {
    private Paint mPaint;// 画笔
    private Context mContext;// 上下文环境引用
    private Bitmap bitmap;// 位图
    private AvoidXfermode avoidXfermode;// AV模式

    private int x, y, w, h;// 位图绘制时左上角的起点坐标

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

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;

        // 初始化画笔
        initPaint();

        // 初始化资源
        initRes(context);
    }

    /**
     * 初始化画笔
     */
    private void initPaint() {
        // 实例化画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        /*
         * 当画布中有跟0XFFFFFFFF色不一样的地方时候才“染”色
         */
        avoidXfermode = new AvoidXfermode(0XFFFFFFFF, 0, AvoidXfermode.Mode.TARGET);
    }

    /**
     * 初始化资源
     */
    private void initRes(Context context) {
        // 获取位图
        bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.a);

        /*
         * 计算位图绘制时左上角的坐标使其位于屏幕中心
         * 屏幕坐标x轴向左偏移位图一半的宽度
         * 屏幕坐标y轴向上偏移位图一半的高度
         */
        x = MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 - bitmap.getWidth() / 2;
        y = MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 - bitmap.getHeight() / 2;
        w = MeasureUtil.getScreenSize((Activity) mContext)[0] / 2 + bitmap.getWidth() / 2;
        h = MeasureUtil.getScreenSize((Activity) mContext)[1] / 2 + bitmap.getHeight() / 2;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 先绘制位图
        canvas.drawBitmap(bitmap, x, y, mPaint);

        // “染”什么色是由我们自己决定的
        mPaint.setARGB(255, 211, 53, 243);

        // 设置AV模式
        mPaint.setXfermode(avoidXfermode);

        // 画一个位图大小一样的矩形
        canvas.drawRect(x, y, w, h, mPaint);
    }
}

在高于API 16的测试机上会得到一个矩形的色块(API 16+的都类似,改ROM和关闭了硬件加速的除外):

我们再用低于API 16(或高于API 16但关闭了硬件加速)的测试机运行就会得到另一个不同的效果:

大家可以看到,在我们的模式为TARGET容差值为0的时候此时只有当图片中像色颜色值为0XFFFFFFFF的地方才会被染色,而其他地方不会有改变

AvoidXfermode(0XFFFFFFFF, 0, AvoidXfermode.Mode.TARGET):

而当容差值为255的时候只要是跟0XFFFFFFFF有点接近的地方都会被染色

而另外一种模式

AvoidXfermode.Mode.AVOID

则与TARGET恰恰相反,TARGET是我们指定的颜色是否与画布的颜色一样,而AVOID是我们指定的颜色是否与画布不一样,其他的都与TARGET类似

AvoidXfermode(0XFFFFFFFF, 0, AvoidXfermode.Mode.AVOID):

当模式为AVOID容差值为0时,只有当图片中像素颜色值与0XFFFFFFFF完全不一样的地方才会被染色

AvoidXfermode(0XFFFFFFFF, 255, AvoidXfermode.Mode.AVOID):

当容差值为255时,只要与0XFFFFFFFF稍微有点不一样的地方就会被染色

那么这玩意究竟有什么用呢?比如说当我们只想在白色的区域画点东西或者想把白色区域的地方替换为另一张图片的时候就可以采取这种方式!

Xfermode的第二个子类

PixelXorXfermode

与AvoidXfermode一样也在API 16过时了,该类也提供了一个含参的构造方法PixelXorXfermode(int opColor),该类的计算实现很简单,从官方给出的计算公式来看就是:op ^ src ^ dst,像素色值的按位异或运算,如果大家感兴趣,可以自己用一个纯色去尝试,并自己计算异或运算的值是否与得出的颜色值一样,这里我就不讲了,Because it was deprecated and useless。

Xfermode的最后一个子类也是惟一一个没有过时且沿用至今的子类

PorterDuffXfermode

该类同样有且只有一个含参的构造方法PorterDuffXfermode(PorterDuff.Mode mode),这个PorterDuff.Mode大家看后是否会有些面熟,它跟上面我们讲ColorFilter时候用到的PorterDuff.Mode是一样的!麻雀虽小五脏俱全,虽说构造方法的签名列表里只有一个PorterDuff.Mode的参数,但是它可以实现很多酷毙的图形效果!!而PorterDuffXfermode就是图形混合模式的意思,其概念最早来自于SIGGRAPH的Tomas Proter和Tom Duff,混合图形的概念极大地推动了图形图像学的发展,延伸到计算机图形图像学像Adobe和AutoDesk公司著名的多款设计软件都可以说一定程度上受到影响,而我们PorterDuffXfermode的名字也来源于这俩人的人名组合PorterDuff,那PorterDuffXfermode能做些什么呢?我们先来看一张API DEMO里的图片:

这张图片从一定程度上形象地说明了图形混合的作用,两个图形一圆一方通过一定的计算产生不同的组合效果,在API中Android为我们提供了18种(比上图多了两种ADD和OVERLAY)模式:

来定义不同的混合效果,这18种模式Android还为我们提供了它们的计算方式比如LIGHTEN的计算方式为[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)],其中Sa全称为Source alpha表示源图的Alpha通道;Sc全称为Source color表示源图的颜色;Da全称为Destination alpha表示目标图的Alpha通道;Dc全称为Destination color表示目标图的颜色,细心的朋友会发现“[……]”里分为两部分,其中“,”前的部分为“Sa + Da - Sa*Da”这一部分的值代表计算后的Alpha通道而“,”后的部分为“Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)”这一部分的值代表计算后的颜色值,图形混合后的图片依靠这个矢量来计算ARGB的值,如果大家感兴趣可以查看维基百科中对Alpha合成的解释:http://en.wikipedia.org/wiki/Alpha_compositing。作为一个猿,我们不需要知道复杂的图形学计算但是一定要知道这些模式会为我们提供怎样的效果,当大家看到上面API DEMO给出的效果时一定会觉得PorterDuffXfermode其实就是简单的图形交并集计算,比如重叠的部分删掉或者叠加等等,事实上呢!PorterDuffXfermode的计算绝非是根据于此!上面我们也说了PorterDuffXfermode的计算是要根据具体的Alpha值和RGB值的,既然如此,我们就来看一个比API DEMO稍微复杂的例子来更有力地说明PorterDuffXfermode是如何工作而我们又能用它做些什么,在这个例子中我将用到两个带有Alpha通道的渐变图形Bitmap:

我们将在不同的模式下混合这两个Bitmap来看看这两个渐变色的颜色值在不同的混合模式下究竟发生了什么?先看看我们的测试代码:

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class PorterDuffView extends View {
    /*
     * PorterDuff模式常量
     * 可以在此更改不同的模式测试
     */
    private static final PorterDuff.Mode MODE = PorterDuff.Mode.ADD;

    private static final int RECT_SIZE_SMALL = 400;// 左右上方示例渐变正方形的尺寸大小
    private static final int RECT_SIZE_BIG = 800;// 中间测试渐变正方形的尺寸大小

    private Paint mPaint;// 画笔

    private PorterDuffBO porterDuffBO;// PorterDuffView类的业务对象
    private PorterDuffXfermode porterDuffXfermode;// 图形混合模式

    private int screenW, screenH;// 屏幕尺寸
    private int s_l, s_t;// 左上方正方形的原点坐标
    private int d_l, d_t;// 右上方正方形的原点坐标
    private int rectX, rectY;// 中间正方形的原点坐标

    public PorterDuffView(Context context, AttributeSet attrs) {
        super(context, attrs);

        // 实例化画笔并设置抗锯齿
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        // 实例化业务对象
        porterDuffBO = new PorterDuffBO();

        // 实例化混合模式
        porterDuffXfermode = new PorterDuffXfermode(MODE);

        // 计算坐标
        calu(context);
    }

    /**
     * 计算坐标
     *
     * @param context
     *            上下文环境引用
     */
    private void calu(Context context) {
        // 获取包含屏幕尺寸的数组
        int[] screenSize = MeasureUtil.getScreenSize((Activity) context);

        // 获取屏幕尺寸
        screenW = screenSize[0];
        screenH = screenSize[1];

        // 计算左上方正方形原点坐标
        s_l = 0;
        s_t = 0;

        // 计算右上方正方形原点坐标
        d_l = screenW - RECT_SIZE_SMALL;
        d_t = 0;

        // 计算中间方正方形原点坐标
        rectX = screenW / 2 - RECT_SIZE_BIG / 2;
        rectY = RECT_SIZE_SMALL + (screenH - RECT_SIZE_SMALL) / 2 - RECT_SIZE_BIG / 2;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 设置画布颜色为黑色以便我们更好地观察
        canvas.drawColor(Color.BLACK);

        // 设置业务对象尺寸值计算生成左右上方的渐变方形
        porterDuffBO.setSize(RECT_SIZE_SMALL);

        /*
         * 画出左右上方两个正方形
         * 其中左边的的为src右边的为dis
         */
        canvas.drawBitmap(porterDuffBO.initSrcBitmap(), s_l, s_t, mPaint);
        canvas.drawBitmap(porterDuffBO.initDisBitmap(), d_l, d_t, mPaint);

        /*
         * 将绘制操作保存到新的图层(更官方的说法应该是离屏缓存)我们将在1/3中学习到Canvas的全部用法这里就先follow me
         */
        int sc = canvas.saveLayer(0, 0, screenW, screenH, null, Canvas.ALL_SAVE_FLAG);

        // 重新设置业务对象尺寸值计算生成中间的渐变方形
        porterDuffBO.setSize(RECT_SIZE_BIG);

        // 先绘制dis目标图
        canvas.drawBitmap(porterDuffBO.initDisBitmap(), rectX, rectY, mPaint);

        // 设置混合模式
        mPaint.setXfermode(porterDuffXfermode);

        // 再绘制src源图
        canvas.drawBitmap(porterDuffBO.initSrcBitmap(), rectX, rectY, mPaint);

        // 还原混合模式
        mPaint.setXfermode(null);

        // 还原画布
        canvas.restoreToCount(sc);
    }
}

代码中我们使用到了View的离屏缓冲,也通俗地称之为层,这个概念很简单,我们在绘图的时候新建一个“层”,所有的绘制操作都在该层上而不影响该层以外的图像,比如代码中我们在绘制了画布颜色和左右上方两个方形后就新建了一个图层来绘制中间的大正方形,这个方形和左右上方的方形是在两个不同的层上的:

注:图中所显示色彩效果与我们的代码不同,上图只为演示图层概念

当我们绘制完成后要通过restore将所有缓冲(层)中的绘制操作还原到画布以结束绘制,具体关于画布的知识在自定义控件其实很简单1/3,这里就不多说了,下面我们看具体各种模式的计算效果

PS:Src为源图像,意为将要绘制的图像;Dis为目标图像,意为我们将要把源图像绘制到的图像……是不是感脚很拗口 = = !Fuck……意会意会~~

PorterDuff.Mode.ADD

计算方式:Saturate(S + D);Chinese:饱和相加

从计算方式和显示的结果我们可以看到,ADD模式简单来说就是对图像饱和度进行相加,这个模式在应用中不常用,我唯一一次使用它是通过代码控制RGB通道的融合生成图片。

PorterDuff.Mode.CLEAR

计算方式:[0, 0];Chinese:清除

清除图像,很好理解不扯了。

PorterDuff.Mode.DARKEN

计算方式:[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)];Chinese:变暗

这个模式计算方式目测很复杂,其实效果很好理解,两个图像混合,较深的颜色总是会覆盖较浅的颜色,如果两者深浅相同则混合,如图,黄色覆盖了红色而蓝色和青色因为是跟透明混合所以不变。细心的朋友会发现青色和黄色之间有一层类似橙色的过渡色,这就是混合的结果。在实际的测试中源图和目标图的DARKEN混合偶尔会有相反的结果比如红色覆盖了黄色,这源于Android对颜色值“深浅”的定义,我暂时没有在官方查到有关资料不知道是否与图形图像学一致。DARKEN模式的应用在图像色彩方面比较广泛我们可以利用其特性来获得不同的成像效果,这点与之前介绍的ColorFilter有点类似。

该模式处理过后,会感觉效果变暗,即进行对应像素的比较,取较暗值,如果色值相同则进行混合;

从算法上看,alpha值变大,色值上如果都不透明则取较暗值,非完全不透明情况下使用上面算法进行计算,受到源图和目标图对应色值和alpha值影响;

PorterDuff.Mode.DST

计算方式:[Da, Dc];Chinese:只绘制目标图像

如Chinese所说,很好理解。

PorterDuff.Mode.DST_ATOP

计算方式:[Sa, Sa * Dc + Sc * (1 - Da)];Chinese:在源图像和目标图像相交的地方绘制目标图像而在不相交的地方绘制源图像

PorterDuff.Mode.DST_IN

计算方式:[Sa * Da, Sa * Dc];Chinese:只在源图像和目标图像相交的地方绘制目标图像

PorterDuff.Mode.DST_OUT

计算方式:[Da * (1 - Sa), Dc * (1 - Sa)];Chinese:只在源图像和目标图像不相交的地方绘制目标图像

PorterDuff.Mode.DST_OVER

计算方式:[Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc];Chinese:在源图像的上方绘制目标图像

这个就不说啦,就是两个图片谁在上谁在下的意思

PorterDuff.Mode.LIGHTEN

计算方式:[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)];Chinese:变亮

可以和 DARKEN 对比起来看,DARKEN 的目的是变暗,LIGHTEN 的目的则是变亮,如果在均完全不透明的情况下 ,色值取源色值和目标色值中的较大值

PorterDuff.Mode.MULTIPLY

计算方式:[Sa * Da, Sc * Dc];Chinese:正片叠底

该模式通俗的计算方式很简单,源图像素颜色值乘以目标图像素颜色值除以255即得混合后图像像素的颜色值,该模式在设计领域应用广泛,因为其特性黑色与任何颜色混合都会得黑色,在手绘的上色、三维动画的UV贴图绘制都有应用,具体效果大家自己尝试我就不说了

PorterDuff.Mode.OVERLAY

计算方式:未给出;Chinese:叠加

这个模式没有在官方的API DEMO中给出,谷歌也没有给出其计算方式,在实际效果中其对亮色和暗色不起作用,也就是说黑白色无效,它会将源色与目标色混合产生一种中间色,这种中间色生成的规律也很简单,如果源色比目标色暗,那么让目标色的颜色倍增否则颜色递减。

PorterDuff.Mode.SCREEN

计算方式:[Sa + Da - Sa * Da, Sc + Dc - Sc * Dc];Chinese:滤色

计算方式我不解释了,滤色产生的效果我认为是Android提供的几个色彩混合模式中最好的,它可以让图像焦媃幻化,有一种色调均和的感觉:

PorterDuff.Mode.SRC

计算方式:[Sa, Sc];Chinese:显示源图

只绘制源图,SRC类的模式跟DIS的其实差不多就不多说了

PorterDuff.Mode.SRC_ATOP

计算方式:[Da, Sc * Da + (1 - Sa) * Dc];Chinese:在源图像和目标图像相交的地方绘制源图像,在不相交的地方绘制目标图像

PorterDuff.Mode.SRC_IN

计算方式:[Sa * Da, Sc * Da];Chinese:只在源图像和目标图像相交的地方绘制源图像

PorterDuff.Mode.SRC_OUT

计算方式:[Sa * (1 - Da), Sc * (1 - Da)];Chinese:只在源图像和目标图像不相交的地方绘制源图像

PorterDuff.Mode.SRC_OVER

计算方式:[Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc];Chinese:在目标图像的顶部绘制源图像

PorterDuff.Mode.XOR

计算方式:[Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc];Chinese:在源图像和目标图像重叠之外的任何地方绘制他们,而在重叠的地方不绘制任何内容

时间: 2024-10-25 09:57:48

转载爱哥自定义View系列--Paint详解的相关文章

转载爱哥自定义View系列--文字详解

FontMetrics FontMetrics意为字体测量,这么一说大家是不是瞬间感受到了这玩意的重要性?那这东西有什么用呢?我们通过源码追踪进去可以看到FontMetrics其实是Paint的一个内部类,而它里面呢就定义了top,ascent,descent,bottom,leading五个成员变量其他什么也没有: 这五个成员变量除了top和bottom我们较熟悉外其余三个都很陌生是做什么用的呢?首先我给大家看张图: 这张图很简单但是也很扼要的说明了top,ascent,descent,bot

转载爱哥自定义View系列--Canvas详解

上面所罗列出来的各种drawXXX方法就是Canvas中定义好的能画什么的方法(drawPaint除外),除了各种基本型比如矩形圆形椭圆直曲线外Canvas也能直接让我们绘制各种图片以及颜色等等,但是Canvas真正屌的我觉得不是它能画些什么,而是对画布的各种活用,上一节最后的一个例子大家已经粗略见识了变换Canvas配合save和restore方法给我们绘制图形带来的极大便利,事实上Canvas的活用远不止此,在讲Canvas之前,我想先给大家说说Canvas中非常屌毛而且很有个性的一个方法:

自定义View系列教程07--详解ViewGroup分发Touch事件

自定义View系列教程01–常用工具介绍 自定义View系列教程02–onMeasure源码详尽分析 自定义View系列教程03–onLayout源码详尽分析 自定义View系列教程04–Draw源码分析及其实践 自定义View系列教程05–示例分析 自定义View系列教程06–详解View的Touch事件处理 自定义View系列教程07–详解ViewGroup分发Touch事件 PS:如果觉得文章太长,那就直接看视频吧 在上一篇中已经分析完了View对于Touch事件的处理,在此基础上分析和理

安卓自定义View进阶-Matrix详解

这应该是目前最详细的一篇讲解Matrix的中文文章了,在上一篇文章Matrix原理中,我们对Matrix做了一个简单的了解,偏向理论,在本文中则会详细的讲解Matrix的具体用法,以及与Matrix相关的一些实用技巧. Matrix方法表 按照惯例,先放方法表做概览. 方法类别 相关API 摘要 基本方法 equals hashCode toString toShortString 比较. 获取哈希值. 转换为字符串 数值操作 set reset setValues getValues 设置.

wing带你玩转自定义view系列(2) 简单模仿qq未读消息去除效果

上一篇介绍了贝塞尔曲线的简单应用 仿360内存清理效果 这一篇带来一个  两条贝塞尔曲线的应用 : 仿qq未读消息去除效果. 转载请注明出处:http://blog.csdn.net/wingichoy/article/details/50503630 老规矩,先上效果图: qq的未读消息去除很炫酷,其实就是用了两条贝塞尔曲线,我们按思路来,先来画两个圆,及两条贝塞尔曲线,辅助点为圆心y坐标的一半.我们把下面移动的圆,叫做mMoveCircle. 这样一画,就很简单明了了对不对.只要在拖动的时候

wing带你玩转自定义view系列(1) 仿360内存清理效果

本篇是接自 手把手带你做自定义view系列 宗旨都是一样,带大家一起来研究自定义view的实现,与其不同的是本系列省去了简单的坐标之类的讲解,重点在实现思路,用简洁明了的文章,来与大家一同一步步学习. 转载请注明出处:http://blog.csdn.net/wingichoy/article/details/50500479 上一篇介绍了:神奇的贝塞尔曲线,这篇就来研究其应用. 我自己的学习方法是:学习了贝塞尔曲线之后,去研究他的规律,然后开始联想有没有见过类似的效果,最后自己去研究实现,在没

自定义View系列教程01--常用工具介绍

在自定义View的时候,常常会用到一些Android系统提供的工具.这些工具封装了我们经常会用到的方法,比如拖拽View,计算滑动速度,View的滚动,手势处理等等.如果我们自己去实现这些方法会比较繁琐,而且容易出一些bug.所以,作为自定义View系列教程的开端,先介绍一下这些常用的工具,以便在后续的学习和工作中使用. Configuration ViewConfiguration GestureDetector VelocityTracker Scroller ViewDragHelper

自定义View系列教程05--示例分析

自定义View系列教程01–常用工具介绍 自定义View系列教程02–onMeasure源码详尽分析 自定义View系列教程03–onLayout源码详尽分析 自定义View系列教程04–Draw源码分析及其实践 自定义View系列教程05–示例分析 PS:如果觉得文章太长,那就直接看视频吧 之前结合源码分析完了自定义View的三个阶段:measure,layout,draw. 那么,自定义有哪几种常见的方式呢? 直接继承自View 在使用该方式实现自定义View时通常的核心操作都在onDraw

View绘制过程详解

View绘制过程详解 界面窗口的根布局是DecorView,该类继承自FrameLayout.说到View绘制,想到的就是从这里入手,而FrameLayout继承自ViewGroup.感觉绘制肯定会在ViewGroup或者View中, 但是木有找到.发现ViewGroup实现ViewParent接口,而ViewParent有一个实现类是ViewRootImpl, ViewGruop中会使用ViewRootImpl- /** * The top of a view hierarchy, imple