Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(上)
前言
Android中绘图离不开的就是Canvas了,Canvas是一个庞大的知识体系,有Java层的,也有jni层深入到Framework。Canvas有许多的知识内容,构建了一个武器库一般,所谓十八般武艺是也,Paint是Canvas的一个重要的合作伙伴,但今天要讲的不是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轴方向上的TILEMODE
mShader = 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);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//这里为了方便演示,将尺寸固定为400*400
setMeasuredDimension(400,400);
}
@Override
protected 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);
//用位图创建BitmapShader
mShader = 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为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(上)的更多相关文章
- Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(下)
LinearGradient 线性渐变渲染器 LinearGradient中文翻译过来就是线性渐变的意思.线性渐变通俗来讲就是给起点设置一个颜色值如#faf84d,终点设置一个颜色值如#CC423C, ...
- Android为TV端助力 转载:RecyclerView分页加载
package com.android.ryane.pulltoloaddata_recyclerview; import android.os.Handler;import android.os.L ...
- Android为TV端助力 转载:android MVC设计模式
Controller控制器 import android.app.Dialog; import android.app.ProgressDialog; import android.os.Bundle ...
- Android为TV端助力 转载自jguangyou的博客,XML基本属性大全
android:layout_width 指定组件布局宽度 android:layout_height 指定组件布局高度 android:alpha 设置组件透明度 android:backgroun ...
- Android为TV端助力 转载弩的博客
Android.mk简介:Android.mk文件用来告知NDK Build 系统关于Source的信息. Android.mk将是GNU Makefile的一部分,且将被Build System解析 ...
- Android为TV端助力:(转载)修改TextView字体样式
一.开篇 因为 Android 字体相关的内容还比较多的.有时候其实我们只需要调整一下属性就可以满足设计师的需求,或者是一个退后的方案(毕竟有发版的时间卡住了),有一些效果可以大概满足需求. 那么本文 ...
- Android为TV端助力转载:码农小阿飞(SpannableString)
用SpannableString打造绚丽多彩的文本显示效果 引语 TeXtView大家应该都不陌生,文本展示控件嘛! 就用TextView显示普普通通的文本,OK,很简单,Android入门的都会,没 ...
- Android为TV端助力 转载:android自定义view实战(温度控制表)!
效果图 package cn.ljuns.temperature.view; import com.example.mvp.R; import android.content.Context;impo ...
- Android为TV端助力 转载:Java 泛型
一. 泛型概念的提出(为什么需要泛型)? 首先,我们看下下面这段简短的代码: 1 public class GenericTest { 2 3 public static void main(Stri ...
随机推荐
- Python - 浅谈Python的编译与反编译
1 - Python编译过程涉及的文件 py 源代码文件,由python.exe解释,可在控制台下运行,可用文本编辑器进行编辑: pyc 源代码文件经过编译后生成的二进制文件,无法用文本编辑器进行编辑 ...
- 剑指offer--2
前言:继续笔记分享! 面试题6:暂无好的解决方法先搁浅一下 面试题7: #include<stdio.h> #include<stdlib.h> typedef struct ...
- 【HDFS API编程】第一个应用程序的开发-创建文件夹
/** * 使用Java API操作HDFS文件系统 * 关键点: * 1)创建 Configuration * 2)获取 FileSystem * 3)...剩下的就是 HDFS API的操作了*/ ...
- python中执行该文件,就调用 mian 方法
代码: test.py import student def main(): st = student.student(1001, 'tommy', 18) st.sing() st.dance() ...
- HBase 在人工智能场景的使用
近几年来,人工智能逐渐火热起来,特别是和大数据一起结合使用.人工智能的主要场景又包括图像能力.语音能力.自然语言处理能力和用户画像能力等等.这些场景我们都需要处理海量的数据,处理完的数据一般都需要存储 ...
- JS判断滚动条到底部,页面是否有滚动条
要判断页面滚动条是否到底,需要了解三个属性: scrollHeight:获取元素内容高度的度量,包括由于溢出导致的视图中不可见内容,说直白点,算上了滚动条不可见的那部分高度. clientHeight ...
- shiro源码篇 - shiro的filter,你值得拥有
前言 开心一刻 已经报废了一年多的电脑,今天特么突然开机了,吓老子一跳,只见电脑管家缓缓地出来了,本次开机一共用时一年零六个月,打败了全国0%的电脑,电脑管家已经对您的电脑失去信心,然后它把自己卸载了 ...
- Java基础系列--throw、throws关键字
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/8484697.html 一.概述 throw和throws就是异常相关的关键字,在java中 ...
- 深入MySQL复制(二):基于GTID复制
相比传统的MySQL复制,gtid复制无论是配置还是维护都要轻松的多.本文对gtid复制稍作介绍. MySQL基于GTID复制官方手册:https://dev.mysql.com/doc/refman ...
- lua的面向对象
Lua中的table就是一种对象,但是如果直接使用仍然会存在大量的问题,见如下代码: Account = {balance = } function Account.withdraw(v) Accou ...