前言

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个子类 BitmapShaderComposeShaderLinearGradientRadialGradientSweepGradient。 本文的目的也是分别讲它的各个子类。

图片渲染器 BitmapShader

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

  1. BitmapShader (Bitmap bitmap,
  2. Shader.TileMode tileX,
  3. Shader.TileMode tileY)

上面是BitmapShader的构造方法。

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

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

神秘莫测的TileMode

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

  1. Shader.TileMode CLAMP
  2. Shader.TileMode MIRROR
  3. Shader.TileMode REPEAT

CLAMP

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

原图的分辨率是562*336

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

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4. int w = getWidth();
  5. int h = getHeight();
  6. Bitmap bmp = BitmapFactory.decodeResource(getResources(),R.drawable.yourname);
  7. mShader = new BitmapShader(bmp, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
  8. mPaint.setShader(mShader);
  9. canvas.drawRect(0,0,w,h,mPaint);
  10. }

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

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

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. android:id="@+id/activity_main"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. tools:context="com.frank.gradientdemo.MainActivity">
  8. <com.frank.gradientdemo.CustomView
  9. android:layout_width="match_parent"
  10. android:layout_height="400dp" />
  11. </RelativeLayout>

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

效果图: 

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

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

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

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

MIRROR

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

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

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4. int w = getWidth();
  5. int h = getHeight();
  6. Bitmap bmp = BitmapFactory.decodeResource(getResources(),R.drawable.yourname);
  7. mShader = new BitmapShader(bmp, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);
  8. mPaint.setShader(mShader);
  9. canvas.drawRect(0,0,w,h,mPaint);
  10. }

这就是镜像的效果。

REPEAT

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

然后代码如下:

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4. int w = getWidth();
  5. int h = getHeight();
  6. Bitmap bmp = BitmapFactory.decodeResource(getResources(),R.drawable.repeat);
  7. mShader = new BitmapShader(bmp, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
  8. mPaint.setShader(mShader);
  9. canvas.drawRect(0,0,w,h,mPaint);
  10. }

效果: 

哇噻!!!好多小狗狗。

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

混合双打

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

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

狗狗看起来更忧伤了。

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

 
有点恐怖是不是?

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

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

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

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

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

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

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

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

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

  1. BitmapShader (Bitmap bitmap,
  2. Shader.TileMode tileX,
  3. Shader.TileMode tileY)

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

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

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

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

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

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

好了,编写代码.

  1. public class CustomView extends View {
  2. private Paint mPaint;
  3. private Shader mShader;
  4. public CustomView(Context context) {
  5. this(context,null);
  6. }
  7. public CustomView(Context context, AttributeSet attrs) {
  8. this(context, attrs,0);
  9. }
  10. public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
  11. super(context, attrs, defStyleAttr);
  12. mPaint = new Paint();
  13. mPaint.setAntiAlias(true);
  14. }
  15. @Override
  16. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  17. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  18. //这里为了方便演示,将尺寸固定为400*400
  19. setMeasuredDimension(400,400);
  20. }
  21. @Override
  22. protected void onDraw(Canvas canvas) {
  23. super.onDraw(canvas);
  24. int w = getWidth();
  25. int h = getHeight();
  26. int radius = w <= h ? w/2 : h/2;
  27. //原图
  28. Bitmap bmp = BitmapFactory.decodeResource(getResources(),R.drawable.repeat);
  29. //以目标宽高创建一个缩放过的图片
  30. Bitmap result = Bitmap.createScaledBitmap(bmp,w,h,false);
  31. //用位图创建BitmapShader
  32. mShader = new BitmapShader(result, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
  33. mPaint.setShader(mShader);
  34. //画圆
  35. canvas.drawCircle(w/2,h/2,radius,mPaint);
  36. }
  37. }

效果图: 

优伤的小狗又出来了。

更牛X的功能。

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

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

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

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

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

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

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

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

Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(上)的更多相关文章

  1. Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(下)

    LinearGradient 线性渐变渲染器 LinearGradient中文翻译过来就是线性渐变的意思.线性渐变通俗来讲就是给起点设置一个颜色值如#faf84d,终点设置一个颜色值如#CC423C, ...

  2. Android为TV端助力 转载:RecyclerView分页加载

    package com.android.ryane.pulltoloaddata_recyclerview; import android.os.Handler;import android.os.L ...

  3. Android为TV端助力 转载:android MVC设计模式

    Controller控制器 import android.app.Dialog; import android.app.ProgressDialog; import android.os.Bundle ...

  4. Android为TV端助力 转载自jguangyou的博客,XML基本属性大全

    android:layout_width 指定组件布局宽度 android:layout_height 指定组件布局高度 android:alpha 设置组件透明度 android:backgroun ...

  5. Android为TV端助力 转载弩的博客

    Android.mk简介:Android.mk文件用来告知NDK Build 系统关于Source的信息. Android.mk将是GNU Makefile的一部分,且将被Build System解析 ...

  6. Android为TV端助力:(转载)修改TextView字体样式

    一.开篇 因为 Android 字体相关的内容还比较多的.有时候其实我们只需要调整一下属性就可以满足设计师的需求,或者是一个退后的方案(毕竟有发版的时间卡住了),有一些效果可以大概满足需求. 那么本文 ...

  7. Android为TV端助力转载:码农小阿飞(SpannableString)

    用SpannableString打造绚丽多彩的文本显示效果 引语 TeXtView大家应该都不陌生,文本展示控件嘛! 就用TextView显示普普通通的文本,OK,很简单,Android入门的都会,没 ...

  8. Android为TV端助力 转载:android自定义view实战(温度控制表)!

    效果图 package cn.ljuns.temperature.view; import com.example.mvp.R; import android.content.Context;impo ...

  9. Android为TV端助力 转载:Java 泛型

    一. 泛型概念的提出(为什么需要泛型)? 首先,我们看下下面这段简短的代码: 1 public class GenericTest { 2 3 public static void main(Stri ...

随机推荐

  1. AI - 机器学习常见算法简介(Common Algorithms)

    机器学习常见算法简介 - 原文链接:http://usblogs.pwc.com/emerging-technology/machine-learning-methods-infographic/ 应 ...

  2. python高级-面向对象(11)

    一.面向过程和面向对象 面向过程:根据业务逻辑从上到下写代码 面向对象:将数据与函数绑定到一起,进行封装,这样能够更快速的开发程序,减少了重复代码的重写过程 二.类和对象 1.类的概念 面向对象编程的 ...

  3. Python并发目录

    Python并发目录 Python-socket网络编程 Python网络编程-IO阻塞与非阻塞及多路复用 Python进程-理论 Python进程-实现 Python进程间通信 Python进程池 ...

  4. Http状态信息

    一.HTTP协议1.简介:http超文本传输协议,基于请求与响应模式的,无状态的,应用层的协议.绝大读书的web开发都是建立在http协议之上的.2.http工作过程:当请求一个超链接时,http就开 ...

  5. MngoDb MongoClientOptions 配置信息及常用配置信息

    MongoClientOptions.Builder addClusterListener(ClusterListener clusterListener)Adds the given cluster ...

  6. [Shell]Shell调用并获取执行jar包后的返回值

    ----------------------------------------------------------------- 原创博文,如需转载请注明出处! 博主:疲惫的豆豆 链接:http:/ ...

  7. Day2----Python学习之路笔记(2)

    学习路线: Day1 Day2 Day3 Day4 Day5 ...待续 一.简单回顾一下昨天的内容 1. 昨天了解到了一些编码的知识 1.1. 我们写好的.py文件头没有加# -*- coding: ...

  8. TCP/IP 笔记 - 安全

    安全:可扩展身份认证协议.IP安全协议.传输层安全.DNS安全.域名密钥识别邮件 任何由用户或以用户账号执行却违背了用户本身意愿的软件被称为恶意软件 网络安全是一门十分广泛及有深度的学识,而本书旨在了 ...

  9. python安装Jieba中文分词组件并测试

    python安装Jieba中文分词组件 1.下载http://pypi.python.org/pypi/jieba/ 2.解压到解压到python目录下: 3.“win+R”进入cmd:依次输入如下代 ...

  10. 使用LINQ生成Where的SQL语句

    实例1-使用实例-单个语句: ; List<, , }; List<User_info> userInfoList = UserCenterBus.Select_WebSiteBas ...