Shader,中文翻译着色器,老实说,我的专业不是图像处理之类的,所以我也不清楚着色器到底是干嘛的,如果非要我在字面上加以理解,通俗就是给某个机器拿着一只画笔在白纸上画东西,而这个机器就叫着色器,我也不知道这样理解对不对,但是这些概念和东西,都不会影响今天我们要学习的内容,着色器Shader。
概念
Android中的shader,其实基本都是自定义view的时候会用到,而且是将shader赋予给paint,类似这样:
Paint paint = new Paint();
Shader shader = new Shader();
paint.setShader(shader);
但是大家一般不会直接使用Shader,而是使用Shader的子类,Shader的子类有5个,如图:
在讲这些子类之前,我希望大家先理解了shader的实质,然后再去学习这些子类,我想,这样会轻松很多。
shader按照官方的解释是:
Shader is the based class for objects that return horizontal spans of colors during drawing. A subclass of Shader is installed in a Paint calling paint.setShader(shader). After that any object (other than a bitmap) that is drawn with that paint will get its color(s) from the shader.
机翻+我的低水准翻译之后[滑稽.jpg]:
Shader是Object的子类,这些对象在绘制过程中返回颜色的水平跨度。通过Paint.setShader(shader)来使用Shader。调用了该方法后,用该paint绘制的任何对象(bitmap除外)都将从shader获得其颜色。
不是谁都能看懂官方都概念的,在我个人的理解下,我觉得,需要我熟悉一个东西,然后我再看它的概念我才能看的很懂。
我用我的理解方式给大家捋一捋,shader到底是干啥的。
这个时候,我们需要换一种理解方式来理解手机屏幕上面的字,如图:
这幅图上的东西是什么,白底?黑字?再也别这样理解了,现在在学习shader呢,换一种理解方式。
这张图片是由2张大小相同的纸构成的,第一张纸,纯黑色,第二张纸,纯白色的,不过第二张纸不是完整的,中间被扣走了部分,被扣走的部分,刚好看着有点像“演示文字”4个大字。然后将这两张纸重叠后,最终形成了如图所示的效果。
类似这种理解:
(PS: 不会扣字,只好扣个方框意思一下)
好的,现在我们已经丢掉了传统的思维方式:在白纸上写字。
OK,我现在要来讲shader了,shader其实就和两张纸的思维方式一样,可以这样强行理解shader,shader就是下面那张黑纸,画笔在白纸上画,或者说划?划破白纸,扣出某个形状,再搭在黑纸上,就能看到那个形状了。
比如我们把上图的那4个字的底下那层黑纸给换成一张图片,按照我们的最新的理解方式,文字就会是这样的
我们把上面那层白纸换成有点透明度的白纸,来看看全貌:
好了,跟着思路,我们开始,shader现在我们已经强行理解成下面那层的图片了,也就是说,当我们在作出如下行为的时候:
Paint paint = new Paint();
Shader shader = new Shader();
paint.setShader(shader);
实际上是修改了底层的图层,并且绘图模式变成了在上面那层扣形状,然后重叠,将下面那层的颜色通过被扣除的部分显示出来,赋予这个形状不一样的颜色。
所以,当我们为画笔设置shader的时候,使用paint.setColor()这种方法就显的没有意义了。
好了,关于shader的概念,我就先讲到这里了,要不要再回去看看官方的解释?
BitmapShader
首先我们先讲BitmapShader,容易跟上节奏,BitmapShader是Shader的子类,在使用他的时候,要传递一个bitmap,可想而知,这个bitmap就成了这个图像的底层。
我们拿这张图片作为例子底层
Paint paint = new Paint();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.shader);
BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
paint.setShader(shader);
待会再讲里面的Shader.TileMode是干嘛的,现在先这样填写,现在这个paint就被赋予了魔力,我们在onDraw中使用这个paint写几个字:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setTextSize(230);
paint.setStyle(Paint.Style.FILL);
canvas.drawText("演示文字", 100, 500, paint);
}
没错,效果就是之前看到的:
然后画圆画方:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(200, 200, 100, paint);
canvas.drawRect(400, 100, 800, 300, paint);
}
接下来我来解释下BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
中后面的那两个参数具体是干嘛的。
首先Shader.TileMode
顾名思义肯定是一种模式,到这里就可以理解为是一种显示模式,进入这个类,我们会发现一共有 CLAMP, REPEAT, MIRROR三种显示模式。方法中的第一个Shader.TileMode
是X轴的显示模式,第二个是Y轴上的显示模式。
老实说,直接看图可能会来的更爽快。
BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
好像图片所不及的区域,就取了图片对应轴上的最后一个颜色对其复制
BitmapShader(bitmap1, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);
这个就是镜面对称
BitmapShader(bitmap1, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
这个自然是无限重复了
LinearGradient
大家既然都对shader那么熟悉了,那我就直接开始使用LinearGradient了。
LinearGradient linearGradient =
new LinearGradient(100, 100, 500, 500, Color.RED, Color.GREEN, Shader.TileMode.CLAMP);
paint.setShader(linearGradient);
在onDraw里面:
paint.setStyle(Paint.Style.FILL);
canvas.drawRect(new Rect(0, 0, 1080, 1920), paint);
// 为了展现上面的100, 100, 500, 500到底在哪个区域
paint.setStyle(Paint.Style.STROKE);
paint.setShader(null);
paint.setStrokeWidth(2);
canvas.drawRect(new Rect(100, 100, 500, 500), paint);
效果图
SweepGradient
SweepGradient sweepGradient = new SweepGradient(200, 200, Color.RED, Color.GREEN);
paint.setShader(sweepGradient);
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(200, 200, 100, paint);
}
实例
关于shader,就讲这么多吧,我觉得大家应该也都明白了,那么就来做两个小玩意儿来耍耍吧。
- 闪烁文字
效果图
代码:
public class TextShaderView extends View {
private Paint paint;
int offset = 0;
LinearGradient linearGradient;
int[] colors = {Color.parseColor("#4C4D4B"), Color.parseColor("#D9D6DA"), Color.parseColor("#4C4D4B")};
float[] positions = {0.2f, 0.5f, 0.8f};
public TextShaderView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
paint = new Paint();
paint.setTextSize(170);
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setIntValues(-1000, 1000);
valueAnimator.setDuration(2000);
valueAnimator.setRepeatCount(-1);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
offset = (int) animation.getAnimatedValue();
linearGradient = new LinearGradient(offset, 300, 1000 + offset, 600, colors, positions, Shader.TileMode.CLAMP);
paint.setShader(linearGradient);
invalidate();
}
});
valueAnimator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.parseColor("#232423"));
paint.setAlpha(255);
canvas.drawText("演示文字", 200, 500, paint);
}
}
- 刮刮奖
效果图:
代码很短,直接上代码吧
public class ShaderView extends View {
private Paint paint;
private Path path;
private float mX;
private float mY;
private float offset = ViewConfiguration.get(getContext()).getScaledTouchSlop();
Bitmap bitmap;
public ShaderView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(30);
paint.setStrokeCap(Paint.Cap.ROUND);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.shader);
BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
paint.setShader(shader);
path = new Path();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(path, paint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
path.reset();
float x = event.getX();
float y = event.getY();
mX = x;
mY = y;
path.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
float x1 = event.getX();
float y1 = event.getY();
float preX = mX;
float preY = mY;
float dx = Math.abs(x1 - preX);
float dy = Math.abs(y1 - preY);
if (dx >= offset || dy >= offset) {
// 贝塞尔曲线的控制点为起点和终点的中点
float cX = (x1 + preX) / 2;
float cY = (y1 + preY) / 2;
path.quadTo(preX, preY, cX, cY);
mX = x1;
mY = y1;
}
}
invalidate();
return true;
}
}
看到最后这个例子,刮刮奖怎么实现,我想你们应该也就知道了。