本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

前言

Android中绘图离不开的就是Canvas了,Canvas是一个庞大的知识体系,有java层的,也有jni层深入到Framework。Canvas有许多的知识内容,构建了一个武器库一般,所谓十八般武艺是也,PaintCanvas的一个重要的合作伙伴,但今天要讲的不是Canvas也不是Paint,而是与Paint相关的知识点Shader.

什么是Shader?

Shader在英语辞典中被解释为着色器。查阅维基百科,有以下结论:

In the field of computer graphics, a shader is a computer program that is used to do shading: the production of appropriate levels of color within an image, or, in the modern era, also to produce special effects or do video post-processing. A definition in layperson’s terms might be given as “a program that tells a computer how to draw something in a specific and unique way.

在计算机图形领域,一个Shader是指一段用来着色的计算机程序,通常用来生成一张图片中适当等级的颜色值,或者是生成特殊的视觉效果,或者是对视频画面进行处理。对于非专业人士的角度来看,它可以被描述为–“一种告诉计算机怎么样通过某种特殊手段绘制一些图像的程序”。

看起来还是比较抽象难懂,但是我觉得正确理解它的定义是应该的,这能让我们真正写出非常高效的代码。

Android中也有Shader的概念,对照上面的定义,它应该也是将图形画面产生某种特殊效果的一类东西。具体是不是这样的呢?我可以先告诉你答案–是的。
为了提高大家对Shader的兴趣,先让大家看看通过Shader得到的一些效果图片。

是不是挺有趣啊?如果你对这些感兴趣,请跟随我的节奏,看下面内容。

Android中Shader相关知识点

看API终于不要翻墙了,其实我也一直没有翻墙,想看API的时候,直接去www.androidxref.com查看源码去了。那么现在可以直接上官网中文页面,查看了。Android中Shader的API地址为Shader

Android中对Shader是这样解释的

Shader是一种基类对象,它在图形绘制过程中返回一段段颜色值,通过调用Paint.setShader()方法,可以将它的子类安装进画笔,这样Paint对象在绘制过程中所获取的颜色就是来自Shader对象。

上面提到了Shader的子类,Shader有5个子类 BitmapShader, ComposeShader, LinearGradient, RadialGradient, SweepGradient。 本文的目的也是分别讲它的各个子类。

图片渲染器 BitmapShader

BitmapShader将一张图片当作纹理(在OpenGL中,纹理就是贴图的意思,可以理解为一个没有颜色的正文形被贴上了一张图片,这样视觉效果就是一张正方形的图片)来绘制。而这张图片可以通过设置BitmapShader的tiling mode来达到镜面和重复的效果。

BitmapShader (Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)

上面是BitmapShader的构造方法。

bitmap是指纹理图片,
tileX是指在X方向轴的tiling mode
tileY是指在Y方向轴的tiling mode

很多人可能有疑问,这个TileMode是什么?

神秘莫测的TileMode

什么是TileMode呢?
事实上它只是一个枚举而已。它只有三个值。

Shader.TileMode CLAMP
Shader.TileMode MIRROR
Shader.TileMode REPEAT

CLAMP

它的意思当要绘制的区间大于图片纹理本身的区间时,多出来的空间位置将被纹理图片的边缘颜色填充。文字很难解释,我用图片来代替吧。
原图如下:

原图的分辨率是562*336

我们编写一个自定义View–CustomView。然后在它的onDraw()方法中画一个矩形,并且设置画笔的Shader为BitmapShader,Shader的tiling模式为CLAMP.
代码如下:

@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);int w = getWidth();int h = getHeight();Bitmap bmp = BitmapFactory.decodeResource(getResources(),R.drawable.yourname);mShader = new BitmapShader(bmp, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);mPaint.setShader(mShader);canvas.drawRect(0,0,w,h,mPaint);}

大家现在只需要关注mShader = new BitmapShader(bmp, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);这行代码就可以了,剩下的呆会讲。

在MainActivity中的布局文件中,我们加入这个自定义View。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/activity_main"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.frank.gradientdemo.MainActivity"><com.frank.gradientdemo.CustomView
        android:layout_width="match_parent"android:layout_height="400dp" />
</RelativeLayout>

我们可以看到CustomView的宽占手机整个屏幕,高是400dp.
我们在代码中以CustomView的宽高画一个矩形,并以上面的图片作为贴图纹理,效果如下:

效果图:

好像和原图有点不一样? 红框外面的是什么?我们把手机弄成横屏再看

这次双不一样了!红框右边也和下边一个德行了
让我们把注意力回到CLAMP的定义。

它的意思当要绘制的区间大于图片纹理本身的区间时,多出来的空间位置将被纹理图片的边缘颜色填充。

结合例子看,这下应该能明白它的含义了吧。上面的例子中,如果贴图的纹理本身小于要绘制的区域,那么超出部分将会以边缘的颜色填充。所以就造成了上面的现象。大家可以细细体会一下。我们看下一个知识点。

MIRROR

这个模式能够让纹理以镜像的方式在X和Y方向复制。

这个模式很容易理解大家看图。

@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);int w = getWidth();int h = getHeight();Bitmap bmp = BitmapFactory.decodeResource(getResources(),R.drawable.yourname);mShader = new BitmapShader(bmp, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);mPaint.setShader(mShader);canvas.drawRect(0,0,w,h,mPaint);
}

这就是镜像的效果。

REPEAT

它的作用是将图片纹理沿XY轴进行复制。什么意思?看图就懂,在这里,我要换一张图片,作为演示效果。

然后代码如下:

@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);int w = getWidth();int h = getHeight();Bitmap bmp = BitmapFactory.decodeResource(getResources(),R.drawable.repeat);mShader = new BitmapShader(bmp, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);mPaint.setShader(mShader);canvas.drawRect(0,0,w,h,mPaint);
}

效果:

哇噻!!!好多小狗狗。

大家有没有觉得Repeat模式特别有用呢?一张图就铺满整个空间。

混合双打

上面讲过的内容都是针对XY方向为同一种模式。能不能混合使用呢?

X—->CLAMP Y—->MIRROR
mShader = new BitmapShader(bmp, Shader.TileMode.CLAMP, Shader.TileMode.MIRROR);

狗狗看起来更忧伤了。

X—->MIRROR Y—->CLAMP
mShader = new BitmapShader(bmp, Shader.TileMode.MIRROR, Shader.TileMode.CLAMP);


有点恐怖是不是?

X—->CLAMP Y—->REPEAT
mShader = new BitmapShader(bmp, Shader.TileMode.CLAMP, Shader.TileMode.REPEAT);

可以看到右边的部分拉伸了,然后上下复制同样的图像。

X—->REPEAT Y—->CLAMP
mShader = new BitmapShader(bmp, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);

可以看到右边进行了复制,下面进行了拉伸。

X—->REPEAT Y—->MIRROR
mShader = new BitmapShader(bmp, Shader.TileMode.REPEAT, Shader.TileMode.MIRROR);

右边的复制,下面的是镜像。

X—->MIRROR Y—->REPEAT
mShader = new BitmapShader(bmp, Shader.TileMode.MIRROR, Shader.TileMode.REPEAT);


右边的是镜像,下面的是上面图像的复制。

好了,TILEMODE讲完了,我们进入主题(感觉怪怪的,这篇文章不是讲TILEMODE的吗?)

BitmapShader (Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)

我们再来回顾下它的构造方法,bitmap是纹理图片,两个TileMode的参数对象我们也已经知道了含义与用法。现在我们来了解一下它的用法。

@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);int w = getWidth();int h = getHeight();int radius = w <= h ? w/2 : h/2;//1 解析bitmap对象Bitmap bmp = BitmapFactory.decodeResource(getResources(),R.drawable.repeat);//2 以bitmap对象生成BitmapShader,并且设置它的X和Y轴方向上的TILEMODEmShader = new BitmapShader(bmp, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);//3 将BitmapShader对象安装到画笔对象上mPaint.setShader(mShader);//4 以该画笔绘制图形canvas.drawCircle(w/2,h/2,radius,mPaint);  }

上面的代码是绘制一个圆形,然后用图片重复铺图。效果如下:

是不是很有感觉? 像自定义圆形图片控件效果一样。这小狗忧伤的让我想想起了张嘉佳的《从你的全世界路过》的梅茜和刘大黑。

我们再发散思维下圆形图像控件代码编写?
相信大家都知道,用可以设置先用canvas绘制一张图片,然后设置画笔的Xfermode Paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); 然后再绘制一个圆。

现在我们尝试用BitmapShader的方式去编写这么一个功能。
思路:
1. 首先我们要确保这个自定义View是正方形的。
2. 我们以目标图片创建一个BitmapShader,然后设置进画笔。
3. 我们用设置好的画笔利用Canvas绘制一个圆形。
4. 关键一点,我们需要对原始的bitmap进行尺寸的调整,使得它的宽高至少要等于圆形的半径。

好了,编写代码.

public class CustomView extends View {private Paint mPaint;private Shader mShader;public CustomView(Context context) {this(context,null);}public CustomView(Context context, AttributeSet attrs) {this(context, attrs,0);}public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);mPaint = new Paint();mPaint.setAntiAlias(true);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);//这里为了方便演示,将尺寸固定为400*400setMeasuredDimension(400,400);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);int w = getWidth();int h = getHeight();int radius = w <= h ? w/2 : h/2;//原图Bitmap bmp = BitmapFactory.decodeResource(getResources(),R.drawable.repeat);//以目标宽高创建一个缩放过的图片Bitmap result = Bitmap.createScaledBitmap(bmp,w,h,false);//用位图创建BitmapShadermShader = new BitmapShader(result, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);mPaint.setShader(mShader);//画圆canvas.drawCircle(w/2,h/2,radius,mPaint);}}

效果图:

优伤的小狗又出来了。

更牛X的功能。

我们已经知道怎么样通过BitmapShader去渲染一个矩形或者是圆形了,但它的神奇之处就在于此吗???

当然不是!!! Shader被称为着色器,它用来渲染物体。在OPENGL 3d世界中,纹理可以看作是光秃秃的模型的皮肤,它可以为正文体,圆球,甚至复杂的人像模型着色。而在Canvas的范畴内,Shader肯定只是为了2d平面着色,除了矩形,圆形,它肯定还适用于三角形和其它多边形以及任何闭合的不规则图形,如何的图形称为不规则图形呢?

我想说文字算不算???
看图说话:

小狗狗的图像粘贴到文字上了。代码却十分的简单。

Bitmap bmp = BitmapFactory.decodeResource(getResources(),R.drawable.repeat);
mShader = new BitmapShader(bmp, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
mPaint.setTextSize(200.0f);
mPaint.setColor(Color.RED);
mPaint.setTypeface(Typeface.DEFAULT_BOLD);
mPaint.setShader(mShader);
canvas.drawText("小狗狗",0,h/2,mPaint);

好了,讲完了,意犹未尽的感觉。

本来还打算讲ComposeShader, LinearGradient, RadialGradient, SweepGradient的,由于篇幅原因,分开讲好了。下一篇讲其它的Shader子类。

Android绘图Canvas十八般武器之Shader详解及实战篇(下)

Android绘图Canvas十八般武器之Shader详解及实战篇(上)相关推荐

  1. Android绘图Canvas十八般武器之Shader详解及实战篇(下)

    前言 上一篇<Android绘图Canvas十八般武器之Shader篇(上)> 我们知道了Bitmap的用法,及TileMode的详细情况.接下来,这一篇作为整个知识体系的下半部要讲的是S ...

  2. 深度学习之图像分类(二十八)-- Sparse-MLP(MoE)网络详解

    深度学习之图像分类(二十八)Sparse-MLP(MoE)网络详解 目录 深度学习之图像分类(二十八)Sparse-MLP(MoE)网络详解 1. 前言 2. Mixture of Experts 2 ...

  3. (十八)享元模式详解(都市异能版) - 转

    作者:zuoxiaolong8810(左潇龙),转载请注明出处. 魔都. 自从越狱风波过去以后,小左的生活便又回到了之前的节奏,依旧是每日徘徊在魔都某天桥,继续着自己的算命之旅. 说起这次越狱风波,着 ...

  4. (十八)享元模式详解(都市异能版)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处. 魔都. 自从越狱风波过去以后,小左的生活便又回到了之前的节奏,依旧是每日徘徊在魔都某天桥,继续着自己的算命之旅. 说起这次越狱风波,着 ...

  5. 十八、redis.conf配置详解

    启动的时候,就通过配置文件来启动! 工作中,一些小小的配置,可以突出专业性! [root@localhost rconfig]# more redis.conf 单位 # Redis configur ...

  6. 网络安全系列-十八: OSI七层模型详解

    什么是OSI七层模型? OSI七层模型:Open System Interconnect,全称开放系统互连参考模型,是国际标准化组织(ISO)和国际电报电话咨询委员会(CCITT)联合制定的开放系统互 ...

  7. 创业十八般武器 第一辑

    朋友圈每天都有得了"创业病"的小伙伴,他们不是在创业的路上,就是在提辞职报告的路上,前赴后继带着"梦想还是要有的,万一实现了呢?"的人生信条雄赳赳.气昂昂地走在 ...

  8. (CUDA 编程5).CUDA编程接口(一)------一十八般武器

    (CUDA 编程5).CUDA编程接口(一)------一十八般武器 作者:赵开勇 来源:http://www.hpctech.com/2009/0818/203.html 子曰:工欲善其事,必先利其 ...

  9. android PorterDuffXfermode ,PorterDuff.Mode 使用 以及Porter-Duff规则详解

    android PorterDuffXfermode ,PorterDuff.Mode 使用 以及Porter-Duff规则详解 1.下面的Xfermode子类可以改变这种行为: AvoidXferm ...

最新文章

  1. spring boot +spring data jpa +druid 多数据源配置
  2. java 继承的内存分配_图解Java继承内存分配
  3. 遗传算法求二元函数极值怎么编码_遗传算法求解一元函数二元函数最值
  4. 10 个实战及面试常用 Shell 脚本编写
  5. SpringBoot集成Elasticsearch实现博客高亮搜索
  6. 规则引擎集成接口(九)Java类对象
  7. MATLAB学习笔记(五)
  8. git 应用 创建分支 并切换分支
  9. git pull出现错误的解决办法
  10. RESTFULL 03 rest-framework视图
  11. java是否安装outlook,Java程序定期检查ms Outlook是否有新邮件
  12. 修复APP的BUG,热修复的知识点和大厂的相关资料汇总
  13. 有关ERP、OA的认识学习
  14. 在sv testbench中加checker的几种办法
  15. 【重磅】《演化学习:理论与算法进展》| 南大周志华、俞扬、钱超重要成果...
  16. Nginx的try_files指令详解
  17. Hex文件头部修改软件
  18. 访问www.baidu.com全过程
  19. Outlook-VBA-02-对收件箱进行遍历
  20. 生成对抗网络GAN的前世今生

热门文章

  1. SURF特征点检测--SurfFeatureDetector、SurfDescriptorExtractor和FlannBasedMatcher
  2. YOLOv4团队打造YOLOv7!最先进的实时目标检测网络来了!
  3. sr550服务器配置硬盘,华为服务器 RAID卡配置 SR450C-M (Avago3508)
  4. java程序设计 一种跨学科_清华大学出版社-图书详情-《Java程序设计:一种跨学科的方法》...
  5. 计算机2级考试分几种,全国计算机2级考试的种类有哪几种啊?
  6. 联想电脑Windows无法访问Autodesk系列注册机解决办法
  7. (二)运行微信小程序:单页面和多页面
  8. SDN技术在数据中心的应用前景
  9. virt-manager安装xp.iso
  10. SUMO学习(二)创建路网