转载:Android绘图Canvas十八般武器之Shader详解及实战篇(下)

LinearGradient 线性渐变渲染器

LinearGradient中文翻译过来就是线性渐变的意思。线性渐变通俗来讲就是给起点设置一个颜色值如#faf84d,终点设置一个颜色值如#CC423C,然后在一个区域内绘图,这个图像的颜色将呈现非常美妙的效果,颜色会从起点颜色到终点颜色过渡。给一张图,大家直观感受一下。

我们看LinearGradient的API,发现它只有两个构造方法,非常简单。

LinearGradient (float x0, 
            float y0, 
            float x1, 
            float y1, 
            int color0, 
            int color1, 
            Shader.TileMode tile)

//x0 和y0是颜色渐变的起点坐标。
//x1和y1是颜色渐变的终点坐标。
//color0是起点颜色值 
//color0是终点颜色值。
//tile 就是TileMode类型参数,这个我们上一篇已经讲过了。

LinearGradient的用法

  1. 创建LinearGradient对象,并设置它的起点坐标,终点坐标,起点颜色值,终点颜色值,然后设置TileMode

     mShader = new LinearGradient(0,0,w,0,Color.parseColor("#faf84d"),
             Color.parseColor("#CC423C"), Shader.TileMode.CLAMP);  
    
  2. 将Shader赋值给Paint对象。

     mPaint.setShader(mShader);  
    
  3. 绘制图形

     canvas.drawRect(0,0,w,h/2,mPaint);
    

LinearGradient还有一个构造方法。

    LinearGradient (float x0, 
            float y0, 
            float x1, 
            float y1, 
            int[] colors, 
            float[] positions, 
            Shader.TileMode tile)

需要注意的是,这里有一个int[] colors 和 float[] positions它们代表什么意思呢?

实际上LinearGradient除了可以指定起点颜色值和终点颜色值外,还有可以指定许多中间颜色值。就如彩虹一般。而colors[]数组存放的就是这样的颜色值组合。大家看看代码和图片效果就可能直观感受到。

渐变的是一个颜色序列(#faf84d,#003449,#808080,#cc423c)

mShader = new LinearGradient(0,0,w,0,new int[]{Color.parseColor("#faf84d"),Color.parseColor("#003449"),
    Color.parseColor("#808080"),
    Color.parseColor("#CC423C")},null,Shader.TileMode.CLAMP);
mPaint.setShader(mShader);
canvas.drawRect(0,0,w,h/2,mPaint);

颜色很丰富是不是?颜色从一个颜色过渡到另外一个颜色直到过渡到终点颜色。

大家有没有注意到,我将上面代码中的float[] positon置为null,而它代表了什么呢?它其实与colors数组对应,代表了各个颜色值在位置,positions数组中的值大小范围从0.0到1.0,0.0代表起点位置,1.0代表终点位置。如果这个数组被置为空的话,颜色就会平均分配。 ,如果这个数组不为空呢?我们结合代码效果来讲解。

mShader = new LinearGradient(0,0,w,0,new int[]{Color.parseColor("#faf84d"),Color.parseColor("#003449"),
    Color.parseColor("#808080"),
    Color.parseColor("#CC423C")},new float[]{0.0f,0.6f,0.8f,1.0f},Shader.TileMode.CLAMP);
mPaint.setShader(mShader);
canvas.drawRect(0,0,w,h/2,mPaint);

代码中colors[]并没有改变,只是多了positon[],效果却不一样了。

new int[]{Color.parseColor("#faf84d"),Color.parseColor("#003449"),
    Color.parseColor("#808080"),
    Color.parseColor("#CC423C")}

new float[]{0.0f,0.6f,0.8f,1.0f}

// #faf84d对应的position值是0.0 所以为起点位置。

// #003449对应0.6 所以这个颜色位置起点到终点中间0.6比率的地方。

// #808080对应0.8 这个颜色在0.8比率的地方

// #cc423c对应1.0 这个颜色为终点处的颜色

需要注意的是,position[]数组中的数组最好是由小到大,这是为什么呢?它不支持0.8 然后再到0.6之类。大家看代码。

mShader = new LinearGradient(0,0,w,0,new int[]{Color.parseColor("#faf84d"),Color.parseColor("#003449"),
    Color.parseColor("#808080"),
    Color.parseColor("#CC423C")},new float[]{0.6f,0.8f,0.2f,0.0f},Shader.TileMode.CLAMP);

可以看到颜色可以从0.6的位置过渡到0.8,后面的就不起作用了。

RadialGradient 环行渲染器

我喜欢称它为径向渐变,因为PHOTOSHOP中就对应有径向渐变的概念。

径向渐变,所谓径向就是辐射状,由中心向四周辐射。

径向渐变也只有两个构造方法,基本用法跟线性渐变差不多。

RadialGradient (float centerX, 
            float centerY, 
            float radius, 
            int centerColor, 
            int edgeColor, 
            Shader.TileMode tileMode)

//centerX  圆心的X坐标
//centerY  圆心的Y坐标
//radius   圆的半径
//centerColor  中心颜色
//edgeColor   边缘颜色
//tileMode   这个不用介绍了吧?

代码如下:

mShader = new RadialGradient(w/2,h/2,w/2,Color.parseColor("#faf84d"),
            Color.parseColor("#CC423C"), Shader.TileMode.CLAMP);
mPaint.setShader(mShader);
canvas.drawRect(0,0,w,h,mPaint);

效果图:

    RadialGradient (float centerX, 
                float centerY, 
                float radius, 
                int[] colors, 
                float[] stops, 
                Shader.TileMode tileMode)

同LinearGradient一样,这里也有一个颜色数组和位置数组,意义也是一样的,stop[]也可以为null,如果为null的话,color[]数组的颜色就会平均分配在区域之中。否则,它对应的颜色就会按照比例填充。

mShader = new RadialGradient(w/2,h/2,w/2,new int[]{Color.parseColor("#00aa00"),Color.parseColor("#880033"),
    Color.parseColor("#F8795A"),
    Color.parseColor("#CC423C")},new float[]{0.0f,0.2f,0.8f,1.0f}, Shader.TileMode.CLAMP);
mPaint.setShader(mShader);
canvas.drawRect(0,0,w,h,mPaint);

效果图:

SweepGradient 梯度渐变渲染器

梯度渐变,或者叫做扫描渐变。我觉得扫描更适合吧,它是指从x轴出发,以逆时钟为方向,以扫描360度形成的区域进行颜色的变换。

    SweepGradient (float cx, 
                   float cy, 
                   int color0, 
                   int color1)
        
        //color0是起始颜色
        //color1是终止颜色

代码示例:

mShader = new SweepGradient(w/2,h/2,Color.RED,Color.BLUE);
mPaint.setShader(mShader);
canvas.drawRect(0,0,w,h,mPaint);

效果图:

SweepGradient (float cx, 
            float cy, 
            int[] colors, 
            float[] positions)

大家应该也明白这个方法中每个参数的含义。

mShader = new SweepGradient(w/2,h/2,new int[]{Color.RED,Color.CYAN,Color.YELLOW,
            Color.GREEN,Color.MAGENTA,Color.BLUE},new float[]{0.0f,0.2f,0.3f,0.4f,0.8f,1.0f});
mPaint.setShader(mShader);
canvas.drawRect(0,0,w,h,mPaint);

我们把颜色丰富点,本来想弄成赤橙黄绿青蓝紫,结果因为懒,就随便弄了点,效果如下:

ComposeShader 组合渲染器

混合渲染,在这里我又开始称Shader为渲染了,因为ComposeShader不仅仅用于颜色,它能将两个Shader对象参考Xfermode规则进行颜色混合。

这张图详细的解释了混合模式的组合效果,再看ComposeShader的两个构造方法。

ComposeShader (Shader shaderA, Shader shaderB, Xfermode mode)

ComposeShader (Shader shaderA, Shader shaderB, PorterDuff.Mode mode)

实战1

  1. 编写1个BitmapShader.

  2. 编写1个RadiasGradient。

  3. 将它们进行混合产生新的Shader.

  4. 以新的Shader绘制一个圆。

     public class CircleView extends View {
         private Paint mPaint;
         private Shader mShader;
    
         public CircleView(Context context) {
                 this(context,null);
         }
    
         public CircleView(Context context, AttributeSet attrs) {
                 this(context, attrs,0);
         }
    
         public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
                super(context, attrs, defStyleAttr);
                mPaint = new Paint();
                mPaint.setAntiAlias(true);
         }
    
         @Override
         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
               //这里为了方便演示,将尺寸固定为400*400
         setMeasuredDimension(400,400);
         }
    
         @Override
         protected void onDraw(Canvas canvas) {
               super.onDraw(canvas);
               int w = getWidth();
               int h = getHeight();
               int radius = w <= h ? w/2 : h/2;
    
    
               Bitmap bmp = BitmapFactory.decodeResource(getResources(),R.drawable.repeat);
               Bitmap result = Bitmap.createScaledBitmap(bmp,w,h,false);
    
               //1. 编写1个BitmapShader
               BitmapShader bitmapShader = new BitmapShader(result, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
               //2. 编写1个RadiasGradient
               RadialGradient radialGradient = new RadialGradient(radius,radius,radius,Color.BLACK,Color.TRANSPARENT, Shader.TileMode.CLAMP);
               //3. 将它们进行混合产生新的Shader
               ComposeShader composeShader = new ComposeShader(bitmapShader,radialGradient,new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    
               mPaint.setShader(composeShader);
               //4. 以新的Shader绘制一个圆。
               canvas.drawCircle(w/2,h/2,radius,mPaint);
    
         }
     }
    

我们来看看混合后的效果是怎么样的。

哇,好梦幻的狗狗。

实战2 倒影功能

以前刚开始学Android的时候,项目里面要用到倒影,当时的自己是写不出来的,好在网上有现成的代码可以copy。现在我们可以运用ComposeShader来实现这么一个View。

需求分析

  1. 倒影与原图比例为1:4。
  2. 倒影与原图之间有5px的间隙。
  3. 倒影的下边缘不能太平整了,要尽量跟真实的一致。

好了为了节省篇幅,我只粘贴onDraw()中的代码。

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //定义各种宽高
    int bmpWidth = 200;
    int bmpHeight = 200;
    int gap = 5;
    int reflectionHeight = bmpHeight / 4;

    //绘制原图
    Bitmap bmp = BitmapFactory.decodeResource(getResources(),R.drawable.repeat);
    Bitmap result = Bitmap.createScaledBitmap(bmp,bmpWidth,bmpHeight,false);
    canvas.drawBitmap(result,0,0,null);

    canvas.save();
    //向下移动准备在原图下方绘制倒影
    canvas.translate(0,bmpHeight+gap);
    Matrix m = new Matrix();
    m.postScale(-1f,1f);
    m.postRotate(-180);
    //将原图水平翻转
    Bitmap texture = Bitmap.createBitmap(result,0,0,result.getWidth(),result.getHeight(),m,false);
    //创建BitmapShader和LinearShader。
    BitmapShader bitmapShader = new BitmapShader(texture, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
    LinearGradient linearGradient = new LinearGradient(0,0,0,reflectionHeight,Color.BLACK,Color.TRANSPARENT, Shader.TileMode.CLAMP);
    ComposeShader composeShader = new ComposeShader(bitmapShader,linearGradient,new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    mPaint.setShader(composeShader);
    //以混合模式绘制矩形区域,可以获得倒影效果。
    canvas.drawRect(0,0,bmpWidth,reflectionHeight,mPaint);

    canvas.restore();
}

效果图:

倒影出来了。

转载:Android绘图Canvas十八般武器之Shader详解及实战篇(上)

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

推荐阅读更多精彩内容