Android 高级UI--1,Android图形绘制Paint之Shader(着色器)

899 阅读7分钟
一:什么是Shader
Shader在三维软件中我们称之为着色器,其作用是来给图像着色。Shader类是Android在图形变换中非常重要的一个类。通常我们调用画笔工具(Paint)的setShader(Shader shader)方法将自己创建的shader对象作为参数传入使用。
但着色器是什么?具体对没有计算机图像或美术基础的程序员来说,其实还是很抽象不好理解的。按照字面意思,着色就是对我们绘制的图像进行颜色设置?那Paint不是有了setColor的方法对图像设置颜色吗?为什么又要一个Shader呢?我个人更倾向于把Shader理解为一套设置颜色的规则或方案(可简单理解为规定了在什么位置画什么色)。我们用setColor方法的局限在于对色彩的设置是定死的纯色。而着色器能给我们提供一个颜色方案,比如绘制一个图像要求从开始到结束以由红到蓝渐变显示,那我就可以通过Shader指定这一色彩渐变的规则 交由画笔Paint按此规则绘制。
一般情况下我们不会直接使用Shader类,而是使用它派生的子类。那我们看下它有哪些子类:



从官方文档可知其子类有五个,因为ComposeShader意为组合着色器。所谓组合,就是把两个 Shader 一起使用,故将其放最后说明。在这里以BitmapShader,xxxGradient,ComposeShader的顺序逐一实践。

二:BitmapShader
位图着色器。先来简单实践一下,比如我们通常在开发中需要绘制出一个圆形头像。使用BitmapShader可以轻易实现。


 mPaint = new Paint();
 Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.xyjy2);
 BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, 
                    Shader.TileMode.CLAMP);
 mRadius = bitmap.getHeight() > bitmap.getWidth() ? 
                 bitmap.getWidth() / 2 : bitmap.getHeight() / 2;
 mPaint.setShader(bitmapShader);
 ...
 canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);

原图与处理后对比

如代码所示,实现这个效果可以解释为 一支设定为位图着色器所指定规则画笔(Paint),在画布(Canvas)上画了一个圆。
此时可能有一个疑问随之而来。前文说的Shader可以理解为一套设置颜色的规则或方案。颜色渐变尚可理解为是一套着色规则,那看上面代码BitmapShader是基于Bitmap的,Bitmap跟颜色没多大关系啊,那怎么可以说BitmapShader是一套设置颜色的方案呢?这里我们可以简单这么理解。我们在手机屏幕上看到的一切都是由一个个像素点所显示的颜色值组成的。所以显示的图片也是由一个个像素点的颜色值(如我们通常说的RGB值)所组成的,那我们通过解析png,jpg等文件生成的Bitmap对象,是不是也需要一个个像素点对应一个个颜色值然后在屏幕上才能将图片正常显示出来。那是不是BitmapShader也是一套设置颜色的规则,在哪个位置绘制哪个色彩值,这个规则也就主要由Bitmap(也即你所加载的图像文件)决定。

继续下一步!
在创建BitmapShader对象时我们不止传入了bitmap对象。如下:


BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, 
                    Shader.TileMode.CLAMP);


那后面的那两个参数具体是干嘛的呢?字面意思是拉伸模式,第一个Shader.TileMode是X轴的显示模式,第二个是Y轴上的显示模式。进入该类



是一个枚举类有三个值,CLAMP,REPEAT,MIRROR并都有做解释。我的理解是:
  • CLAMP --是拉伸边缘像素铺满
  • REPEAT ---横向纵向不足的重复放置
  • MIRROR ---是横向纵向不足处不断翻转镜像平铺
字面解释还是有些抽象,具体看看效果大概就能明白了。


BitmapShader bitmapShader = new BitmapShader(bitmap, 
          Shader.TileMode.CLAMP/REPEAT/MIRROR, Shader.TileMode.CLAMP/REPEAT/MIRROR);
 canvas.drawRect(0, 0, mWidth, mHeight, mPaint);
# mWidth.mHeight取大于图像宽高二倍以上即可看到效果


XY:CLAMP


XY:REPEAT


XY:MIRROR


当然根据位图着色器来写个艺术字啥的也是很容易实现的啦。


bitmapShader1 = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
mPaint.setShader(bitmapShader1);
mPaint.setAlpha(50);
//绘制底层图像作为参照
canvas.drawRect(0, 0, mWidth, mHeight, mPaint);

mPaint.setAlpha(255);
canvas.drawText("男儿何不带吴钩",20,300,mPaint);
canvas.drawText("收取关山五十州",20,600,mPaint);
canvas.drawText("请君暂上凌烟阁",20,900,mPaint);
canvas.drawText("若个书生万户侯",20,1200,mPaint);


BitmapShader 艺术字

三.LinearGradient
线性渐变。先看一下怎么创建该类对象,构造函数如下


显而易见可以将1,2行归为第a类,3,4行归为第b类。两类构造函数,前四个参数都一致float类型,表示起始坐标x、y,结束坐标x,y。最后一个也即拉伸模式,上文已作介绍,不再赘述。不同的是第5,6两位参数,b是两个int/long型color,color1这表示的是渐变的起始颜色和结束颜色;a是两个数组分别(colors,positions),在我们遇到需指定除起止两处外中间多个位置(相对)渐变色数值时可用到。这里就以复杂点的b实践下。


/**线性渐变
 * int[]  mColors, 中间依次要出现的几个颜色
 * float[] positions 相对位置数组,position的取值范围[0,1],
                       作用是指定几个颜色分别放置在那个位置上。
 * 如果传null,渐变就线性变化。
 * tile 用于指定控件区域大于指定的渐变区域时,空白区域的颜色填充方法
 */
    private int[] mColors = {Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW};
    //水平 0,0 ->300,0
    LinearGradient linearGradient =
                new LinearGradient(0, 0, 300, 0, mColors, null, Shader.TileMode.CLAMP);
    mPaint.setShader(linearGradient);
    canvas.drawRect(0, 0, 300, 300, mPaint);
    canvas.translate(0,330);
    //垂直 0,0 -> 0,300
    LinearGradient linearGradient1 =
        new LinearGradient(0, 0, 0, 300,
                mColors, null, Shader.TileMode.CLAMP);
    mPaint.setShader(linearGradient1);
    canvas.drawRect(0, 0, 300, 300, mPaint);
    //平移画布
    canvas.translate(0,330);
    //倾斜 0,0 -> 300,300
    LinearGradient linearGradient2 =
        new LinearGradient(0, 0, 300, 300,
                mColors, null, Shader.TileMode.CLAMP);
    mPaint.setShader(linearGradient2);
    canvas.drawRect(0, 0, 300, 300, mPaint);

线性渐变

我们在工作中可能会有需求要求实现一个渐变的水平进度条,用LinearGradient便可实现了。

四.RadialGradient
环形渐变。


因为环形渐变是从点向面扩散的(可以想象下平静水面扔了一个石头水波纹的扩散效果),所以容易理解上面参数centerX,centerY,radius;分别代表原点x,y坐标及扩散最大半径。至于后面几项参数类似上面介绍线性渐变时的解释,应不难理解其各自含义。我们来实践下看看具体效果就明白了。


mRadius = 250;
canvas.translate(30, 0);
//1.渐变中心位于圆心&起止颜色值 见下方:顶部图
RadialGradient radialGradient =
        new RadialGradient(mRadius, mRadius, mRadius,
                Color.GREEN, Color.RED, Shader.TileMode.CLAMP);
mPaint.setShader(radialGradient);
canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);

canvas.translate(0, 2 * mRadius  + 20);
//2.渐变中心位于圆心&多颜色值 见下方:中间图
int[] colors = new int[]{
        Color.YELLOW, Color.GREEN, Color.WHITE, Color.RED};
float[] positions = new float[]{0.2f, 0.3f, 0.6f, 0.9f};
RadialGradient radialGradient1 =
        new RadialGradient(mRadius, mRadius, mRadius,
                colors, positions, Shader.TileMode.CLAMP);
mPaint.setShader(radialGradient1);
canvas.drawCircle(mRadius, mRadius, mRadius, mPaint); 

canvas.translate(0, 2 * mRadius  + 20); 
//3.渐变中心不在圆心&多颜色值 见下方:底部图
RadialGradient radialGradient2 =
        new RadialGradient(0, 0, mRadius,
                colors, positions, Shader.TileMode.REPEAT);
mPaint.setShader(radialGradient2);
canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);


环形渐变

四.SweepGradient
扫描渐变:


cx,cy即为扫描中心x,y坐标.


//mRadius取宽高最小值一半
SweepGradient swGradient =
        new SweepGradient(mRadius, mRadius,
                Color.RED, Color.GREEN);
mPaint.setShader(swGradient);
canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);

扫描渐变

五.ComposeShader
组合着色器。


构造函数第一二个参数为两个shader,组合着色器的用途即,将shaderA,shaderB两个shader以指定的模式组合起来。(注:指定模式有Xfermode,PorterDuff.Mode及api29加入的BlendMode等。之后会有专门的文章来介绍Xfermode的使用。可以理解为两个shader组合起来的方式,这里就不具体一一介绍。)


mPaint = new Paint();
mBitMap = ((BitmapDrawable) getResources().getDrawable(R.mipmap.xyjy2)).getBitmap();
LinearGradient linearGradient = new LinearGradient(0, 0, 500, 800,
        mColors, null, Shader.TileMode.CLAMP);
BitmapShader bitMapShader = new BitmapShader(mBitMap, Shader.TileMode.REPEAT,
        Shader.TileMode.REPEAT);

composeShader = new ComposeShader(linearGradient, bitMapShader,
        PorterDuff.Mode.ADD);
mPaint.setShader(composeShader);

...
canvas.drawRect(0, 0, 800, 1000, mPaint);


上面就是一个BitmapShader与LinearGradient组合显示的效果。


...
到此为止,Android Shader相关知识以大致学习完了,做一个简单总结。



预告:下一篇介绍图片滤镜相关知识。