Android 自定义View学习(六)——Paint 关于Shader的学习

上篇学了PorterDuffXfermode,本篇记录学习Shader

学习资料:

本人很菜,哪里有错误,感谢指出


1.Shader 着色器

着色器就是用来上色的,可以用来实现一系列的渐变、渲染效果,有5个子类

  1. BitmapShader 位图Shader
  2. LinerGradient 线性Shader
  3. RadialGradient 光束Shader
  4. SweepGradient 梯度Shader
  5. ComposeShader 混合Shader

BitmapShader是唯一个可以用来给一个图片着色,其他四个就是渐变、渲染效果


2.BitmapShader 位图着色器

Shader used to draw a bitmap as a texture. The bitmap can be repeated or mirrored by setting the tiling mode.

将一个Bitmap作为纹理进行绘制,通过设置模式可以进行翻转或者镜像操作

只有一个构造方法

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

构造方法中除了一个Bitmap外,还需要两个TileMode枚举类型的参数,一个代表在x轴的模式,一个在y轴的模式


2.1 TileMode 瓷砖模式

TileModeShader中的一个枚举,有三个值

  1. CLAMP 拉伸,图片的最后的一个像素,不断重复
  2. REPEAT 重复,横向、纵向不断重复
  3. MIRROR 镜像,横向不断翻转重复,纵向不断翻转重复

这里最常使用的就是CLAMP拉伸模式,虽然它会拉伸最后一个像素,但是只要将图像设置为一定的大小,就可以避免这种拉伸

利用CLMP得到一个圆形图片


200dp
private void init() {
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    final Bitmap mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    final BitmapShader shader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
    mPaint.setShader(shader);
}

/**
 * 利用 clmp得到圆形图片
 */
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    float x = getWidth() / 2;
    float y = getHeight() / 2;
    float radius = Math.min(getWidth(), getHeight()) / 2;
    canvas.drawCircle(x, y, radius,mPaint);
}

布局文件中,大小设置的为200dp,这个时候可以正常显示,但设置为300dp

300dp

这时图片的下面部分已经出现了问题,明显是最后一个元素被拉伸过多,使用这种方式得到圆形图片,拉伸后的图片要大于控件的大小,这样最后一个元素既然被拉伸变形,也看不到


2.2 BitmapShader下三种模式的效果

  • REPEAT 重复

简单修改代码,将CLAMP变为REPEAT,图片资源变为R.mipmap.ic_launcher,画的图形由圆形变为矩形,布局中的宽度改为match_parent

 private void init() {
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    final Bitmap mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
    final BitmapShader shader = new BitmapShader(mBitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
    mPaint.setShader(shader);
}

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

在整个View中,就会重复出现资源图片


  • MIRROR 镜像
mirror镜像

上下对称,出现镜像

上面的情况两个TileMode都是同一个值


  • x轴CLAMP , y轴 MIRROR
final BitmapShader shader = new BitmapShader(mBitmap,Shader.TileMode.CLAMP,  Shader.TileMode.MIRROR);
x轴CLAMP , y轴 MIRROR

y轴是镜像,x轴最后的像素拉伸


  • x轴 MIRROR, y轴 CLMP
final BitmapShader shader = new BitmapShader(mBitmap,  Shader.TileMode.MIRROR,Shader.TileMode.CLAMP);
x轴 MIRROR, y轴 CLAMP

x轴镜像, y轴拉伸

BitmapShader总是先应用Y轴上的模式后,再应用X轴上的模式

x轴 MIRROR, y轴 CLMP,这种情况下,y轴先进行最后一个像素的拉伸后,再进行x轴的镜像,就出现了上面图片的效果


3. LinearGradient 线性渐变

构造方法,有两个

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

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

可以画出线性渐变


3.1第一种构造方法简单使用:

LinearGradient shader = new LinearGradient(0, 0, 600, 600, Color.RED, Color.YELLOW, Shader.TileMode.REPEAT);
mPaint.setShader(shader);
  • x0 绘制x轴起始点
  • y0 绘制y轴起始点
  • x1 绘制x轴结束点
  • y1 绘制y轴结束点
  • color0 起始颜色
  • color1 结束颜色
  • tile 瓷砖模式
LinearGradient第1种构造方法

效果很容易理解,在控件中从0,0左上角点到600,600右下角两种颜色渐变


TileMode的效果和BitmapShader效果是一样的

但此时Shader.TileMode.REPEAT貌似没啥作用,这是由于控件的大小为600 * 600,渐变的结束点也是600 * 600,将渐变的结束点变成一半300 * 300

代码简单改动

final LinearGradient shader = new LinearGradient(0, 0, 300, 300, Color.MAGENTA, Color.CYAN, Shader.TileMode.REPEAT);
渐变结束点为 (300,300)

这时,就出现了一个重复的渐变效果


3.2第二种构造方法

两个构造方法的区别在于,第4参数,是一个int[],第5个参数为一个float[],使用这个构造方法可以设置多种颜色的渐变

final int [] colors =  new int [] {Color.MAGENTA,Color.CYAN,Color.RED};
final float [] positions = new float[]{0f,0.50f,1f};
final LinearGradient shader = new LinearGradient(0, 0, 600, 600,colors ,positions ,Shader.TileMode.REPEAT);
  • colors 颜色int值数组
  • postions 数组中的值有效范围是0f~1f,渐变结束所在区域的比例,1f的结束位置,与x1,y1有关
LinearGradient第2种构造方法

三种颜色,在起始点(0,0),二分之一点(300,300),结束点(600,600)的渐变效果


3.3 图片倒影效果

思路:

  1. 绘制原图,考虑绘制坐标,图片缩放
  2. 绘制倒影,利用Matrix
  3. 绘制渐变层,利用PortDuffXfermodeLinearGradient
图片倒影

代码:

public class ReflectView extends View {
    private Paint mPaint;
    private Bitmap dstBitmap, srcBitmap;
    private PorterDuffXfermode xfermode;
    private int x, y;

    public ReflectView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        //原图Bitmap
        dstBitmap = decodeBitmapFormRes(getResources(), R.drawable.wa,540, 960);
        //垂直翻转
        Matrix matrix = new Matrix();
        matrix.setScale(1f, -1f);
        //倒影Bitmap
        srcBitmap = Bitmap.createBitmap(dstBitmap, 0, 0, dstBitmap.getWidth(), dstBitmap.getHeight(), matrix, true);
        //初始化画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
         //屏幕宽度
        int screenW = getResources().getDisplayMetrics().widthPixels;
        //起始点
        x = screenW / 2 - dstBitmap.getWidth() / 2;
        y = 0;
        //设置渐变矩形
        mPaint.setShader(new LinearGradient(x, dstBitmap.getHeight(), x, dstBitmap.getHeight() + dstBitmap.getHeight() / 2, 0xDD000000, Color.TRANSPARENT, Shader.TileMode.CLAMP));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制背景
        canvas.drawColor(Color.BLACK);
        //绘制原图
        canvas.drawBitmap(dstBitmap, x, y, null);
        //绘制倒影图片
        canvas.drawBitmap(srcBitmap, x, dstBitmap.getHeight(), null);
        mPaint.setXfermode(xfermode);
        //绘制渐变层
        canvas.drawRect(x, dstBitmap.getHeight(), x + dstBitmap.getWidth(), dstBitmap.getHeight() * 2, mPaint);
        mPaint.setXfermode(null);
    }

    /**
     * 测量
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(300, 300);
        } else if (wSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(300, hSpecSize);
        } else if (hSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(wSpecSize, 300);
        }
    }

    /**
     * 图片的缩放
     */
    private Bitmap decodeBitmapFormRes(Resources resources, int resId, int targetWidth, int targetHeight) {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        options.inJustDecodeBounds = false;
        BitmapFactory.decodeResource(resources, resId, options);
        int inSample = calculateInSample(options, targetWidth, targetHeight);
        options.inSampleSize = inSample;
        return BitmapFactory.decodeResource(resources, resId, options);
    }

    private int calculateInSample(BitmapFactory.Options options, int targetWidth, int targetHeight) {
        if (targetWidth <= 0 || targetHeight <= 0) {
            return 1;
        }
        int inSample = 1;
        final int rawWidth = options.outWidth;
        final int rawHeight = options.outHeight;
        if (rawWidth > targetWidth || rawHeight > targetHeight) {
            final int halfWidth = rawWidth / 2;
            final int halfHeight = rawHeight / 2;
            while ((halfWidth / inSample >= targetWidth) && (halfHeight / inSample >= targetHeight)) {
                inSample *= 2;
            }
        }
        return inSample;
    }
}

关于Matrix下下篇可能会学习 : )


4.RadialGradient 光束渐变

同样有两个构造方法

RadialGradient(float centerX, float centerY, float radius,  int centerColor, int edgeColor, @NonNull TileMode tileMode)

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

用法同LinearGradient类似

第一种构造方法简单使用:

final RadialGradient shader = new RadialGradient(300f,300f,300,Color.RED,Color.YELLOW, Shader.TileMode.CLAMP);
RadialGradient第1种构造方法
  • centerX 渐变中心点的X轴坐标
  • centerY 渐变中心点的Y轴坐标
  • radius 渐变区域的半径

控件大小为600 * 600(300f,300f)为控件的中心,半径为300


第二种构造方法简单使用:

final int[] colors = new int[]{Color.YELLOW,Color.BLUE ,Color.RED};
final float[] positions = new float[]{0f, 0.5f ,1f};
final RadialGradient shader = new RadialGradient(300f,300f,300,colors,positions, Shader.TileMode.CLAMP);
RadialGradient第2种构造方法

colors中的元素个数要和positions中的元素个数相等


5. SweepGradient 梯度渐变

也有两个构造方法

SweepGradient(float cx, float cy, int color0, int color1) 

SweepGradient(float cx, float cy, int colors[], float positions[])

cx,cy是旋转点的x,y轴坐标,渐变过程总是顺时针方向旋转

第一种构造方法简单使用:

final SweepGradient shader = new SweepGradient(300f,300f,Color.RED,Color.YELLOW);
        mPaint.setShader(shader);
SweepGradient第1种构造方法

控件的大小为600 * 600300f * 300f就是整个控件的中心,可以试试其他的值


第二种构造方法简单使用:


SweepGradient第2种构造方法
final int[] colors = new int[]{Color.MAGENTA, Color.CYAN,Color.YELLOW,Color.BLUE ,Color.RED};
final float[] positions = new float[]{0f, 0.25f,0.5f ,0.75f,1f};
final SweepGradient shader = new SweepGradient(300f, 300f, colors, positions);

这里的positons中的值是固定的,o.25f的位置就是画的竖线的位置,其他的值类推


6. ComposeShader 混合渐变

也是有两个构造方法,但和之前的形式不再类似

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

ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode)

构造方法中,前两个参数相同,都是需要一个着色器,差别在于第三个参数。第一个构造方法需要一个PorterDuff.Mode,而第二个构造构造方法需要PorterDuffXfermode

前面的三种的渐变,都是一种单一的渐变,ComposeShader可以把前面两种渐变混合进一种渐变效果


简单使用:

private void init() {
    setLayerType(View.LAYER_TYPE_SOFTWARE, null);//关闭硬件加速
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    final LinearGradient linearGradient = new LinearGradient(0, 0, 600, 600,Color.GREEN ,Color.BLUE ,Shader.TileMode.CLAMP);
    final RadialGradient radialGradient = new RadialGradient(300f,300f,300,Color.RED,Color.YELLOW, Shader.TileMode.CLAMP);
       
    final ComposeShader shader = new ComposeShader(linearGradient, radialGradient, PorterDuff.Mode.SCREEN);
    mPaint.setShader(shader);
}
ComposeShader简单使用

在系统5.1.1的坚果手机上,必须要把硬件加速关闭,ComposeShader混合渐变效果才会出现,否则屏幕一片空白,手边暂时没有了别的手机,不知道会不会也是这样

第3个参数,new PorterDuffXfermode(PorterDuff.Mode.SCREEN)PorterDuff.Mode.SCREEN这两种写法有啥区别,暂时也没看出来


7.最后

Paint的基础知识学习先到这里了。下篇记录学习Canvas中的方法及属性

一直写到现在,脑袋有点混了,休息

突然感觉写的基础知识都没记住,回头再看看

周末愉快,共勉 : )

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 系列文章之 Android中自定义View(一)系列文章之 Android中自定义View(二)系列文章之 And...
    YoungerDev阅读 2,159评论 0 4
  • 概述 Shader 是Android中非常重要的一个类 一般称之为着色器,其作用是用来给图像着色,我们一般在自定义...
    明朗__阅读 8,598评论 0 38
  • 你不懂我的悲伤 繁华淡尽的孤芳 因抓不住而彷徨 你不懂我的悲伤 岁月沉淀的灵魂 因已过时而迷茫 你不懂我的悲伤……
    鉬鈺阅读 302评论 7 5
  • 爱情,可以是一朵滋生在心中的花,圣洁而美丽 又或者是一汪清泉,贻人而甘甜 但如果你把爱情当饭吃, 最后的你,只会饿...
    蓝小小小鱼阅读 312评论 0 0
  • 看到棋盘,立马想起这首《约客》的诗,“黄梅时节家家雨,青草池塘处处蛙,有约不来过夜半,闲敲棋子落灯花”。眼前仿佛有...
    渔樵w阅读 404评论 0 0