Android之Shader完全理解指南

  • Post author:
  • Post category:其他

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,就讲这么多吧,我觉得大家应该也都明白了,那么就来做两个小玩意儿来耍耍吧。

  1. 闪烁文字
    效果图
    在这里插入图片描述
    代码:
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);

    }
}
  1. 刮刮奖
    效果图:

    代码很短,直接上代码吧
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;
    }
}

看到最后这个例子,刮刮奖怎么实现,我想你们应该也就知道了。


版权声明:本文为IT_XF原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。