Android着色器——Shader

简介

Shader :顾名思义,也就是着色器,通常也有人叫做渲染器。我们看下Android源码中是如何介绍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.
 */
复制代码

这是Android源码的介绍,Shader 是一个基类对象,在绘制时会返回一个水平跨越的颜色对象,主要功能是在绘制时通过setShader方法设置着色器的子类对象之后,任何对象(除了位图之外)都可从着色器中得到它的想要的颜色。

也就是说,Shader 只是一个基类,我们主要还是通过使用Shader的子类,来获取我们想要的颜色。

那么Shader 有那些子类呢?

其实Shader 总共有五个子类:

1)BimapShader:位图的图像渲染器

2)LinearGradient:线性渲染器

3)RadialGradient:环形渲染器,一般的水波纹效果,充电水波纹扩散效果、调色板都可以使用该渲染器实现。

4)SweepGradient:梯度渲染器(即扫描渲染),可以使用该渲染器实现,如:微信等雷达扫描效果、手机卫士垃圾扫描。

5)ComposeShader:组合渲染器

BimapShader

BitmapShader位图的图像渲染器,使用时,需要创建BitmapShader对象,其构造函数如下:

/**
     * Call this to create a new shader that will draw with a bitmap.
     *
     * @param bitmap The bitmap to use inside the shader
     * @param tileX The tiling mode for x to draw the bitmap in.
     * @param tileY The tiling mode for y to draw the bitmap in.
     */
    public BitmapShader(@NonNull Bitmap bitmap, @NonNull TileMode tileX, @NonNull TileMode tileY) {
        this(bitmap, tileX.nativeInt, tileY.nativeInt);
    }
复制代码

创建BitmapShader对象时,需要传入Bitmap,那么构造函数的后面两个函数干嘛用的呢?

tileX:X轴上绘制位图的tiling模式。

tileY:Y轴上绘制位图的tiling模式。

其中有三种tiling模式(TileMode):

1)TileMode.CLAMP:拉伸最后一个像素去铺满剩下的地方;

2)TileMode.MIRROR:通过镜像翻转铺满剩下的地方;

3)TileMode.REPEAT:重复图片平铺整个画面(如电脑设置壁纸)。

接下来我们看下分别设置这几种模式下的图像的效果。

TileMode.CLAMP

从上图中可以看出该模式下,确实是只拉伸图像的最后一个像素去铺满控件剩下的地方。

TileMode.MIRROR

这就是MIRROR模式下,通过镜像翻转铺满剩下的地方的效果图。

TileMode.REPEAT

上图就是REPEAT模式的效果,就是通过重复画面来铺满整个画面。

以上都是通过拉伸,或者重复画面来铺满真个View,效果都不太好,宽/高不一致,有没有办法解决宽/高一致,然后让bitmap铺满画面呢?

答案当然是有的,可以通过以下方法来解决:

1)设置像素矩阵,来调整大小,可以参考《Android BitmapShader 实战 实现圆形、圆角图片》这篇文章。

2)通过shapeDrawable

public class MyShaderView extends View {

    private Bitmap mBitmap;
    private Paint mPaint;
    private BitmapShader mBitmapShader;
    private int width;
    private int height;

    public MyShaderView(Context context) {
        super(context);
        mBitmap = ((BitmapDrawable)getResources().getDrawable(R.drawable.animationtarget)).getBitmap();
        mPaint = new Paint();
        width = mBitmap.getWidth();
        height = mBitmap.getHeight();

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.WHITE);//画布颜色
        mBitmapShader = new BitmapShader(mBitmap, TileMode.CLAMP, TileMode.CLAMP);
        ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
        shapeDrawable.getPaint().setShader(mBitmapShader);
        shapeDrawable.setBounds(0, 0, width, width);
        shapeDrawable.draw(canvas);
    }
}
复制代码
BitmapShader应用,制作放大镜
public class ZoomImageView extends View {

    private Bitmap bitmap;
    private ShapeDrawable drawable;
    //放大倍数
    private static final int FACTOR = 3;
    //放大镜的半径
    private static final int RADIUS = 100;
    private Matrix matrix = new Matrix();

    public ZoomImageView(Context context) {
        super(context);
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.animationtarget);
        Bitmap bmp = bitmap;
        //放大后的整个图片
        bmp = Bitmap.createScaledBitmap(bmp, bmp.getWidth() * FACTOR, bmp.getHeight() * FACTOR, true);
        //制作一个圆形的图片(放大的局部),盖在canvas上面
        BitmapShader shader = new BitmapShader(bmp, TileMode.CLAMP, TileMode.CLAMP);
        drawable = new ShapeDrawable(new OvalShape());
        drawable.getPaint().setShader(shader);
        //切出矩形区域---用于绘制圆(内切圆)
        drawable.setBounds(0, 0, RADIUS * 2, RADIUS * 2);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
        canvas.drawBitmap(bitmap, 0, 0, null);
        //画制作好的圆形图片
        drawable.draw(canvas);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();

        matrix.setTranslate(RADIUS - x * FACTOR, RADIUS - y * FACTOR);
        drawable.getPaint().getShader().setLocalMatrix(matrix);

        drawable.setBounds(x - RADIUS, y - RADIUS, x + RADIUS, y + RADIUS);

        invalidate();
        return true;
    }

}
复制代码

上图中使用放大镜放大了男孩的头部。

LinearGradient

LinearGradient也就是线性渲染器,也叫做线性渐变。 初始化时的构造方法:

    public LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorInt int colors[], @Nullable float positions[], @NonNull TileMode tile) 
复制代码

x0、y0:渲染的起始点;

x1、y1:渲染的结束点;

colors: 渲染的颜色,也就是中间依次要出现的几个颜色,它是一个颜色数组,数组长度必须大于等于2;

positions:数组大小跟colors数组一样大,中间依次摆放的几个颜色分别放置在那个位置上(参考比例从左往右);

tile:平铺方式,有CLAMP、REPEAT和MIRROR这三种。

使用:

LinearGradient linearGradient = new LinearGradient(0, 0, 400, 400, colors, null, TileMode.REPEAT);
mPaint.setShader(linearGradient);
canvas.drawRect(0, 0, 400, 400, mPaint);
复制代码

接下来我们主要分析下TileMode的三种模式。

CLAMP

该模式是表示:重复最后一种颜色直到该View结束的地方

LinearGradient linearGradient = new LinearGradient(0, 0, getWidth()/2, 0, colors, null, TileMode.CLAMP);
mPaint.setShader(linearGradient);
canvas.drawRect(0, 0, 800, 400, mPaint);
复制代码

上图便是CLAMP模式的效果图,从图中可以看出重复了最后一种颜色(黄色),直到该View结束的地方。

CLAMP

该模式表示着色器在水平或者垂直方向上对控件进行重复着色。

LinearGradient linearGradient = new LinearGradient(0, 0, getWidth()/2, 0, colors, null, TileMode.REPEAT);
mPaint.setShader(linearGradient);
canvas.drawRect(0, 0, 800, 400, mPaint);
复制代码

上图是REPEAT模式下的线性渲染器的效果图,可以看到在水平方向上是重复着色的。

MIRROR

该模式表示:在水平方向或者垂直方向上以镜像的方式进行渲染,这种渲染方式的一个特征就是具有翻转的效果。

LinearGradient linearGradient = new LinearGradient(0, 0, getWidth()/2, 0, colors, null, TileMode.MIRROR);
mPaint.setShader(linearGradient);
canvas.drawRect(0, 0, 800, 400, mPaint);
复制代码

效果图中的就是在水平方向上以镜像方式进行翻转的渲染效果。

LinearGradient 应用
public class LinearGradientTextView extends AppCompatTextView {
    private TextPaint paint;
    private LinearGradient linearGradient;
    private Matrix matrix;
    private float translateX;
    private float deltaX = 20;

    public LinearGradientTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        paint = getPaint();
        //GradientSize=两个文字的大小
        String text = getText().toString();
        float textWidth = paint.measureText(text);
        int GradientSize =(int) (3*textWidth/text.length());
        linearGradient = new LinearGradient(-GradientSize, 0, 0, 0, new int[]{0x22ffffff,0xffffffff,0x22ffffff}, new float[]{0,0.5f,1}, Shader.TileMode.CLAMP);//边缘融合
        paint.setShader(linearGradient);
        matrix = new Matrix();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
        float textWidth = getPaint().measureText(getText().toString());
        translateX += deltaX;
        if(translateX > textWidth + 1|| translateX < 1){
            deltaX = -deltaX;
        }
        matrix.setTranslate(translateX, 0);
        linearGradient.setLocalMatrix(matrix);

        postInvalidateDelayed(50);
    }
}
复制代码

xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#ff000000"
    tools:context="com.main.shader.ShaderActivity">

    <com.main.shader.LinearGradientTextView
        android:id="@+id/linearGradientTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="走的最慢的人,只要他不丧失目标,也比漫无目的徘徊的人走得快。"
        android:textColor="#ff000000"/>

</LinearLayout>

复制代码

RadialGradient

RadialGradient:环形渲染器,呈现一般的水波纹渲染效果。

使用:

RadialGradient radialGradient = new RadialGradient(300, 300, 100, colors, null, TileMode.REPEAT);
mPaint.setShader(radialGradient);
canvas.drawCircle(300, 300, 300, mPaint);
复制代码

RadialGradient的构造方法

RadialGradient(float centerX, float centerY, float radius,@NonNull @ColorInt int colors[], @Nullable float stops[],@NonNull TileMode tileMode)
复制代码

centerX、centerY:环形中心点坐标;

radius:环形半径;

colors:渲染的颜色;

stops:中间依次摆放的颜色分别放置的位置;

TileMode :平铺模式。

在RadialGradient 中同样提供了三种TileMode平铺模式:

1)CLAMP

2)REPEAT

3)MIRROR

这三种模式呈现的效果对应LinearGradient的TileMode的三种模式呈现效果。该渲染其的应用,可以参考《自定义控件三部曲之绘图篇(二十)——RadialGradient与水波纹按钮效果》这篇文章。

SweepGradient

扫描渲染器,使用:

SweepGradient sweepGradient = new SweepGradient(300, 300, colors, null);
mPaint.setShader(sweepGradient);
canvas.drawCircle(300, 300, 300, mPaint);
复制代码

我们看下SweepGradient的构造方法:

public SweepGradient(float cx, float cy,@NonNull @ColorInt int colors[], @Nullable float positions[])
复制代码

参数解析:

cx、cy:扫描中心点坐标;

colors:颜色梯度,分布在中心点周围,至少提供两种颜色;

positions:依次摆放的颜色分别放置的位置;

关于扫描渲染的应用,网上很多优秀的demo,比如:自定义控件之圆形颜色渐变进度条--SweepGradient

ComposeShader

组合渲染器ComposeShader,顾名思义,可以组合多种渲染器。如下代码所示:

ComposeShader composeShader = new ComposeShader(linearGradient, mBitmapShader, PorterDuff.Mode.SRC_OVER);
mPaint.setShader(composeShader);
canvas.drawRect(0, 0, 800, 1000, mPaint);
复制代码

构造方法的最后一个参数PorterDuff.Mode,也就是过度模式,总共有17种模式,关于这十七种模式,读者可以参考《各个击破搞明白PorterDuff.Mode》这篇文章,写得很详细,相信读者读完之后都会感到ComposeShader的强大之处。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值