Android自定义控件入门到精通--Shader

《Android自定义控件入门到精通》文章索引 ☞ https://blog.youkuaiyun.com/Jhone_csdn/article/details/118146683

《Android自定义控件入门到精通》所有源码 ☞ https://gitee.com/zengjiangwen/Code

Shader

着色器

在这里插入图片描述

BitmapShader

BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY)

  • bitmap :用来着色(填充)的图像
  • tileX : x轴方向上的填充策略
  • tileY : y轴方向上的填充策略
  • TileMode
    • TileMode.CLAMP : 以边缘颜色填充
    • TileMode.REPEAT : 重复图像填充
    • TileMode.MIRROR : 重复镜像图像填充

TileMode.CLAMP

首先我准备了一张图片

在这里插入图片描述

private Paint mPaint;

//初始化画笔Paint
private void init() {
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.panda);
    mPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
}

@Override
protected void onDraw(Canvas canvas) {
    canvas.drawRect(new RectF(0,0,getWidth(),getHeight()),mPaint);
}

在这里插入图片描述

我们看到,右边的红色和下边的绿色是由图片的边缘色决定的,那右下角的咖啡色是怎么来的呢?我们放大图像看看

在这里插入图片描述

可以清楚的看到,右下角有一个咖啡色像素点,所以右下角填充什么颜色,就看右下角顶点是什么颜色

TileMode.REPEAT

代码是一样的,只是吧模式换成TileMode.REPEAT

在这里插入图片描述

TileMode.MIRROR

镜像填充

在这里插入图片描述

这个shader真是神奇,我们只是在onDraw中画了一个矩形,就能实现这么震撼的效果

canvas.drawRect(new RectF(0,0,getWidth(),getHeight()),mPaint);

我们扩展一下

    private Paint mPaint;
    private int cx,cy;
    private void init() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.panda);
        mPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR));
        cx=bitmap.getWidth()/2;
        cy=bitmap.getHeight()/2;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawCircle(cx,cy,cx,mPaint);
        canvas.drawRoundRect(cx*2,0,cx*4,cy*2,10,10,mPaint);
    }

在这里插入图片描述

我们常常用的圆形和圆角头像,就可以这样实现,当然,各种形状的头像相信也不在话下了。

原理:用图片做一个底,然后用蒙版盖住,然后用Paint绘制出可见的区域:

在这里插入图片描述

基于这个原理,我们可以搞个有意思的效果(望远镜效果)

在这里插入图片描述

附上代码:

public class TestView extends View {

    public TestView(Context context) {
        this(context, null);
    }

    public TestView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    private Paint mPaint;
    private Bitmap mBitmap;
    private boolean hasSetShader;

    //初始化画笔Paint
    private void init() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        //加载bitmap图片
        mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.girl);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (!hasSetShader) {
            hasSetShader = true;
            //用canvas.drawBitmap的方式,把上面加载的bitmap缩放到控件尺寸大小
            Bitmap bitmapShader = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
            Canvas canvasBg = new Canvas(bitmapShader);
            canvasBg.drawBitmap(mBitmap, null, new RectF(0, 0, getWidth(), getHeight()), mPaint);
            //设置Shader,模式随意,因为图片已经缩放到控件大小,没有多余的控件应用重复策略
            mPaint.setShader(new BitmapShader(bitmapShader, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
        }
        if (mX > 0 && mY > 0) {
            canvas.drawCircle(mX, mY, 100, mPaint);
            canvas.drawCircle(mX + 150, mY, 100, mPaint);
        }
    }

    private float mX, mY;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mX = event.getX();
                mY = event.getY();
                return true;
            case MotionEvent.ACTION_MOVE:
                mX = event.getX();
                mY = event.getY();
                break;
            case MotionEvent.ACTION_UP:
                mX = 0;
                mY = 0;
                break;
        }
        invalidate();
        return super.onTouchEvent(event);
    }
}

LinearGradient

线性渐变

LinearGradient(x0,y0,x1,y1,int color0, int color1, TileMode tile)

@Override
protected void onDraw(Canvas canvas) {
    //当Shader的直线距离(x0,y0)-->(x1,y1)等于drawRect的宽度,正常渐变
    LinearGradient linearGradient = new LinearGradient(0, 0, getWidth(), 0, 0xFFFF0000, 0xFF00FF00, Shader.TileMode.CLAMP);
    mPaint.setShader(linearGradient);
    canvas.drawRect(0, 0, getWidth(), 100, mPaint);

    canvas.translate(0, 150);

    //当Shader的直线距离(x0,y0)-->(x1,y1)大于drawRect的宽度,只显示正常渐变的一部分
    LinearGradient linearGradient1 = new LinearGradient(0, 0, getWidth(), 0, 0xFFFF0000, 0xFF00FF00, Shader.TileMode.CLAMP);
    mPaint.setShader(linearGradient1);
    canvas.drawRect(0, 0, 200, 100, mPaint);

    canvas.translate(0, 150);

    //当Shader的直线距离(x0,y0)-->(x1,y1)小于drawRect的宽度,TileMode.CLAMP
    LinearGradient linearGradient2 = new LinearGradient(0, 0, 200, 0, 0xFFFF0000, 0xFF00FF00, Shader.TileMode.CLAMP);
    mPaint.setShader(linearGradient2);
    canvas.drawRect(0, 0, getWidth(), 100, mPaint);

    canvas.translate(0, 150);

    //当Shader的直线距离(x0,y0)-->(x1,y1)小于drawRect的宽度,TileMode.REPEAT
    LinearGradient linearGradient3 = new LinearGradient(0, 0, 200, 0, 0xFFFF0000, 0xFF00FF00, Shader.TileMode.REPEAT);
    mPaint.setShader(linearGradient3);
    canvas.drawRect(0, 0, getWidth(), 100, mPaint);

    canvas.translate(0, 150);

    //当Shader的直线距离(x0,y0)-->(x1,y1)小于drawRect的宽度,TileMode.MIRROR
    LinearGradient linearGradient4 = new LinearGradient(0, 0, 200, 0, 0xFFFF0000, 0xFF00FF00, Shader.TileMode.MIRROR);
    mPaint.setShader(linearGradient4);
    canvas.drawRect(0, 0, getWidth(), 100, mPaint);
}

在这里插入图片描述

这个没什么难度,对着代码看就能明白

LinearGradient(x0,y0,x1,y1,int[] colors, float[] positions, TileMode tile)

  • colors:多个颜色渐变
  • positions:颜色占比 [0,1]
@Override
protected void onDraw(Canvas canvas) {
    int[] colors = new int[]{0xffff0000, 0xff00ff00, 0xff0000ff, 0xff00ffff};
    float[] positions = new float[]{0, 0.2f, 0.5f, 1f};

    //当Shader的直线距离(x0,y0)-->(x1,y1)等于drawRect的宽度,正常渐变
    LinearGradient linearGradient = new LinearGradient(0, 0, getWidth(), 0, colors, positions, Shader.TileMode.CLAMP);
    mPaint.setShader(linearGradient);
    canvas.drawRect(0, 0, getWidth(), 100, mPaint);

    canvas.translate(0, 150);

    //当Shader的直线距离(x0,y0)-->(x1,y1)大于drawRect的宽度,只显示正常渐变的一部分
    LinearGradient linearGradient1 = new LinearGradient(0, 0, getWidth(), 0, colors, positions, Shader.TileMode.CLAMP);
    mPaint.setShader(linearGradient1);
    canvas.drawRect(0, 0, 200, 100, mPaint);

    canvas.translate(0, 150);

    //当Shader的直线距离(x0,y0)-->(x1,y1)小于drawRect的宽度,TileMode.CLAMP
    LinearGradient linearGradient2 = new LinearGradient(0, 0, 200, 0, colors, positions, Shader.TileMode.CLAMP);
    mPaint.setShader(linearGradient2);
    canvas.drawRect(0, 0, getWidth(), 100, mPaint);

    canvas.translate(0, 150);

    //当Shader的直线距离(x0,y0)-->(x1,y1)小于drawRect的宽度,TileMode.REPEAT
    LinearGradient linearGradient3 = new LinearGradient(0, 0, 200, 0, colors, positions, Shader.TileMode.REPEAT);
    mPaint.setShader(linearGradient3);
    canvas.drawRect(0, 0, getWidth(), 100, mPaint);

    canvas.translate(0, 150);

    //当Shader的直线距离(x0,y0)-->(x1,y1)小于drawRect的宽度,TileMode.MIRROR
    LinearGradient linearGradient4 = new LinearGradient(0, 0, 200, 0, colors, positions, Shader.TileMode.MIRROR);
    mPaint.setShader(linearGradient4);
    canvas.drawRect(0, 0, getWidth(), 100, mPaint);
}

在这里插入图片描述

对于positions,我画个图,大家就能理解了

在这里插入图片描述

RadialGradient

径向渐变

RadialGradient (x0,y0,x1,y1,int color0, int color1, TileMode tile) 两个颜色渐变

RadialGradient (x0,y0,x1,y1,int[] colors, float[] stops, TileMode tile) 多个颜色渐变

@Override
protected void onDraw(Canvas canvas) {
    int[] colors = new int[]{0xffff0000, 0xff00ff00, 0xff0000ff, 0xff00ffff};
    float[] stops = new float[]{0, 0.2f, 0.5f, 1f};
    RadialGradient radialGradient = new RadialGradient(200, 200, 100, 0xffff0000, 0xff00ff00, Shader.TileMode.CLAMP);
    mPaint.setShader(radialGradient);
    canvas.drawCircle(200, 200, 100, mPaint);

    canvas.translate(250, 0);

    RadialGradient radialGradient1 = new RadialGradient(200, 200, 100, colors, stops, Shader.TileMode.CLAMP);
    mPaint.setShader(radialGradient1);
    canvas.drawCircle(200, 200, 100, mPaint);
}

在这里插入图片描述

SweepGradient

扫描渐变(强行命名)

@Override
protected void onDraw(Canvas canvas) {
    int[] colors = new int[]{0xffff0000, 0xff00ff00, 0xff0000ff, 0xff00ffff};
    float[] stops = new float[]{0, 0.2f, 0.5f, 1f};

    SweepGradient sweepGradient = new SweepGradient(200, 200, 0xffff0000, 0xff00ff00);
    mPaint.setShader(sweepGradient);
    canvas.drawCircle(200, 200, 100, mPaint);

    canvas.translate(250, 0);

    SweepGradient sweepGradient1 = new SweepGradient(200, 200, colors, stops);
    mPaint.setShader(sweepGradient1);
    canvas.drawCircle(200, 200, 100, mPaint);
}

在这里插入图片描述

ComposeShader

ComposeShader(Shader shaderA, Shader shaderB,PorterDuff.Mode mode)

从构造方法可以看出就是传入两个着色器shaderA和shaderB,通过mode混合模式构造新的着色器

shaderA:我们称之为目标dst着色器

shaderB:我们称之为源src着色器

注意,shaderA和shaderB不能为同类型Shader,比如不能两个都是BitmapShader或者两个都是LinearGradient,否则没有效果

@Override
protected void onDraw(Canvas canvas) {
    //创建BitmapA,并填充0xffff0000红色
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.panda);
    //创建BitmapShaderA
    BitmapShader bitmapShaderA = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
    //创建LinearGradientA
    LinearGradient linearGradientA = new LinearGradient(0, 0, getWidth(), 0, 0xffff0000, 0xff00ff00, Shader.TileMode.CLAMP);
    //组合两个Shader
    ComposeShader composeShader = new ComposeShader(bitmapShaderA, linearGradientA, PorterDuff.Mode.ADD);
    //设置画笔着色器(填充器)
    mPaint.setShader(composeShader);
    canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
}

在这里插入图片描述

可以看到两中着色器的效果都有了

其本质是颜色的混合效果,我们不防用一个纯色的图片BitmapShader和一个渐变LinearGradient来做下试验

我们把src和dst shader画出来,并让他们有一部分重叠,并在重叠的区域用ComposeShader重新画一遍,得效果:

在这里插入图片描述

可以看到,中间黄色区域为两种shader混合后(ComposeShader)的效果

我们可以借住Ps来观察一下

在这里插入图片描述

可以发现PorterDuff.Mode.ADD的效果跟Ps中的 变亮、滤色、线性减淡效果差不多

PorterDuff.Mode

我在Xfermode中有具体讲解

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一鱼浅游

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值