LinearGradient 线性渐变渲染器

LinearGradient中文翻译过来就是线性渐变的意思。线性渐变通俗来讲就是给起点设置一个颜色值如#faf84d,终点设置一个颜色值如#CC423C,然后在一个区域内绘图,这个图像的颜色将呈现非常美妙的效果,颜色会从起点颜色到终点颜色过渡。给一张图,大家直观感受一下 
Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(下)-LMLPHP

我们看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[] colorsfloat[] 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);

Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(下)-LMLPHP

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

大家有没有注意到,我将上面代码中的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);

Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(下)-LMLPHP

代码中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);

Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(下)-LMLPHP

可以看到颜色可以从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);

效果: 
Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(下)-LMLPHP

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);

Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(下)-LMLPHP

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);

效果: 
Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(下)-LMLPHP

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);

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

Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(下)-LMLPHP

ComposeShader 组合渲染器

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

网上的一张图: 
Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(下)-LMLPHP

这张图详细的解释了混合模式的组合效果,我机会我也写一篇相关博文。 
再看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); }
}

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

Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(下)-LMLPHP

哇,好梦幻的狗狗。

实战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为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(下)-LMLPHP

倒影出来了。

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

04-13 22:19