Paint 常用方法解析第二篇

Paint 常用方法解析第一篇中分析了Paint的setColorFilter方法,下面接着分析Paint的其它set方法。

3 setMaskFilter

用来给Paint设置遮罩过滤器,该方法接受一个MaskFilter类型的参数,MaskFilter源码如下:

/**
 * MaskFilter is the base class for object that perform transformations on
 * an alpha-channel mask before drawing it. A subclass of MaskFilter may be
 * installed into a Paint. Blur and emboss are implemented as subclasses of MaskFilter.
 */
public class MaskFilter {

    protected void finalize() throws Throwable {
        nativeDestructor(native_instance);
        native_instance = 0;  // Other finalizers can still call us.
    }

    private static native void nativeDestructor(long native_filter);
    long native_instance;
}

从注释中可以得到如下两个信息:
1> MaskFilter是用来在绘制之前变化透明度通道值的基类。
2> MaskFilter不应该被直接使用,即应该使用MaskFilter的子类
Google一共为我们提供了2个MaskFilter子类:


3.1 BlurMaskFilter(模糊遮罩过滤器)

先来看看源码:

/**
 * This takes a mask, and blurs its edge by the specified radius. Whether or
 * or not to include the original mask, and whether the blur goes outside,
 * inside, or straddles, the original mask's border, is controlled by the
 * Blur enum.
 */
public class BlurMaskFilter extends MaskFilter {

    public enum Blur {
        /**
         * Blur inside and outside the original border.
         */
        NORMAL(0),

        /**
         * Draw solid inside the border, blur outside.
         */
        SOLID(1),

        /**
         * Draw nothing inside the border, blur outside.
         */
        OUTER(2),

        /**
         * Blur inside the border, draw nothing outside.
         */
        INNER(3);
        
        Blur(int value) {
            native_int = value;
        }
        final int native_int;
    }
    
    /**
     * Create a blur maskfilter.
     *
     * @param radius The radius to extend the blur from the original mask. Must be > 0.
     * @param style  The Blur to use
     * @return       The new blur maskfilter
     */
    public BlurMaskFilter(float radius, Blur style) {
        native_instance = nativeConstructor(radius, style.native_int);
    }

    private static native long nativeConstructor(float radius, int style);
}

上面代码的注释已经很清晰了,BlurMaskFilter通过指定的模糊半径来模糊边界,并且通过BlurMaskFilter.Blur来控制模糊的范围。
BlurMaskFilter的构造函数的参数:
1> radius 表示是Blur Radius,即阴影的模糊半径
2> style 用来控制模糊的范围
NORMAL :同时模糊边框的内部和外部
SOLID:加粗边框内部,模糊边框外部
OUTER:边框内部不绘制(即透明),模糊边框外部
INNER:模糊边框内部,边框外部不做处理
下面就举个例子:

public class MaskFilterView extends View {
    private Paint paint = null;
    private Bitmap bitmap = null;

    public MaskFilterView(Context context) {
        super(context);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
    }

    public MaskFilterView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MaskFilterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        BlurMaskFilter blurMaskFilter = new BlurMaskFilter(30, BlurMaskFilter.Blur.NORMAL);
        paint.setMaskFilter(blurMaskFilter);
        paint.setColor(Color.BLUE);
        paint.setTextSize(210f);
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bitmap_shader);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawText("ANDROID", 100, 300, paint);
        canvas.drawRect(200, 500, 500, 800, paint);
        canvas.translate(200, 1000);
        canvas.drawBitmap(bitmap, 0, 0, paint);
    }

运行截图如下:


上面是NORMAL类型得到的结果,SOLID、OUTER、INNER的运行截图依次如下:
SOLID

OUTER

INNER

应用 图片的阴影效果
上面说过MaskFilter是用来在绘制之前变化透明度通道值的基类,因此我们可以先获取到图片的alpha通道值并且对图片的alpha通道值进行模糊处理,然后依次绘制图片的alpha通道值和图片,代码如下:

public class MaskFilterView2 extends View {
    private Paint paint = null;
    private Bitmap alphaBitmap = null;
    private Bitmap bitmap = null;

    public MaskFilterView2(Context context) {
        super(context);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
    }

    public MaskFilterView2(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MaskFilterView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        BlurMaskFilter blurMaskFilter = new BlurMaskFilter(20, BlurMaskFilter.Blur.OUTER);
        paint.setMaskFilter(blurMaskFilter);
        paint.setColor(Color.DKGRAY);
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bitmap_shader);
        alphaBitmap = bitmap.extractAlpha();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(200, 200);
        canvas.drawBitmap(alphaBitmap, 0, 0, paint);
        canvas.drawBitmap(bitmap, 0, 0, null);
    }

运行截图如下:


3.2 EmbossMaskFilter(浮雕遮罩过滤器)

先来看看源码:

public class EmbossMaskFilter extends MaskFilter {
    /**
     * Create an emboss maskfilter
     *
     * @param direction  array of 3 scalars [x, y, z] specifying the direction of the light source
     * @param ambient    0...1 amount of ambient light
     * @param specular   coefficient for specular highlights (e.g. 8)
     * @param blurRadius amount to blur before applying lighting (e.g. 3)
     * @return           the emboss maskfilter
     */
    public EmbossMaskFilter(float[] direction, float ambient, float specular, float blurRadius) {
        if (direction.length < 3) {
            throw new ArrayIndexOutOfBoundsException();
        }
        native_instance = nativeConstructor(direction, ambient, specular, blurRadius);
    }

    private static native long nativeConstructor(float[] direction, float ambient, float specular, float blurRadius);
}

由上面的代码可知,EmbossMaskFilter通过指定光源的方向、环境光强度、镜面高亮系数和模糊半径实现来实现浮雕效果。
EmbossMaskFilter是通过构造方法指定上面所说的4个属性,构造方法参数如下:
direction:光源的方向,取值为长度为3的数组[x,y,z]
ambient:环境光的强度,取值范围0...1
specular: 镜面高亮系数
blurRadius:模糊半径
下面举个例子:

public class EmbossFilterView extends View {
    private Paint paint = null;
    private Bitmap bitmap = null;

    public EmbossFilterView(Context context) {
        super(context);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
    }

    public EmbossFilterView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public EmbossFilterView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        float[] direction = new float[]{10, 10, 10};
        float ambient = 0.5f;
        float specular = 5;
        float blurRadius = 5;
        EmbossMaskFilter embossMaskFilter = new EmbossMaskFilter(direction, ambient, specular, blurRadius);
        paint.setMaskFilter(embossMaskFilter);
        paint.setColor(Color.BLUE);
        paint.setTextSize(210f);
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bitmap_shader);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawText("ANDROID", 100, 300, paint);
        canvas.drawRect(200, 500, 500, 800, paint);
        canvas.translate(200, 1000);
        canvas.drawBitmap(bitmap, 0, 0, paint);
    }
}

运行截图如下:


4 setPathEffect

参考Drawable绘制过程源码分析和自定义Drawable实现动画中的2.1

5 setShader

参考Drawable绘制过程源码分析和自定义Drawable实现动画中的2.2

6 setXfermode

用来给Paint设置Xfermode模式,该方法接受一个Xfermode类型的参数,Xfermode的源码如下:

/**
 * Xfermode is the base class for objects that are called to implement custom
 * "transfer-modes" in the drawing pipeline. The static function Create(Modes)
 * can be called to return an instance of any of the predefined subclasses as
 * specified in the Modes enum. When an Xfermode is assigned to an Paint, then
 * objects drawn with that paint have the xfermode applied.
 */
public class Xfermode {

    protected void finalize() throws Throwable {
        try {
            finalizer(native_instance);
            native_instance = 0;
        } finally {
            super.finalize();
        }
    }

    private static native void finalizer(long native_instance);

    long native_instance;
}

可以看到Xfermode并没有具体的实现,因此Xfermode一定有子类去实现一些方法供我们使用:



可以看到Xfermode一共有3个子类,对于AvoidXfermode和PixelXorXfermode这两个类,在API16时被作废,在API24的时候被移除(将targetSdkVersion和compileSdkVersion设置为24或者更高时,AvoidXfermode和PixelXorXfermode是找不到的),因此下面就不会再研究这两个类。

3.1 PorterDuffXfermode

public class PorterDuffXfermode extends Xfermode {
    /**
     * @hide
     */
    public final PorterDuff.Mode mode;

    /**
     * Create an xfermode that uses the specified porter-duff mode.
     *
     * @param mode           The porter-duff mode that is applied
     */
    public PorterDuffXfermode(PorterDuff.Mode mode) {
        this.mode = mode;
        native_instance = nativeCreateXfermode(mode.nativeInt);
    }
    
    private static native long nativeCreateXfermode(int mode);
}

可以看到PorterDuffXfermode只是提供了一个接受PorterDuff.Mode类型参数的构造方法供我们使用,PorterDuff是由Thomas Porter和Tom Duff两个人的名字组成,其概念最早来自于他们在1984年题为“合成数字图像”的开创性文章中,因为Thomas Porter和Tom Duff的工作只关注 source和destination的alpha通道的影响,所以在原始论文中描述的12种运算在这里被称为alpha合成模式,PorterDuff类还提供了几种混合模式,它们类似地定义了合成source和destination但不限于alpha通道的结果, 这些混合模式不是由Thomas Porter和Tom Duff定义的,但为了方便起见,这些模式已被包括在PorterDuff类中。

下面展示的所有示例图都使用相同的Source image和Destination image:


下面会依次列举PorterDuff类枚举的18种合成模式的图像表示和计算公式,计算公式中的Sa表示Source image的alpha通道值,Sc表示Source image的颜色值,Da代表Destination image的alpha通道值,Dc代表Destination image的颜色值,Ra代表Result image的alpha通道值,Rc表示Result image的颜色值。
12种alpha合成模式
1> SRC


Destination像素被丢弃,使Source完好无损。
[站外图片上传中...(image-94b805-1569758227361)]

2> SRC_OVER


Source像素被绘制在目标像素之上。
[站外图片上传中...(image-cfbe08-1569758227362)]Da\& Rc=Sc+(1-Sa)Dc\end{align*})

3> SRC_IN



保留覆盖Destination像素的Source像素,丢弃剩余的Source和Destination像素。
[图片上传失败...(image-6058fa-1569758227362)]

4> SRC_ATOP


丢弃不和Destination像素重叠的Source像素。 在Destination像素上绘制剩余的Source像素。
[图片上传失败...(image-b6ac84-1569758227362)]Dc\end{align})

5> DST



Source像素被丢弃,使Destination完好无损。
[图片上传失败...(image-9663ba-1569758227362)]

6> DST_OVER


Source像素被绘制在Destination像素之后。
[图片上传失败...(image-ef946b-1569758227362)]Sa\& Rc=Dc+ (1-Da)Sc\end{align*})

7> DST_IN



保留覆盖Source像素的Destination像素,丢弃剩余的Source和Destination像素。
[图片上传失败...(image-a04f3d-1569758227362)]

8> DST_ATOP


丢弃未被Source像素覆盖的Destination像素。 在Source像素上绘制剩余的Destination像素。
[图片上传失败...(image-eca4b9-1569758227362)]Sc\end{align})

9> CLEAR



[站外图片上传中...(image-8ac303-1569758227362)]

10> SRC_OUT


保留Destination像素未覆盖的Source像素。 舍弃Destination像素覆盖的Source像素。 丢弃所有Destination像素。
[图片上传失败...(image-4ca76-1569758227362)]Sa\& Rc=(1-Da)Sc\end{align*})

11> DST_OUT


保持Source像素未覆盖的Destination像素。 舍弃Source像素覆盖的Destination像素。 丢弃所有Source像素。
[图片上传失败...(image-44fff1-1569758227362)]Da\& Rc=(1-Sa)Dc\end{align*})

12> XOR


丢弃重叠的Source和Destination像素。 绘制剩余的Source和Destination像素。
[图片上传失败...(image-fc65b3-1569758227362)]Sa+(1-Sa)Da\& Rc=(1-Da)Sc+(1-Sa)Dc\end{align*})

除了上面的12种alpha合成模式,PorterDuff类还提供了如下6种合成模式:
13> DARKEN


保留Source和Destination像素的最小组合。
[图片上传失败...(image-d69718-1569758227362)]Sc+(1-Sa)Dc + min(Sc, Dc)\end{align*})

14> LIGHTEN


保留Source和Destination像素的最大组合。
[图片上传失败...(image-eb5c75-1569758227362)]Sc+(1-Sa)Dc + max(Sc, Dc)\end{align*})

15> MULTIPLY



将Source和Destination像素相乘。
[图片上传失败...(image-34ec90-1569758227362)]

16> SCREEN



添加Source和Destination像素,然后减去与Destination相乘的Source像素。
[图片上传失败...(image-d9561-1569758227362)]

17> OVERLAY


Multiplies or screens the source and destination depending on the destination color.
[图片上传失败...(image-ac2f8b-1569758227362)](Sa-Dc)&&{otherwise}\end{array}\right.\end{align})

18> ADD



将Source pixels添加到Destination pixels,并使结果饱和。
[图片上传失败...(image-d8fe58-1569758227362)]

3.2 PorterDuffXfermode应用举例

对于开发者来说,我们只需要知道这些模式提供什么样的效果,而对于这些模式对应的计算公式就不需要关心了,感兴趣的同学可以参考Alpha compositing,讲了这么多,PorterDuffXfermode到底有什么用呢:

1> 自定义实现不规则形状的View


具体实现参考自定义实现不规则形状的View

2>实现橡皮擦的效果
对于这个效果应该用的比较广泛了,最常见的就是刮奖区的效果,首先给大家一个效果图:



运行效果还是很满意的,下面就是实现的源码:

布局代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical">

    <com.cytmxk.customview.eraser.EraserView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:eraser_view_bg="@drawable/fourth_pic"
        app:eraser_view_fg="@android:color/darker_gray"/>

</LinearLayout>

//自定义橡皮擦View的代码
public class EraserView extends View {
    private int touchSlop = 8; // 滑动距离阀值:如果在屏幕上滑动距离小于此值则不会绘制
    private int fgColor = 0xFFAAAAAA; // 前景颜色
    private Bitmap fgBitmap = null; // 前景图片
    private Drawable bgDrawable = null; // 背景图片
    private float lastX; // 记录上一个触摸事件的位置坐标
    private float lastY;
    private Path path = null; // 橡皮擦的摩擦路径
    private Paint paint = null; // 模拟橡皮擦的画笔
    private Canvas pathCanvas = null; // 用于绘制橡皮擦路径的canva


    public EraserView(Context context, int bgResId, int fgColorId) {
        super(context);
        if (bgResId < 0) {
            throw new IllegalArgumentException("EraserView args error!");
        }
        Resources resources = context.getResources();
        bgDrawable = resources.getDrawable(bgResId);
        fgColor = resources.getColor(fgColorId);
        if (null == bgDrawable) {
            throw new IllegalArgumentException("EraserView args error!");
        }
        init();
    }

    public EraserView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public EraserView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.EraserView);
        bgDrawable = ta.getDrawable(R.styleable.EraserView_eraser_view_bg);
        fgColor = ta.getColor(R.styleable.EraserView_eraser_view_fg, 0xFFAAAAAA);
        ta.recycle();

        if (null == bgDrawable) {
            throw new IllegalArgumentException("EraserView args error!");
        }
        bgDrawable.setCallback(this);
        init();
    }

    private void init() {

        ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext());
        touchSlop = viewConfiguration.getScaledTouchSlop();

        path = new Path();
        paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.TRANSPARENT);
        paint.setStrokeWidth(50);
        paint.setStrokeJoin(Paint.Join.ROUND);
        paint.setStrokeCap(Paint.Cap.ROUND);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        paint.setColor(Color.TRANSPARENT);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (null != bgDrawable) {
            bgDrawable.setBounds(0, 0, getWidth(), getHeight());
        }
        createFgBitmap();
    }

    private void createFgBitmap() {
        fgBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
        pathCanvas = new Canvas(fgBitmap);
        pathCanvas.drawColor(fgColor);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                lastX = x;
                lastY = y;
                path.reset();
                path.moveTo(x, y);
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                float offsetX = x - lastX;
                float offsetY = y - lastY;
                if (offsetX >= touchSlop || offsetY >= touchSlop) {
                    path.quadTo(lastX, lastY, x, y);
                    lastX = x;
                    lastY = y;
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                break;
            }
        }
        invalidate();
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        bgDrawable.draw(canvas);
        canvas.drawBitmap(fgBitmap, 0, 0, null);
        pathCanvas.drawPath(path, paint);
    }
}

上面的代码很简单,我就不在解释了,写不动了,我要睡了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容