前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android绘图Canvas十八般武器之Shader详解及实战篇(下)

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

作者头像
Frank909
发布2019-01-14 18:09:26
1.3K0
发布2019-01-14 18:09:26
举报
文章被收录于专栏:Frank909Frank909

前言

上一篇《Android绘图Canvas十八般武器之Shader篇(上)》 我们知道了Bitmap的用法,及TileMode的详细情况。接下来,这一篇作为整个知识体系的下半部要讲的是Shader的其它几个子类。 首先声明,网上很多称之为渲染,如图形渲染,线性渲染等,而在这里我更喜欢称为渐变。

LinearGradient 线性渐变渲染器

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

这里写图片描述
这里写图片描述

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

代码语言:javascript
复制
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的用法。

代码语言:javascript
复制
//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还有一个构造方法。

代码语言:javascript
复制
LinearGradient (float x0, 
                float y0, 
                float x1, 
                float y1, 
                int[] colors, 
                float[] positions, 
                Shader.TileMode tile)

需要注意的是,这里有一个int[] colorsfloat[] positions它们代表什么意思呢? 实际上LinearGradient除了可以指定起点颜色值和终点颜色值外,还有可以指定许多中间颜色值。就如彩虹一般。而colors[]数组存放的就是这样的颜色值组合。大家看看代码和图片效果就可能直观感受到。

代码语言:javascript
复制
//渐变的是一个颜色序列(#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代表终点位置。如果这个数组被置为空的话,颜色就会平均分配。 ,如果这个数组不为空呢?我们结合代码效果来讲解。

代码语言:javascript
复制
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[],效果却不一样了。

代码语言:javascript
复制
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之类。大家看代码。

代码语言:javascript
复制
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中就对应有径向渐变的概念。 径向渐变,所谓径向就是辐射状,由中心向四周辐射。 径向渐变也只有两个构造方法,基本用法跟线性渐变差不多。

代码语言:javascript
复制
RadialGradient (float centerX, 
                float centerY, 
                float radius, 
                int centerColor, 
                int edgeColor, 
                Shader.TileMode tileMode)

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

上代码。

代码语言:javascript
复制
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);

效果:

这里写图片描述
这里写图片描述
代码语言:javascript
复制
RadialGradient (float centerX, 
                float centerY, 
                float radius, 
                int[] colors, 
                float[] stops, 
                Shader.TileMode tileMode)

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

代码语言:javascript
复制
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度形成的区域进行颜色的变换。

代码语言:javascript
复制
SweepGradient (float cx, 
                float cy, 
                int color0, 
                int color1)
//color0是起始颜色
//color1是终止颜色

代码示例:

代码语言:javascript
复制
mShader = new SweepGradient(w/2,h/2,Color.RED,Color.BLUE);
mPaint.setShader(mShader);
canvas.drawRect(0,0,w,h,mPaint);

效果:

这里写图片描述
这里写图片描述
代码语言:javascript
复制
SweepGradient (float cx, 
                float cy, 
                int[] colors, 
                float[] positions)

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

代码语言:javascript
复制
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的两个构造方法。

代码语言:javascript
复制
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绘制一个圆。
代码语言:javascript
复制
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()中的代码。

代码语言:javascript
复制
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();

}

效果:

这里写图片描述
这里写图片描述

倒影出来了。

好吧,关于CanvasShader部分就写完了。Canvas还有许多有趣的类和API,有时间我再写。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年12月16日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • LinearGradient 线性渐变渲染器
  • RadialGradient 环行渲染器
  • SweepGradient 梯度渐变渲染器
  • ComposeShader 组合渲染器
    • 实战1
      • 实战2 倒影功能
        • 需求分析
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档