Paint 常用方法解析第二篇

作者: 小芸论 | 来源:发表于2017-07-24 22:28 被阅读276次

    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完好无损。
    ![](http://latex.codecogs.com/svg.latex?\begin{align}&Ra=Sa\& Rc=Sc\end{align})

    2> SRC_OVER


    Source像素被绘制在目标像素之上。
    ![](http://latex.codecogs.com/svg.latex?\begin{align}&Ra=Sa+(1-Sa)Da\& Rc=Sc+(1-Sa)Dc\end{align})

    3> SRC_IN


    保留覆盖Destination像素的Source像素,丢弃剩余的Source和Destination像素。
    ![](http://latex.codecogs.com/svg.latex?\begin{align}&Ra=SaDa\& Rc=ScDa\end{align})

    4> SRC_ATOP


    丢弃不和Destination像素重叠的Source像素。 在Destination像素上绘制剩余的Source像素。
    ![](http://latex.codecogs.com/svg.latex?\begin{align}&Ra=Da\& Rc=DaSc+(1-Sa)Dc\end{align})

    5> DST


    Source像素被丢弃,使Destination完好无损。
    ![](http://latex.codecogs.com/svg.latex?\begin{align}&Ra=Da\& Rc=Dc\end{align})

    6> DST_OVER


    Source像素被绘制在Destination像素之后。
    ![](http://latex.codecogs.com/svg.latex?\begin{align}&Ra=Da+(1-Da)Sa\& Rc=Dc+ (1-Da)Sc\end{align})

    7> DST_IN


    保留覆盖Source像素的Destination像素,丢弃剩余的Source和Destination像素。
    ![](http://latex.codecogs.com/svg.latex?\begin{align}&Ra=SaDa\& Rc=DcSa\end{align})

    8> DST_ATOP


    丢弃未被Source像素覆盖的Destination像素。 在Source像素上绘制剩余的Destination像素。
    ![](http://latex.codecogs.com/svg.latex?\begin{align}&Ra=Sa\& Rc=SaDc+(1-Da)Sc\end{align})

    9> CLEAR


    ![](http://latex.codecogs.com/svg.latex?\begin{align}&Ra=0\& Rc=0\end{align})

    10> SRC_OUT


    保留Destination像素未覆盖的Source像素。 舍弃Destination像素覆盖的Source像素。 丢弃所有Destination像素。
    ![](http://latex.codecogs.com/svg.latex?\begin{align}&Ra=(1-Da)Sa\& Rc=(1-Da)Sc\end{align})

    11> DST_OUT


    保持Source像素未覆盖的Destination像素。 舍弃Source像素覆盖的Destination像素。 丢弃所有Source像素。
    ![](http://latex.codecogs.com/svg.latex?\begin{align}&Ra=(1-Sa)Da\& Rc=(1-Sa)Dc\end{align})

    12> XOR


    丢弃重叠的Source和Destination像素。 绘制剩余的Source和Destination像素。
    ![](http://latex.codecogs.com/svg.latex?\begin{align}&Ra=(1-Da)Sa+(1-Sa)Da\& Rc=(1-Da)Sc+(1-Sa)Dc\end{align})

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


    保留Source和Destination像素的最小组合。
    ![](http://latex.codecogs.com/svg.latex?\begin{align}&Ra=Sa+Da-SaDa\& Rc=(1-Da)Sc+(1-Sa)Dc + min(Sc, Dc)\end{align*})

    14> LIGHTEN


    保留Source和Destination像素的最大组合。
    ![](http://latex.codecogs.com/svg.latex?\begin{align}&Ra=Sa+Da-SaDa\& Rc=(1-Da)Sc+(1-Sa)Dc + max(Sc, Dc)\end{align*})

    15> MULTIPLY


    将Source和Destination像素相乘。
    ![](http://latex.codecogs.com/svg.latex?\begin{align}&Ra=SaDa\& Rc=ScDc\end{align})

    16> SCREEN


    添加Source和Destination像素,然后减去与Destination相乘的Source像素。
    ![](http://latex.codecogs.com/svg.latex?\begin{align}&Ra=Sa+Da - SaDa\& Rc=Sc+Dc-ScDc\end{align})

    17> OVERLAY


    Multiplies or screens the source and destination depending on the destination color.
    ![](http://latex.codecogs.com/svg.latex?\begin{align}&Ra=Sa+Da-SaDa\& Rc=\left{\begin{array}{lcl}2ScDc&&{2Dc<Da}\SaDa-2(Da-Sc)(Sa-Dc)&&{otherwise}\end{array}\right.\end{align*})

    18> ADD



    将Source pixels添加到Destination pixels,并使结果饱和。



    具体实现参考自定义实现不规则形状的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);
        }
    }
    

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

    相关文章

      网友评论

        本文标题:Paint 常用方法解析第二篇

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