zoukankan      html  css  js  c++  java
  • Paint、Canvas、Matrix使用解说(一、Paint)

    我正在參加 CSDN 2015博客之星评选 感恩分享活动,假设认为文章还不错,请投个票鼓舞下吧:http://vote.blog.csdn.net/blogstar2015/candidate?

    username=tianjian4592


    好了,前面主要讲了Animation,Animator 的使用,以及桌面火箭效果和水波纹效果,分别使用android框架和自己绘制实现,俗话说,工欲善其事。必先利其器。接下来几篇文章主要讲绘制中我们须要常使用的一些利器;

    Paint:画笔

    Canvas:画布

    Matrix:变换矩阵

    绘制动效确实就像拿着笔在画布上面画画一样,而Paint就是我们拿着的笔,Canvas就是使用的画布;


    一、Paint(画笔)

    依据我们要画的类型,我们能够选择不同的笔,比方大气磅礴的山水画,我们能够选择大头的毛笔;细腻入微的肖像画我们能够选择尖头的铅笔。

    而且依据我们想要的效果,我们在绘画的时候,还会选择不同的颜料或不同颜色的笔。

    那么在程序中,Paint 就足以满足以上全部的须要,我们能够依据我们自己的须要去自行设置我们画笔的属性,首先来看看都能设置哪些属性:


    Paint 有三个构造函数,各自是:

    Paint()创建一个画笔对象;

    Paint(int flags):在构造的时候能够传入一些定义好的属性。eg:Paint.ANTI_ALIAS_FLAG  --用于绘制时抗锯齿

    Paint(Paint paint):使用构造函数中Paint的属性生成一个新的Paint

    private void initPaint() {
            // 构建Paint时直接加上去锯齿属性
            mColorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            // 直接使用mColorPaint的属性构建mBitmapPaint
            mBitmapPaint = new Paint(mColorPaint);
        }


    方法经常使用的主要有下面一些:

    setARGB (int a, int r, int g, int b):用于设置画笔颜色,A 代表 alpha(透明度)。R 代表Red (红色),G 代表 Green(绿色),B 代表 Blue(蓝色)

    色值採用16进制。取值在 0 - 255 之间 ,0(0x00) 即 全然没有 ,255(0xff) 代表满值 ;

    setAlpha(int a): 用于设置Paint 的透明度。

    setColor(int color):相同设置颜色。假设是经常使用色,能够使用Color 类中定义好的一些色值 ,eg:Color.WHITE

    setColorFilter(ColorFilter filter):设置颜色过滤器,能够通过颜色过滤器过滤掉相应的色值,比方去掉照片颜色,生成老照片效果。

    ColorFilter有下面几个子类可用:

    ColorMatrixColorFilter

    LightingColorFilter

    PorterDuffColorFilter


    1.ColorMatrixColorFilter:通过颜色矩阵(ColorMatrix)对图像中的色值进行改变

    在Android中,图片是以一个个 RGBA 的像素点的形式载入到内存中的,所以假设须要改变图片的颜色,就须要针对这一个个像素点的RGBA的值进行改动,事实上主要是RGB,A是透明度。

    改动图片 RGBA 的值须要ColorMatrix类的支持,它定义了一个 4*5 的float[]类型的矩阵。矩阵中每一行表示 RGBA 中的一个參数。

    颜色矩阵M是以一维数组m=[a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t]的方式进行存储的;

    颜色矩阵


    而对于一张图像来说,展示的颜色效果取决于图像的RGBA(红色、绿色、蓝色、透明度)值。而图像的 RGBA 值则存储在一个5*1的颜色分量矩阵C中,由颜色分量矩阵C能够控制图像的颜色效果。颜色分量矩阵如图所看到的: 

    所以为了改变图像的显示效果。仅仅须要改变 4*5 的颜色矩阵ColorMatrix,然后通过

    就可以得到新的图像显示矩阵。

    由此可见,通过颜色矩阵 ColorMatrix 改动了原图像的 RGBA 值,从而达到了改变图片颜色效果的目的。而且,通过如上图所看到的的运算可知,颜色矩阵 ColorMatrix 的第一行參数abcde决定了图像的红色成分,第二行參数fghij决定了图像的绿色成分,第三行參数klmno决定了图像的蓝色成分,第四行參数pqrst决定了图像的透明度,第五列參数ejot是颜色的偏移量。

      通常。改变颜色分量时能够通过改动第5列的颜色偏移量来实现。如上面所看到的的颜色矩阵。通过计算后能够得知该颜色矩阵的作用是使图像的红色分量和绿色分量均添加100,这种效果就是图片泛黄(由于红色与绿色混合后得到黄色):


    除此之外,也能够通过直接对颜色值乘以某一系数而达到改变颜色分量的目的。

    例如以下图所看到的的颜色矩阵,将绿色分量放大了2倍,这种效果就是图片泛绿色:


    基于此,我们利用ColorFilter 和 ColorMatrixColorFilter类 和 Paint 的setColorFilter 就能够改变图片的展示效果(颜色,饱和度。对照度等),从而得到类似市面上图像软件中的黑白老照片、泛黄旧照片、羞涩的青春... ...特效。


         

                                      (原图效果)                         (调节效果)


        // 通过外层传入的 A、R、G、B值生成相应的ColorMatrix ,然后又一次绘制图像
        public void setArgb(float alpha, float red, float green, float blue) {
            mRedFilter = red;
            mGreenFilter = green;
            mBlueFilter = blue;
            mAlphaFilter = alpha;
            mColorMatrix = new ColorMatrix(new float[] {
                    mRedFilter, 0, 0, 0, 0,
                    0, mGreenFilter, 0, 0, 0,
                    0, 0, mBlueFilter, 0, 0,
                    0, 0, 0, mAlphaFilter, 0,
            });
            mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));
    
            postInvalidate();
        }


    在Activity中拖动seekbar时。动态改变相应的色值參数。然后设置给里面的View:

        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            float filter = (float) progress / 100;
            if (seekBar == mRedSeekBar) {
                mRedSeek = filter;
            } else if (seekBar == mGreenSeekBar) {
                mGreenSeek = filter;
            } else if (seekBar == mBlueSeekBar) {
                mBlueSeek = filter;
            } else if (seekBar == mAlphaSeekBar) {
                mAlphaSeek = filter;
            }
            mColorFilterView.setArgb(mAlphaSeek, mRedSeek, mGreenSeek, mBlueSeek);
        }

    我们再专门把绿色位置扩大为两倍。看下效果:

        private static final float[] mMatrixFloats = new float[] {
                1, 0, 0, 0, 0,
                0, 2, 0, 0, 0,
                0, 0, 1, 0, 0,
                0, 0, 0, 1, 0
        };

    能够看到这个图片绿的敞亮

    我们继续改动影响绿色的这一排矩阵值,看会发生什么变化,这次我们仅仅是把2改大一点。改为10看看:

        private static final float[] mMatrixFloats = new float[] {
                1, 0, 0, 0, 0,
                0, 10, 0, 0, 0,
                0, 0, 1, 0, 0,
                0, 0, 0, 1, 0
        };
    这次我们已经认为图片绿的过头了

    我们再将影响绿色的矩阵值改小,大家已经能够推測下图片的效果了。那就是绿色会成倍的减弱。减弱到一定的层度就会显得发灰。
    而最后面的颜色增量也是一样,能够依据数值的大小。正负一定程度上增强和减弱相应的色值;

    上面的demo仅仅是通过动态的改变上面颜色矩阵里的a、g、s、m 值,进而改变图像相应的色值分量。达到改变图片显示效果的目的,我们还能够通过改变矩阵的其它数据来调节出各种有意思的效果:

    改动影响红色的这一排矩阵:

        private static final float[] mMatrixFloats = new float[] {
                2, 0.5f, 0.5f, 0.5f, 20,
                0, 1, 0, 0, 0,
                0, 0, 1, 0, 0,
                0, 0, 0, 1, 0
        };
    继续改:

       private static final float[] mMatrixFloats = new float[] {
                0.22f, 0.5f, 0.1f, 0, 0,
                0.22f, 0.5f, 0.1f, 0, 0,
                0.22f, 0.5f, 0.1f, 0, 0,
                0, 0, 0, 1, 0
        };
    这下图片变灰了

    再改:
        private static final float[] mMatrixFloats = new float[] {
                -1, 0, 0, 1, 0,
                0, -1, 0, 1, 0,
                0, 0, -1, 1, 0,
                0, 0, 0, 1, 0
        };

    将RGB均反相,但假设仅仅这样会变成黑色。然后再增强一下。
    再改:
        private static final float[] mMatrixFloats = new float[] {
                0, 0, 1, 0, 0,
                1, 0, 0, 0, 0,
                0, 2, 0, 0, 0,
                0, 0, 0, 1, 0
        };


    依据上面的矩阵,我们相当于去除了图片原先的RGB。然后原先的蓝色转变为红色,原先的红色转变为绿色,原先色绿色转变为蓝色,并变为2倍,经过以上变化,则形成了上面终于的效果。

    所以通过改动颜色矩阵值,能够调出各种图片展示效果。如:高对照度、低对照度、高饱和度、低饱和度、负片等等。大家能够自己体验体验;


    2.LightingColorFilter:

        /**
         * Create a colorfilter that multiplies the RGB channels by one color, and then adds a second color,
         * pinning the result for each component to [0..255]. The alpha components of the mul and add arguments
         * are ignored.
         */
        public LightingColorFilter(int mul, int add) {
            native_instance = native_CreateLightingFilter(mul, add);
            nativeColorFilter = nCreateLightingFilter(native_instance, mul, add);
        }


    从源代码上看,LightingColorFilter 仅仅有一个构造方法。然后传入 mul 和 add 。这两个 各自是16进制色值。定义例如以下:

        private static final int MUL_COLOR = 0xff00ffff;
        private static final int ADD_COLOR = 0x000000ff;

    终于颜色的计算方式为:(MUL_COLOR * 原色值 + ADD_COLOR )% 255

    所以上面则可以过滤掉红色色值,并增强蓝色色值,另外从源代码的凝视上也可以看到LightingColorFilter 是不计算透明度的;

    3.PorterDuffColorFilter:能够类比于PS里的 图层混合模式

        /**
         * Create a colorfilter that uses the specified color and porter-duff mode.
         *
         * @param srcColor       The source color used with the specified
         *                       porter-duff mode
         * @param mode           The porter-duff mode that is applied
         */
        public PorterDuffColorFilter(int srcColor, PorterDuff.Mode mode) {
            native_instance = native_CreatePorterDuffFilter(srcColor, mode.nativeInt);
            nativeColorFilter = nCreatePorterDuffFilter(native_instance, srcColor, mode.nativeInt);
        }

    事实上就是用一个色值和要绘制的图片进行叠加。然后能够选择叠加的模式(PorterDuff.Mode),PorterDuff.Mode 以枚举的形式定义,共同拥有例如以下一些:

        // these value must match their native equivalents. See SkPorterDuff.h
        public enum Mode {
            /** [0, 0] */
            CLEAR       (0),
            /** [Sa, Sc] */
            SRC         (1),
            /** [Da, Dc] */
            DST         (2),
            /** [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] */
            SRC_OVER    (3),
            /** [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] */
            DST_OVER    (4),
            /** [Sa * Da, Sc * Da] */
            SRC_IN      (5),
            /** [Sa * Da, Sa * Dc] */
            DST_IN      (6),
            /** [Sa * (1 - Da), Sc * (1 - Da)] */
            SRC_OUT     (7),
            /** [Da * (1 - Sa), Dc * (1 - Sa)] */
            DST_OUT     (8),
            /** [Da, Sc * Da + (1 - Sa) * Dc] */
            SRC_ATOP    (9),
            /** [Sa, Sa * Dc + Sc * (1 - Da)] */
            DST_ATOP    (10),
            /** [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] */
            XOR         (11),
            /** [Sa + Da - Sa*Da,
                 Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] */
            DARKEN      (12),
            /** [Sa + Da - Sa*Da,
                 Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] */
            LIGHTEN     (13),
            /** [Sa * Da, Sc * Dc] */
            MULTIPLY    (14),
            /** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */
            SCREEN      (15),
            /** Saturate(S + D) */
            ADD         (16),
            OVERLAY     (17);

    以上公式中,Sa 代表 Source alpha (源透明度 -- 传入颜色的透明度),Da 代表 Destination alpha (目标 alpha)。Sc 代表 Source Color (源颜色),Dc 代表 Destination Color (目标色);每一种模式的计算方法分别如上;

    我们简单測试下,还是用前面样例的图:


    mBitDuffPaint.setColorFilter(new PorterDuffColorFilter(DUFF_COLOR, PorterDuff.Mode.DARKEN));  ----  变暗
    我们看一下效果:


    这时得出的图片效果和在PS里对两个图层使用变暗的叠加效果是一样的。PS里的效果例如以下:



    我们继续调几个模式看看效果,变亮效果:

    mBitDuffPaint.setColorFilter(new PorterDuffColorFilter(DUFF_COLOR, PorterDuff.Mode.LIGHTEN));
    onDraw 中:

    canvas.drawBitmap(mBitmap, mSrcRect, mDestRect, mBitDuffPaint);

    程序 和 PS 展示效果分别为:

        

    我上面仅仅是使用蓝色 和图片进行了混合。大家能够使用其它颜色再调的玩玩,当中详细的叠加规律大家也能够自己稍加总结;
    好了,颜色过滤器就讲这么多;

    setDither(boolean dither):防抖动。这个属性的需求场景主要出如今绘制渐变色彩或含渐变的图片时。android对不含alpha通道的图片会进行一个转化,

    成为RGB565 格式的。这样的格式占用内存小,但由于如此,就会出现讨厌的“色带”情景,让人感觉过渡的不是那么柔和,针对这个问题,android提出了

    防抖动,它会将原始颜色的过渡处依据两边的色值进行一些改变,从而让颜色过渡更加的柔和,让人认为是平滑的过渡;

    setFilterBitmap(boolean filter):假设该项设置为true。则图像在动画进行中会滤掉对Bitmap图像的优化操作。加快显示速度

    setFlags(int flags):能够用来给Paint设置里面定义好的一些属性,如抗锯齿。防抖动等;

    setMaskFilter(MaskFilter maskFilter):设置绘制时图片边缘效果。能够有模糊和浮雕;

    MaskFilter类能够为Paint分配边缘效果。
            对MaskFilter的扩展能够对一个Paint边缘的alpha通道应用转换。Android包括了以下几种MaskFilter:

            BlurMaskFilter   指定了一个模糊的样式和半径来处理Paint的边缘。
            EmbossMaskFilter  指定了光源的方向和环境光强度来加入浮雕效果。

    setPathEffect(PathEffect effect):是用来控制绘制轮廓(线条)的方式:

    这个类本身并没有做什么特殊的处理,仅仅是继承了Object,通常我们使用的是它的几个子类。就是上面图片中所显示的。在使用的时候,一般是
    PathEffect pe = new 一个详细的子类;
    然后使用Paint的setPathEffect(PathEffect pe)方法就可以。

    CornerPathEffect:

    这个类的作用就是将Path的各个连接线段之间的夹角用一种更平滑的方式连接,类似于圆弧与切线的效果。
    一般的。通过CornerPathEffect(float radius)指定一个详细的圆弧半径来实例化一个CornerPathEffect;

    DashPathEffect:

    这个类的作用就是将Path的线段虚线化:
    构造函数为DashPathEffect(float[] intervals, float phase),当中intervals为虚线的ON和OFF数组。该数组的length必须大于等于2。phase为绘制时的偏移量。

    DiscretePathEffect:

    这个类的作用是打散Path的线段,使得在原来路径的基础上发生打散效果。
    一般的。通过构造DiscretePathEffect(float segmentLength,float deviation)来构造一个实例,当中,segmentLength指定最大的段长。deviation指定偏离量。

    PathDashPathEffect:

    这个类的作用是使用Path图形来填充当前的路径,其构造函数为PathDashPathEffect (Path shape, float advance, float phase,PathDashPathEffect.Stylestyle)。


    shape则是指填充图形,advance指每一个图形间的间距,phase为绘制时的偏移量,style为该类自由的枚举值,有三种情况:Style.ROTATE、Style.MORPH和Style.TRANSLATE;

    当中ROTATE的情况下。线段连接处的图形转换以旋转到与下一段移动方向相一致的角度进行转转,MORPH时图形会以发生拉伸或压缩等变形的情况与下一段相连接,TRANSLATE时。图形会以位置平移的方式与下一段相连接。

    ComposePathEffect:

    组合效果。这个类须要两个PathEffect參数来构造一个实例,ComposePathEffect (PathEffect outerpe,PathEffect innerpe),表现时,会首先将innerpe表现出来,然后再在innerpe的基础上去添加outerpe的效果。

    SumPathEffect:

    叠加效果。这个类也须要两个PathEffect作为參数SumPathEffect(PathEffect first,PathEffect second),但与ComposePathEffect不同的是,在表现时,会分别对两个參数的效果各自独立进行表现。然后将两个效果简单的重叠在一起显示出来;

    关于參数phase

    在存在phase參数的两个类里。假设phase參数的值不停发生改变,那么所绘制的图形也会随着偏移量而不断的发生变动。这个时候,看起来这条线就像动起来了一样:

    举个栗子:

    public class PathEffectView extends View {
    
        private float mPhase;
        private PathEffect[] mEffects = new PathEffect[7];
        private int[] mColors;
        private Paint mPaint;
        private Path mPath;
    
        public PathEffectView(Context context) {
            super(context);
            mPaint = new Paint();
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setStrokeWidth(4);
            // 创建,并初始化Path
            mPath = new Path();
            mPath.moveTo(0, 0);
            for (int i = 1; i <= 15; i++)
            {
                // 生成15个点,随机生成它们的坐标,并将它们连成一条Path
                mPath.lineTo(i * 20, (float) Math.random() * 60);
            }
            // 初始化七个颜色
            mColors = new int[] {
                    Color.BLACK, Color.BLUE, Color.CYAN,
                    Color.GREEN, Color.MAGENTA, Color.RED, Color.YELLOW
            };
        }
    
        protected void onDraw(Canvas canvas)
        {
            // 将背景填充成白色
            canvas.drawColor(Color.WHITE);
            // -------以下開始初始化7种路径的效果
            // 使用路径效果--原始效果
            mEffects[0] = null;
            // 使用CornerPathEffect路径效果--參数为圆角半径
            mEffects[1] = new CornerPathEffect(10);
            // 初始化DiscretePathEffect -- segmentLength指定最大的段长,deviation指定偏离量
            mEffects[2] = new DiscretePathEffect(3.0f, 5.0f);
            // 初始化DashPathEffect --intervals为虚线的ON和OFF数组,offset为绘制时的偏移量
            mEffects[3] = new DashPathEffect(new float[] {
                    20, 10, 5, 10
            }, mPhase);
            // 初始化PathDashPathEffect
            Path p = new Path();
            p.addRect(0, 0, 8, 8, Path.Direction.CCW);
            // shape则是指填充图形,advance指每一个图形间的间距。phase为绘制时的偏移量,style为该类自由的枚举值
            mEffects[4] = new PathDashPathEffect(p, 12, mPhase, PathDashPathEffect.Style.ROTATE);
            // 组合效果
            mEffects[5] = new ComposePathEffect(mEffects[2], mEffects[4]);
            // 叠加效果
            mEffects[6] = new SumPathEffect(mEffects[4], mEffects[3]);
            // 将画布移到8,8处開始绘制
            canvas.translate(8, 8);
            // 依次使用7种不同路径效果,7种不同的颜色来绘制路径
            for (int i = 0; i < mEffects.length; i++)
            {
                mPaint.setPathEffect(mEffects[i]);
                mPaint.setColor(mColors[i]);
                canvas.drawPath(mPath, mPaint);
                canvas.translate(0, 60);
            }
            // 改变phase值,形成动画效果
            mPhase += 1;
            invalidate();
        }
    }


    效果为:



    setShader(Shader shader)


    设置图像效果,使用Shader能够绘制出各种渐变效果。

    Shader以下有五个子类可用:

    BitmapShader :位图图像渲染

    LinearGradient:线性渲染

    RadialGradient:环形渲染

    SweepGradient:扫描渐变渲染/梯度渲染

    ComposeGradient:组合渲染,能够和其它几个子类组合起来使用

    这几个类中LinearGradient、RadialGradient、SweepGradient均是能够将颜色进行处理,形成柔和的过渡。也能够称为渐变,而BitmapShader 则是直接使用位图进行渲染。就类似于贴图,在贴图的过程中依据须要自然就能够选择对应的模式,有三种模式可供选择。各自是:

    枚举: 
    emun Shader.TileMode 

    定义了平铺的3种模式: 
    static final Shader.TileMode CLAMP: 边缘拉伸,即使用边缘的最后一个像素进行延展。

    static final Shader.TileMode MIRROR:在水平方向和垂直方向交替景象, 两个相邻图像间没有缝隙,从名称上看就像照镜子一样;

    Static final Shader.TillMode REPETA:在水平方向和垂直方向反复摆放,两个相邻图像间有缝隙缝隙;


    Shader在应用中也是很的广泛,不少大产品中的亮点设计也与之相关,接下来我们分别看个小样例:


    1.LinearGradient(线性渲染--也叫线性渐变)


    我们在PS上假设要拉出一条线性渐变,仅仅须要先把渐变模式选为线性,然后拉出一条渐变线。同理,在android中。仅仅须要一个起始点和一个终点就可以确定一条渐变线。那么颜色怎样渐变过去呢,往下看,LinearGradient 有两个构造方法:

            /**	Create a shader that draws a linear gradient along a line.
            @param x0       The x-coordinate for the start of the gradient line
            @param y0       The y-coordinate for the start of the gradient line
            @param x1       The x-coordinate for the end of the gradient line
            @param y1       The y-coordinate for the end of the gradient line
            @param  color0  The color at the start of the gradient line.
            @param  color1  The color at the end of the gradient line.
            @param  tile    The Shader tiling mode
    	*/
    	public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,
                TileMode tile) {--省略--}


    这个构造方法就是传入起点和终点作为渐变线,然后color0作为起始点的颜色值,color1作为终点的颜色值,然后生成的效果就是从起始点到终点从color0均匀的渐变到color1。注意是均匀:

    我们来看下效果:


        // 定义几种初始颜色
        private static final int COLOR_BLUE = 0xff0000ff;
        private static final int COLOR_GREEN = 0xff00ff00;
        private static final int COLOR_RED = 0xffff0000;
    
        // 定义起始和终于颜色
        private int mStartColor = COLOR_BLUE;
        private int mToColor = COLOR_RED;
    


    然后创建一个从屏幕上中心点到屏幕1/3高度的下中心点的渐变:


    new LinearGradient(mHalfWidth, 0, mHalfWidth, mTotalHeight/3, mStartColor, mToColor, Shader.TileMode.CLAMP)


    绘制一个全屏矩形:


    canvas.drawRect(0, 0, mTotalWidth, mTotalHeight,
                    mGradientPaint);

    看下三种模式下的效果:

                                                                        
          (Shader.TileMode.CLAMP)             (Shader.TileMode.MIRROR)                (Shader.TileMode.REPEAT)


    接下来改下渐变起始点和终点为左上角和1/3屏幕高度的右下角:

    new LinearGradient(0, 0, mTotalWidth, mTotalHeight/3, mStartColor, mToColor, Shader.TileMode.CLAMP)


    还是绘制相同的矩形,看下三种模式下的效果:

                                                                      

      (Shader.TileMode.CLAMP)             (Shader.TileMode.MIRROR)                (Shader.TileMode.REPEAT)


    细致看三种模式下的渐变,可能有些人会认为奇怪,由于感觉图一次的结尾处并非1/3高度处,原因是啥呢?是由于渐变线是斜角,大家能够看REPEAT模式下的图,一次结尾处在屏幕右边的位置刚好是屏幕 1/3 处。看到这儿应该清楚了吧;

    渐变ok了,但有时候我们的需求不止于此,还须要多种颜色渐变。而且还得控制渐变的大小。比方须要红绿蓝三种颜色渐变。而且还要 红色占比 10% ,绿色占比 60% 。蓝色占比 30%,这个时候该怎么做呢?我们来看LinearGradient 的第二个构造函数:

            /**	Create a shader that draws a linear gradient along a line.
            @param x0           The x-coordinate for the start of the gradient line
            @param y0           The y-coordinate for the start of the gradient line
            @param x1           The x-coordinate for the end of the gradient line
            @param y1           The y-coordinate for the end of the gradient line
            @param  colors      The colors to be distributed along the gradient line
            @param  positions   May be null. The relative positions [0..1] of
                                each corresponding color in the colors array. If this is null,
                                the the colors are distributed evenly along the gradient line.
            @param  tile        The Shader tiling mode
    	*/
    	public LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[],
                TileMode tile) {---省略---}

     当中 colors 代表要使用的颜色色值数组,positions 代表与颜色相应的相应的位置数组,位置数组里的取值 从 0 -1,分别相应从開始到结束。这样里面的每个值就相应了距离起始点的距离比例。而且使用该方式,则默认觉得有多种颜色,所以颜色数组长度必须大于等于2,而且两数组长度必须一致。这么说可能还不是特清晰,我们来完毕上面的需求-须要红黄蓝三种颜色渐变,而且还要 红色占比 10% ,黄色占比 60% 。蓝色占比 30%:

    好。咱们依照上面的说法。把相应颜色的位置依照比例设置上。看下效果:

        // 颜色数组
        private int[] mColors = new int[] {
                COLOR_RED, COLOR_GREEN, COLOR_BLUE
        };
        // 与颜色数组相应的位置数组
        private float[] mPositions = new float[] {
                0.1f, 0.7f, 1f
        };

    我们就以 REPEAT 模式为例。看看效果:


    Shader.TileMode.REPEAT


    我们发现根本和我们想要的比例不一样。并非 红:绿:蓝 = 1 :6 :3 。反而 接近于 4 :4.5 :1.5 ,为什么?

    那是由于 positions 里面代表的是相应颜色的比例位置,红色在 10% 处,绿色在 70% 处 ,蓝色在 100% 处 ,那么 红色和 绿色之间则有 60%的位置用于渐变。那么平均下来红色和绿色分别能够占领 30% 的位置(当然中间是渐变的,仅仅是估算大概位置),同理能够计算出  绿 和 蓝 渐变的过程中各占 15% 的位置,综合起来则是 4:4.5:1.5;

    这么计算下来。我们能够依据需求能够算出 红 和 绿 的距离为20% ,绿和蓝的距离为 60% ,这样能够的大致算出颜色的位置应该为-红 : 绿 :绿:蓝 =  0f, 0.2f, 0.4f, 1f;

        // 颜色数组
        private int[] mColors = new int[] {
                COLOR_RED, COLOR_GREEN, COLOR_GREEN, COLOR_BLUE
        };
        // 与颜色数组相应的位置数组
        private float[] mPositions = new float[] {
                0f, 0.2f, 0.4f, 1f
        };

    展示效果例如以下:


    Shader.TileMode.REPEAT

    好了。LinearGradient 就说这么多;

    2.接下来我们来看  RadialGradient (环形渲染):

    熟悉了 LinearGradient (线性渲染)之后,RadialGradient (环形渲染)就变得非常easy了,看源代码可知相同有两个可用的构造函数:

    <span style="font-family:SimSun;font-size:14px;">public RadialGradient(float x, float y, float radius,int color0, int color1, TileMode tile) {}</span>
    该函数里的 x,y分别代表 RadialGradient (环形渲染)所基于的中心点,color0 代表中心点的颜色。color1代表结尾处的颜色,tile 相同代表emun Shader.TileMode (平铺模式);

    public RadialGradient(float x, float y, float radius,int colors[], float positions[], TileMode tile) {}
    该方法里与上面不同的就是单一颜色变成了颜色数组。并与之相应一个位置比例数组。相应关系和上面 LinearGradient (线性渲染)一样。

    那么使用RadialGradient (环形渲染)我们能够做些什么呢,举个比較有用的小栗子。用于给大家开开脑洞:

    eg:用于做柔和的渐变背景:

    在产品的设计中。为了美观,一般背景都不会是纯色,要么加上一些光泽,要么是渐变,要么加上一些其它的元素,而渐变的时候。线性渐变相对会显得比較硬,所以设计师在做界面的时候非常可能会选择环形渐变这种方式,例如以下图:


    对于这种效果,假设直接切图的话在xxhdpi(1920 * 1080)这种分辨率上预计少则会有几十K。而且为了控制在小分辨率手机上执行时的内存大小,则还须要切相应小型分辨率的图。这样加下来,则会影响到上百K的包大小,有人可能会说,这么点大小实在不值一提,产品用户量小的话确实影响不大,等产品用户量大到一定数量,这点包大小可能就会影响到产品各方面的数据,而这些数据影响到的也直接是真金白银,言归正传,我们要做的就是在保证产品优势的条件下依照耀鸡湿的要求进行实现,事实上有条经验挺好使的,那就是:

    看到一个效果之后。我们能够先别急着去做,能够先问射鸡湿在软件上的实现方式,射鸡湿们一般就有用PS,AE 这种设计软件,PS里面用图层进行叠图,叠图的时候又可能选择不同的叠加模式。比方做渐变会选择渐变模式,然后直接拉渐变线形成效果 ... ...。通过知道他们的实现方式。非常大程度上就能解决我们程序上的实现问题,由于里面绝大部分的方式android里面都有可用的api去进行实现。即使没有或存在版本号兼容的问题,也相当于给我们提供了一种思路,让我们去想其它的解决方式:

    好,我们还是回到上面的需求。要实现上面的效果假定提出了三个方案:

    1.直接由UX进行切图,包大小增大150K;

    2.使用纯色背景,然后在背景上放一张光的图片。进行叠加产生效果 -毕竟是两张图。叠加效果稍有不佳。而且相同增大包大小;

    3.纯代码实现相应设计效果,该方式有以上两个长处,即不须要切图,而且界面展示效果佳;

    既然如此。我们就直接上第三种实现方式。通过与射鸡湿沟通。能够知道几个重要数据,首先渐变的起始点在图上最亮的地方。我们使用屏幕高的比例大概取14/15 处,然后中间共同拥有四种颜色的渐变,我们能够直接让射鸡湿提供 相应的色值 和大概的位置。有了这些数据我们做出来的效果也就自然而然和她们在PS上做的效果保持了高度的一致性,好了,直接上代码:

        // 颜色数组
        private static final int[] COLOR_BLUES = new int[] {
                0xff1db6ff, 0xff1db6ff, 0xff0b58c2, 0xff002a6d
        };
    
        // 颜色相应的位置数组
        private static final float[] COLOR_LOCATIONS = new float[] {
                0, 0.15f, 0.65f, 1f
        };
     mRadialGradient = new RadialGradient(mHalfWidth, mTotalHeight * SCREEN_HEIGHT_FRACTION,
                    mTotalHeight * SCREEN_HEIGHT_FRACTION,
                    mCurrentColors, COLOR_LOCATIONS, Shader.TileMode.MIRROR);
            mLightGradientPaint.setShader(mRadialGradient);


    相同还是画矩形:

    canvas.drawRect(0, 0, mTotalWidth, mTotalHeight, mLightGradientPaint);


    效果就不再贴了。如上;

    这个时候,有些人可能就会说了,这种需求比較简单,假设是这种需求该怎样做呢。页面是渐变的,然后后台在扫描。依据扫描的情况,前台展示的界面还须要动态的过渡到其它的渐变色(当前页面是渐变的。但整体偏蓝,须要过渡到偏红。而每时每刻单个界面也是渐变的),好了这个问题这里不说了,有兴趣的能够思考下。后面再抽出时间专门讲;

    当然用RadialGradient 还能够做手点击即产生震荡展开的水波纹效果,大家能够考虑下,注意不要忽略了这三种不同的模式。

    3.SweepGradient:扫描渐变渲染/梯度渲染:

    直接看android 给我们提供的构造方法,一如即往,还是两个:

    public SweepGradient(float cx, float cy, int color0, int color1) {}
    public SweepGradient(float cx, float cy,
                             int colors[], float positions[]) {}


    从方法设计上基本和上面保持一致,中心点位置,然后就是起始点和终点颜色。假设须要多种颜色均匀或不均匀渐变,则採用传入数组的方式,颜色和比例进行相应,我们来写个小样例,还是用上面的几种颜色:

        // 定义几种初始颜色
        private static final int COLOR_RED = 0xffff0000;
        private static final int COLOR_GREEN = 0xff00ff00;
        private static final int COLOR_BLUE = 0xff0000ff;
    
        // 颜色数组
        private int[] mColors = new int[] {
                COLOR_RED, COLOR_GREEN, COLOR_GREEN, COLOR_BLUE
        };
        // 与颜色数组相应的位置数组
        private float[] mPositions = new float[] {
                0f, 0.2f, 0.4f, 1f
        };
    mGradientPaint.setShader(new SweepGradient(mHalfWidth, mHalfHeight, mColors, mPositions));
    还是绘制全屏矩形:

    canvas.drawRect(0, 0, mTotalWidth, mTotalHeight,mGradientPaint);
    我们来看下效果:



    大家能够看到,比例还是等于之前咱们算的 1:6:3。说明这几种渲染模式都是相同的计算规则。好了SweepGradient(扫描渐变渲染/梯度渲染)就说这么多;

    大家还能够自己将绘制的矩形改为圆形。然后再让它旋转起来。咱们一起来看最后一种单独的渲染模式

    4.BitmapShader(位图渲染):

    我们还是先看android给我们提供了什么方法可用:

    public BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY) {
    各自是一张位图 和 横向。竖向的平铺模式;

    我们如果如今背景是一个墙面,墙面一般都是相同的砖块,这时候我们就能够直接用一小部分砖块直接无缝生成整个界面,而不需切整个背景图。


                  

    如此相同的也能够一定降低包大小,优化执行时内存;

    当然BitmapShader的用途还能够非常多,这里就举这个栗子当作给大家开脑洞吧;

    最后咱们一起来看看ComposeGradient(组合渲染)

    5、ComposeGradient(组合渲染):

    顾名思义,也就是能够把多种渲染模式组合到一起进行绘制;

    android 给我们提供了两个方法使用:

    public ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode) {}
    public ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode) {

    第一个我们在前面PorterDuffColorFilter里讲过。叠加模式(PorterDuff.Mode),第二个是后面即将要讲的图像的混合模式,这两种模式事实上是很类似的,另外两个參数也就是 两种渲染方式;

    以叠加模式(PorterDuff.Mode)为例,讲个小样例:

    QQ 或 非常多应用里面都有圆形头像。怎么做呢?用shader做的话就非常easy,我们仅仅须要绘制一个圆,仅仅是把头像的bitmap作为BitmapShader 传到paint里就可以;

    原图例如以下:


    好,接下来我们首先须要让他显示成圆形。

    我们仅仅须要下面简单的几步:

    1.创建位图

    2.将位图scale到目标大小

    3.创建BitmapShader并设置给paint

    4.使用该paint绘制目标大小的圆

            //创建位图
            mBitmap = ((BitmapDrawable) getResources().getDrawable(R.drawable.touxiang)).getBitmap();
            //将图scale成我们想要的大小
            mBitmap = Bitmap.createScaledBitmap(mBitmap, mTotalWidth, mTotalWidth, false);
    
            // 创建位图渲染
            BitmapShader bitmapShader = new BitmapShader(mBitmap, TileMode.REPEAT, TileMode.REPEAT);
            // 将位图渲染设置给paint
            mBitmapPaint.setShader(bitmapShader);

    onDraw中:

    canvas.drawCircle(mHalfWidth, mHalfWidth, mHalfWidth, mBitmapPaint);


    此时效果例如以下:


    当然。我们的目标肯定不止于此。帅气逼人的脸庞必须主体突出,有虚有实,好,咱们让图片显示出外围虚化的效果。把帅脸显出来,该怎么做呢,事实上仅仅须要加一个以中心为全透明,外围为纯白的环形渐变(RadiulGradient),代码上仅仅须要做例如以下改变;

            // 创建位图渲染
            BitmapShader bitmapShader = new BitmapShader(mBitmap, TileMode.REPEAT, TileMode.REPEAT);
            // 创建环形渐变
            RadialGradient radialGradient = new RadialGradient(mHalfWidth, mHalfWidth, mHalfWidth,
                    Color.TRANSPARENT, Color.WHITE, TileMode.MIRROR);
            // 创建组合渐变,因为直接按原样绘制就好,所以选择Mode.SRC_OVER
            ComposeShader composeShader = new ComposeShader(bitmapShader, radialGradient,
                    PorterDuff.Mode.SRC_OVER);
            // 将组合渐变设置给paint
            mBitmapPaint.setShader(composeShader);


    我们再来看下效果:


    当然,我们直接均匀渐变。会让渐变盖住一部分脸,让脸显得朦胧。假设我们须要不影响脸的展示该怎么做呢?创建颜色和位置数组把中心脸的部分露出来,我们再改改:
    先加两个数组记录颜色和位置:
    private static final int[] mColors = new int[] {
                Color.TRANSPARENT, Color.TRANSPARENT, Color.WHITE
        };
        private static final float[] mPositions = new float[] {
                0, 0.6f, 1f
        };

    然后改动环形渐变:

            // 创建环形渐变
            RadialGradient radialGradient = new RadialGradient(mHalfWidth, mHalfWidth, mHalfWidth,
                    mColors, mPositions, TileMode.MIRROR);

    再看下效果:


    好了,这篇文章就先写到这儿吧。实在写不动了,预计大家看的也够累,后面的重点主要有两个:
    1.setXfermode(Xfermode xfermode)--图像混合模式
    2.文字的绘制
    这两块后面都单独用一篇文章写吧。大家能够先看看后面都还有哪些方法。

    setShadowLayer(float radius ,float dx,float dy,int color):在图形以下设置阴影层。产生阴影效果,radius为阴影的角度。dx和dy为阴影在x轴和y轴上的距离。color为阴影的颜色

    setStyle(Paint.Style style):设置画笔的样式,为FILL,FILL_OR_STROKE,或STROKE

    setStrokeCap(Paint.Cap cap):当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式。如圆形样式;

    Cap.ROUND,或方形样式Cap.SQUARE 

    setSrokeJoin(Paint.Join join):设置绘制时各图形的结合方式,如平滑效果等;

    setStrokeWidth(float width):当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的粗细度;

    setXfermode(Xfermode xfermode)设置图形重叠时的处理方式,如合并,取交集或并集;


    Text文本绘制相关:

    setFakeBoldText(boolean fakeBoldText)模拟实现粗体文字,设置在小字体上效果会很差;

    setSubpixelText(boolean subpixelText)设置该项为true,它能够保证在绘制斜线的时候使用抗锯齿效果来平滑该斜线的外观;

    setTextAlign(Paint.Align align)设置绘制文字的对齐方向。

    setTextScaleX(float scaleX)设置绘制文字x轴的缩放比例,能够实现文字的拉伸的效果;

    setTextSize(float textSize)设置绘制文字的字号大小;

    setTextSkewX(float skewX)设置斜体文字,skewX为倾斜弧度;

    setTypeface(Typeface typeface)设置Typeface对象。即字体风格,包含粗体,斜体以及衬线体,非衬线体等;

    setUnderlineText(boolean underlineText)设置带有下划线的文字效果;

    setStrikeThruText(boolean strikeThruText)设置带有删除线的效果;


    前两天看到一个比較酷炫的loading效果,下一篇文章跟大家分享一下:




    我正在參加 CSDN 2015博客之星评选 感恩分享活动。假设认为文章还不错,请投个票鼓舞下吧:http://vote.blog.csdn.net/blogstar2015/candidate?

    username=tianjian4592









  • 相关阅读:
    MySQL-数据表操作
    MySQL基础命令
    Navicat 15激活
    禅道-启动失败问题整理
    python-开头的注释作用及区别
    SpringBoot、SpringCloud版本中GA/PRE/SNAPSHOT的详解
    mybatis的一些重要配置
    简历对应的知识点
    idea的破解
    SFTP和FTP的区别
  • 原文地址:https://www.cnblogs.com/cynchanpin/p/6728910.html
Copyright © 2011-2022 走看看