Paint着色器

1,831 阅读5分钟

Paint着色器简介

Paint着色器会对Paint绘制的区域进行填充。
通过Paint.setShader()方法设置着色器,Paint着色器有如下几种:

  • BitmapShader
  • LinearGradient
  • SweepGradient
  • RadialGradient
  • ComposeShader

BitmapShader

BitmapShader使用Bitmap来进行填充,下面来看下它的构造方法:

public BitmapShader(@NonNull Bitmap bitmap, @NonNull TileMode tileX, @NonNull TileMode tileY)

bitmap为填充的位图
tileX为X轴方向位图填充方式
tileY为Y轴方向位图填充方式

TileMode表示以何种方式来填充,有如下3种类型:

  • CLAMP:当位图的大小小于Paint绘制区域时,以边界区域进行填充
  • MIRROR:当位图的大小小于Paint绘制区域时,以位图镜像方式进行填充
  • REPEAT:当位图的大小小于Paint绘制区域时,位图重复进行填充 aa.jpg

着色器可通过setLocalMatrix()来设置Matrix,通过Matrix来对位图进行平移、缩放、旋转等矩阵操作

圆形头像效果

Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
//创建BitmapShader
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
paint.setShader(bitmapShader);
//绘制矩形区域大小为图片大小
canvas.drawRect(0, 0, bitmap.getWidth(), bitmap.getHeight(), paint);

canvas.translate(0, bitmap.getHeight() + 20);
//绘制圆形
canvas.drawCircle(bitmap.getHeight() / 2f, bitmap.getHeight() / 2f, bitmap.getHeight() / 2f, paint);

canvas.translate(0, bitmap.getHeight() + 20);
Matrix matrix = new Matrix();
matrix.setTranslate(-(bitmap.getWidth() - bitmap.getHeight()) / 2f, 0);
//设置Matrix,通过Matrix来平移位图
bitmapShader.setLocalMatrix(matrix);
//绘制圆形
canvas.drawCircle(bitmap.getHeight() / 2f, bitmap.getHeight() / 2f, bitmap.getHeight() / 2f, paint);

canvas.translate(0, bitmap.getHeight() + 20);
matrix = new Matrix();
matrix.postTranslate(-(bitmap.getWidth() - bitmap.getHeight()) / 2f, 0);
matrix.preRotate(180, bitmap.getWidth() / 2f, bitmap.getHeight() / 2f);
//设置Matrix,通过Matrix来平移、旋转位图
bitmapShader.setLocalMatrix(matrix);
//绘制圆形
canvas.drawCircle(bitmap.getHeight() / 2f, bitmap.getHeight() / 2f, bitmap.getHeight() / 2f, paint);

1638782284(1).png

放大镜效果

public class ScaleView extends View {
    //原图
    private Bitmap mBitmap;
    //放大后的图片
    private Bitmap mScaleBitmap;
    private Paint mPaint;
    //圆形放大镜半径
    private final int RADIUS = 100;
    //图片放大比例
    private final int scale = 3;
    //原图触摸点
    private int mCenterX;
    private int mCenterY;
    private BitmapShader mBitmapShader;
    private Matrix mShaderMatrix;

    public ScaleView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        //原图
        mBitmap = ((BitmapDrawable) getResources().getDrawable(R.mipmap.a)).getBitmap();
        Matrix matrix = new Matrix();
        matrix.postScale(scale, scale);
        //原图放大后的图片
        mScaleBitmap = Bitmap.createBitmap(mBitmap, 0, 0, mBitmap.getWidth(), mBitmap.getHeight(), matrix, false);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        //使用放大后的图片来填充画笔
        mBitmapShader = new BitmapShader(mScaleBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        mShaderMatrix = new Matrix();
        mPaint.setShader(mBitmapShader);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制原图
        canvas.drawBitmap(mBitmap, 0, 0, null);
        //将原图触摸坐标装换为对应放大图片的坐标
        mShaderMatrix.setTranslate(mCenterX * (1 - scale), mCenterY * (1 - scale));
        mBitmapShader.setLocalMatrix(mShaderMatrix);
        //绘制圆形放大镜
        canvas.drawCircle(mCenterX, mCenterY, RADIUS, mPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mCenterX = (int) event.getX();
        mCenterY = (int) event.getY();
        invalidate();
        return true;
    }
}

1638782878(1).png

LinearGradient

LinearGradient使用线性渐变色来填充Paint绘制区域,下面来看看它的构造方法:

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用于设置颜色
positions表示颜色渐变的比例,其值为[0~1],并且数组长度必须和colors一样
tile表示填充模式

跑马灯效果

public class LinearGradientTextView extends View {
    private final int[] colors = new int[]{0x22ffffff, 0xffffffff, 0x22ffffff};
    private String text = "在 Android 开发者技能中,如果想进大厂,一般拥有较好的学历可能有优势一些。" +
            "但是如果你靠硬实力也是有机会的,例如死磕Framework。";
    private final Paint mPaint;
    private final List<String> mLineTexts;
    private final List<Float> mLineWidths;
    private final List<Float> mLineBaselines;
    private LinearGradient mLinearGradient;
    private Matrix mMatrix;
    private float mLinearGradientWidth;
    private int mLinearGradientLine;
    private float mLinearGradientStart;

    public LinearGradientTextView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mLineTexts = new ArrayList<>();
        mLineWidths = new ArrayList<>();
        mLineBaselines = new ArrayList<>();
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setTextSize(60);
        mPaint.setColor(Color.WHITE);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //清空数据
        mLineTexts.clear();
        mLineBaselines.clear();
        mLineWidths.clear();
        int start = 0;
        //获取FontMetrics
        Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
        //第一行文字的中心线
        float centerY = fontMetrics.bottom - fontMetrics.top;
        do {
            float[] lineWidth = new float[1];
            //计算指定宽度中可绘制文字数量,并获取该行宽
            int count = mPaint.breakText(text,
                    start,
                    text.length(),
                    true,
                    getWidth(), lineWidth);
            //保存当前行文本
            mLineTexts.add(text.substring(start, start + count));
            //保存当前行宽度
            mLineWidths.add(lineWidth[0]);
            //计算当前行基线坐标
            mLineBaselines.add(centerY - (fontMetrics.bottom + fontMetrics.top) / 2f);
            //下一行的中线坐标
            centerY += fontMetrics.bottom - fontMetrics.top;
            //下一行文本起始索引位置
            start += count;
        } while (start < text.length());

        //5个文字的宽度
        mLinearGradientWidth = mPaint.measureText(text) / text.length() * 5;
        mLinearGradientStart = -mLinearGradientWidth;
        //颜色渐变长度为5个文字的宽度
        mLinearGradient = new LinearGradient(0,
                0, mLinearGradientWidth,
                0, colors, new float[]{0, 0.5f, 1}, Shader.TileMode.CLAMP);
        mMatrix = new Matrix();
        mLinearGradient.setLocalMatrix(mMatrix);
        //设置Shader
        mPaint.setShader(mLinearGradient);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制每一行
        for (int i = 0; i < mLineTexts.size(); i++) {
            //如果当前行使用颜色渐变
            if (i == mLinearGradientLine) {
                //平移LinearGradient
                mMatrix.setTranslate(mLinearGradientStart, 0);

                float DELTA = 20;
                mLinearGradientStart += DELTA;
                if (mLinearGradientStart > mLineWidths.get(i)) {
                    mLinearGradientStart = -mLinearGradientWidth;
                    mLinearGradientLine += 1;
                    if (mLinearGradientLine == mLineTexts.size())
                        mLinearGradientLine = 0;
                }
            } else {
                //平移LinearGradient
                mMatrix.setTranslate(-mLinearGradientWidth, 0);
            }
            mLinearGradient.setLocalMatrix(mMatrix);
            //绘制文本
            canvas.drawText(mLineTexts.get(i), 0, mLineBaselines.get(i), mPaint);
        }
        postInvalidateDelayed(16);
    }
}

1638783604(1).png

SweepGradient

SweepGradient使用梯度渐变色来填充Paint绘制区域,下面来看看其构造方法:

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

cx、cy表示渐变的中心点
colors表示渐变色
positions表示颜色渐变比例,范围为[0~1],可以为null,如果不会null则数组长度需和colors长度一样

雷达效果

//用于改变旋转角度
Matrix matrix = new Matrix();

private void drawSweepGradient(Canvas canvas) {
    float centerX = 200;
    float centerY = 200;
    float radius = 200;
    int[] colors = new int[]{
            0x0A0000ff,
            0x1A0000ff,
            0x2A0000ff,
            0x3A0000ff,
            0x4A0000ff,
            0x5A0000ff};
    //创建SweepGradient
    SweepGradient sweepGradient = new SweepGradient(centerX, centerY, colors, null);
    //每次顺时针旋转3°
    matrix.postRotate(3, centerX, centerY);
    sweepGradient.setLocalMatrix(matrix);

    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    //设置Shader
    paint.setShader(sweepGradient);
    //使用SweepGradient来填充绘制的圆形
    canvas.drawCircle(centerX, centerY, radius, paint);
    //每隔20ms绘制一次
    postInvalidateDelayed(20);
}

1638842299(1).png

RadialGradient

RadialGradient使用环形渲染填充Paint绘制区域,下面来看看其构造方法:

public RadialGradient(float centerX, float centerY, float radius,
        @NonNull @ColorInt int[] colors, @Nullable float[] stops,
        @NonNull TileMode tileMode)

centerX、centerY表示环形中点 radius表示环形半径大小 colors表示环形渐变色 stops表示渐变色颜色比例 tileMode为填充模式

水波纹效果

点击按钮时会有一个水波纹效果,该效果可使用RadialGradient来实现,通过不断放大RadialGradient即可实现

private void drawRadialGradient(Canvas canvas) {
    //按钮宽
    int width = 600;
    //按钮长
    int height = 200;
    //模拟点击位置
    float centerX = width / 4f;
    float centerY = height / 4f;
    //水波纹最小半径
    float minRadius = height / 2f;
    //水波纹最大半径
    float maxRadius = width * 1.0f;
    //水波纹渐变色
    int[] colors = new int[]{
            0x5Aff0000,
            0x00999999
    };
    //创建环形着色器
    if (radialGradient == null)
        radialGradient = new RadialGradient(centerX, centerY,
                minRadius,
                colors,
                null, Shader.TileMode.CLAMP);
    //使用Matrix来缩放环形着色器
    matrix.setScale(scale, scale, centerX, centerY);
    //修改环形着色器缩放比例
    scale += 0.2f;
    boolean stop = false;
    if (scale > maxRadius / minRadius) {
        scale = 1;
        stop = true;
    }
    radialGradient.setLocalMatrix(matrix);

    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setColor(Color.parseColor("#666666"));
    paint.setShader(null);
    //绘制矩形用于模拟按钮
    canvas.drawRect(0, 0, 600, 200, paint);

    //使用环形着色器绘制矩形
    paint.setShader(radialGradient);
    canvas.drawRect(0, 0, 600, 200, paint);
    //不断绘制,形成动画效果
    postInvalidateDelayed(stop ? 1000 : 20);
}

1638847026(1).png

ComposeShader

ComposeShader用于组合两个着色器,下面来看其构造方法:

public ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB,
        @NonNull PorterDuff.Mode mode)

mode表示组合shaderA和shaderB的方式,PoterDuff将在下篇文章介绍。
其中shaderA表示PorterDuff中的“dst”,shaderB表示“src”

倒影效果

倒影效果:将一张图片向下进行镜像翻转,然后使用线性渐变色作用于倒影上。使用BitmapShader实现向下镜像翻转,LinearGradient实现线性渐变色。

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.xyjy2);
//以镜像形式进行位图填充
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);
//由于镜像图片位于原图下面,所以渐变色顶部为原图高度大小,底部为原图高度的2倍
LinearGradient linearGradient = new LinearGradient(0, bitmap.getHeight(), 0, 2 * bitmap.getHeight(),
        new int[]{0x0Affffff, 0xBAffffff, 0xffffffff}, new float[]{0, 0.5f, 1}, Shader.TileMode.CLAMP);
//组合BitmapShader和LinearGradient
ComposeShader composeShader = new ComposeShader(bitmapShader, linearGradient, PorterDuff.Mode.SRC_ATOP);
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setShader(composeShader);
//绘制矩形,矩形宽为图片宽,高为图片高的2倍
canvas.drawRect(0, 0,
        bitmap.getWidth(), bitmap.getHeight() * 2,
        paint);

1638847696(1).png