Android 自定义 View 系列文章至今已经分析了自定义流程的 measure,layout 以及对 TouchEvent 的处理。那么接下来当然应该讲到对draw原理分析了,draw这一步骤是整个自定义过程中极为重要的一步,而今天这一篇文章就是对 draw 原理进行分析。经过 measure 测量和 layou t定位后,自定义 View 便进入了 draw 绘制阶段。
既然是对 draw 原理分析,那么自然首先看一下 View 的 draw() 方法源码了
在看源码之前我们先看 Google 官方文档对 android.view.View.draw() 方法的描述,这有助于我们对源码的理解:
Manually render this view (and all of its children) to the given Canvas. The view must have already done a full layout before this function is called. When implementing a view, implement onDraw(android.graphics.Canvas) instead of overriding this method. If you do need to override this method, call the superclass version.
上面的话大约有以下几点需要注意:
在给定的画布(canvas)上手动渲染这个视图(以及它所有的子视图(子 view))
在调用此函数之前,视图必须已经完成了完整的布局
当实现一个视图时,实现o nDraw(android.graphics.Canvas)而不是覆盖(重写)这个方法
如果你确实需要重写这个方法,调用超类的版本
我们可以看到绘制(draw)的操作是在canvas(画布)上进行的,所以这里我们再来了解一下Canvas类吧!
Google官方文档对 Canvas类 的描述如下:
The Canvas class holds the “draw” calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).
以上信息主要有以下几点:
Canvas 类保存对 draw() 方法的调用
如果要进行绘制(draw),需要四个基本组成部分
储存绘制的内容,用于保存像素的位图(Bitmap )、一个 Canvas 来承载绘制调用(写入位图,调用各种方法,如 canvas.drawLine()、canvas。drawPaint())、绘图图元(根据自己需求绘制,例如圆(Rect),路径(Path),文本,Bitmap)、画笔(Paint,描述绘图的颜色和风格)
我们可以看到 Canvas 类在 draw 过程中起着重要的作用,接下来我们直接上源码
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
if (!dirtyOpaque) onDraw(canvas);
dispatchDraw(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
onDrawForeground(canvas);
return;
}
boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;
float topFadeStrength = 0.0f;
float bottomFadeStrength = 0.0f;
float leftFadeStrength = 0.0f;
float rightFadeStrength = 0.0f;
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;
if (verticalEdges && (top + length > bottom - length)) {
length = (bottom - top) / 2;
}
if (horizontalEdges && (left + length > right - length)) {
length = (right - left) / 2;
}
if (verticalEdges) {
topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
drawTop = topFadeStrength * fadeHeight > 1.0f;
bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
}
if (horizontalEdges) {
leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength * fadeHeight > 1.0f;
rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
drawRight = rightFadeStrength * fadeHeight > 1.0f;
}
saveCount = canvas.getSaveCount();
int solidColor = getSolidColor();
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
}
if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null, flags);
}
if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null, flags);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}
if (!dirtyOpaque) onDraw(canvas);
dispatchDraw(canvas);
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, bottom - length, right, bottom, p);
}
if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, left + length, bottom, p);
}
if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(right - length, top, right, bottom, p);
}
canvas.restoreToCount(saveCount);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
onDrawForeground(canvas);
}
以上就是 draw() 方法的具体实现逻辑了,主要有一下几步:
绘制背景
如果需要,保存画布的图层以准备褪色
绘制视图的内容
绘制子 View
如有必要,绘制褪色边缘并恢复图层
绘制装饰(例如滚动条)
接下来对以上几步进行具体分析:
第一步:
在 canvas 画布上进行背景绘制
第二步:
保存当前画布的堆栈状态并在该画布上创建 Layer 用于绘制 View 在滑动时的边框渐变效果
第三步:
绘制 View 的内容
这一步是 draw 过程的核心所在,这一步会调用 onDraw() 方法绘制 View 的内容。
在之前对 onLayout() 方法进行源码分析的时候发现 onLayout() 方法是一个空方法,而是由 ViewGroup 的子类去实现具体实现逻辑。onDraw() 方法与之相似,因为每个 View 实现的内容都不一样,所以需要由具体的子 View 去实现不同的需求
第四步:
用 dispatchDraw() 绘制 View 的子 View
第五步:
绘制当前视图在滑动时的边框渐变效果,通常情况下我们是不需要处理这一步的
第六步:
绘制 View 的滚动条
看完 draw() 源码后我们知道此方法的核心是在第三步调用了 onDraw() 方法,所以接下来我们就从此方法继续深入分析吧!
protected void onDraw(Canvas canvas) {
}
可以看到传入的是一个 canvas 对象,在前面我们已经知道了进行绘制需要的四个基本组成部分,这里我们再来看一下如何构建一个 canvas,先看 Canvas 类的两个构造方法:
Canvas()
Construct an empty raster canvas.
/**
* Construct an empty raster canvas. Use setBitmap() to specify a bitmap to
* draw into. The initial target density is {@link Bitmap#DENSITY_NONE};
* this will typically be replaced when a target bitmap is set for the
* canvas.
*/
public Canvas() {
if (!isHardwareAccelerated()) {
mNativeCanvasWrapper = initRaster(null);
mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper);
} else {
mFinalizer = null;
}
}
从对该方法的注释可知,官方不推荐通过该无参的构造方法生成一个canvas。如果要这么做那就需要调用setBitmap( )为其设置一个Bitmap。为什么Canvas非要一个Bitmap对象呢?原因很简单:Canvas需要一个Bitmap对象来保存像素,如果画的东西没有地方可以保存,又还有什么意义呢?既然不推荐这么做,那就我们接着看有参的构造方法。
Canvas(Bitmap bitmap)
Construct a canvas with the specified bitmap to draw into.
/**
* Construct a canvas with the specified bitmap to draw into. The bitmap
* must be mutable.
*
* The initial target density of the canvas is the same as the given
* bitmap's density.
*
* @param bitmap Specifies a mutable bitmap for the canvas to draw into.
*/
public Canvas(Bitmap bitmap) {
if (!bitmap.isMutable()) {
throw new IllegalStateException("Immutable bitmap passed to Canvas constructor");
}
throwIfCannotDraw(bitmap);
mNativeCanvasWrapper = initRaster(bitmap);
mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper);
mBitmap = bitmap;
mDensity = bitmap.mDensity;
}
这一个方法就传入了一个 Bitmap 作为参数来保持绘制图像的信息。
好了,既然已经知道如何创建 canvas,那么接下来我们就尝试着使用 canvas 来进行绘制吧!
protected void drawOnBitmap(){
Bitmap bitmap = Bitmap.createBitmap(800, 400, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(Color.Blue);
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setTextSize(60);
canvas.drawText("This is my view", 150, 200, paint);
mImageView.setImageBitmap(bitmap);
}
可以看到在此处为 canvas 设置一个 Bitmap,然后利用 canvas 画了一小段文字,最后使用 ImageView 显示了 Bitmap。
到这一步我们不禁思索,我们平常用得最多的 View 的 onDraw() 方法,为什么没有 Bitmap 也可以画出各种图形呢?
我们知道 onDraw( ) 的输入参数是一个 canvas,它与我们自己创建的 canvas 不同。这个系统传递给我们的 canvas 来自于 ViewRootImpl 的 Surface,在绘图时系统将会 SkBitmap 设置到 SkCanvas 中并返回与之对应 Canvas。所以,在 onDraw() 中也是有一个 Bitmap 的,只是这个 Bitmap 是由系统创建的罢了。
既然已经提到了 onDraw( ) 我们就利用 onDraw( )方法来画一些常见的图形。
@Override
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
mPaint.setColor(Color.WHITE);
canvas.drawRect(0,0,800,800,mPaint);
mPaint.reset();
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(10);
canvas.drawLine(0,0,20,40,mPaint);
mPaint.reset();
mPaint.setStrokeWidth(10);
mPaint.setARGB(150,90,255,0);
mPaint.setStyle(Paint.Style.STROKE);
RectF rectF1 = new RectF(30, 60, 350, 350);
canvas.drawRect(rectF1, mPaint);
mPaint.reset();
mPaint.setStrokeWidth(10);
mPaint.setColor(Color.BLUE);
mPaint.setAntiAlias(true);
canvas.drawCircle(670, 300, 70, mPaint);
mPaint.reset();
mPaint.setColor(Color.GREEN);
RectF rectF2 = new RectF(200, 430, 600, 600);
canvas.drawOval(rectF2, mPaint);
mPaint.reset();
mPaint.setColor(Color.BLACK);
mPaint.setTextSize(60);
mPaint.setUnderlineText(true);
canvas.drawText("Hello Android", 150, 720, mPaint);
mPaint.reset();
}
以上演示了常用图形的绘制方法,更多绘制方法请参考 Google 开发文档 API,除了绘制图形之外,还可以调用API方法设置动画如平移、旋转、缩放、剪裁等!下面演示此类常用方法:
canvas.translate()
canvas.rotate()
canvas.clipRect()
canvas.save()和canvas.restore()
PorterDuffXfermode
Bitmap和Matrix
Shader
PathEffect
下面分别介绍它们的使用:
canvas.translate()
使用 canvas.translate() 可以实现平移操作
@Override
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
canvas.drawColor(GREEN);
Paint mPaint = new Paint();
mPaint.setTextSize(70);
mPaint.setColor(Color.RED);
canvas.drawText("Test translate", 20, 80, mPaint);
canvas.translate(100, 300);
mPaint.setColor(Color.BLACK);
canvas.drawText("Test translate", 20, 80, mPaint);
}
使用 translate() 使其在 X 方向平移了 100,在 Y 方向上平移了 300。
所以,执行平移后文字的位置 = 执行平移前的位置 + 平移的单位。也就是说 canvas.translate() 相当于移动了坐标原点,移动了坐标系。
canvas.rotate()
使用 canvas.rotate() 可以实现旋转操作
@Override
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
canvas.drawColor(Color.GREEN);
Paint mPaint = new Paint();
mPaint.setTextSize(70);
mPaint.setColor(Color.RED);
canvas.drawText("Test ratate", 20, 80, mPaint);
canvas.rotate(15);
mPaint.setColor(Color.BLACK);
canvas.drawText("Test ratate", 20, 80, mPaint);
}
canvas.clipRect()
使用 canvas.clipRect 进行剪裁操作,执行该操作后的绘制将显示在剪裁区域。
@Override
protected void onDraw(Canvas canvas){
super.onDraw(cancas);
canvas.drawColor(Color.GREEN);
Paint mPaint = new Paint();
mPaint.setTextSize(70);
mPaint.setColor(Color.RED);
canvas.drawText("Test clip", 20, 80, mPaint);
Rect rect = new Rect(20, 200, 900, 1000);
canvas.clipRect(rect);
canvas.drawColor(Color.BLUE);
mPaint.setColor(Color.BLACK);
canvas.drawText("Test clip", 10, 310, mPaint);
}
调用 canvas.clipRect( ) 后,如果继续进行绘制那么所绘制的图只会在所剪裁的范围内体现。
当然除了按照矩形剪裁以外,还可以有别的剪裁方式,如:canvas.clipPath( )和canvas.clipRegion( )。
canvas.save() 和 canvas.restore()
canvas.save() 表示对画布的锁定操作,执行方法后绘制的图形将被锁定,之后的绘制不会影响之前绘制好的图形。
既然 save() 不会影响到原本已经画好的图形,那之后的操作又发生在哪里呢?
当执行 canvas.save( ) 时会生成一个新的图层(Layer),并且这个图层是透明的。此时,所有 draw 的方法都是在这个图层上进行,所以不会对之前画好的图形造成任何影响。在进行一些绘制操作后再使用 canvas.restore() 将这个新的图层与底下原本的画好的图像相结合形成一个新的图像。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.GREEN);
Paint paint=new Paint();
paint.setTextSize(70);
paint.setColor(Color.BLUE);
canvas.drawText("Before clip", 20, 80, paint);
canvas.save();
Rect rect=new Rect(20,200,900,1000);
canvas.clipRect(rect);
canvas.drawColor(Color.YELLOW);
paint.setColor(Color.BLACK);
canvas.drawText("After clip", 10, 310, paint);
canvas.restore();
paint.setColor(Color.RED);
canvas.drawText("Test", 20, 170, paint);
}
以上代码主要有以下几步:
执行 canvas.save( ) 锁定 canvas
在新的 Layer 上裁剪和绘图
执行 canvas.restore( ) 将 Layer 合并到原图
继续在原图上绘制
在使用 canvas.save 和 canvas.restore 时需注意一个问题:
save() 和 restore() 最好配对使用,若 restore() 的调用次数比 save() 多可能会造成异常
PorterDuffXfermode
在项目开发中,我们经常会遇到这样的需求,为图片设置圆角显示
/**
* @param bitmap 原图
* @param pixels 角度
* @return 带圆角的图
*/
public Bitmap getRoundCornerBitmap(Bitmap bitmap, float pixels) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
Bitmap roundCornerBitmap = Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(roundCornerBitmap);
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setAntiAlias(true);
Rect rect = new Rect(0, 0, width, height);
RectF rectF = new RectF(rect);
canvas.drawRoundRect(rectF, pixels, pixels, paint);
PorterDuffXfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
paint.setXfermode(xfermode);
canvas.drawBitmap(bitmap, rect, rect, paint);
return roundCornerBitmap;
}
主要操作如下:
生成 canvas
绘制圆角矩形
为 Paint 设置 PorterDuffXfermode
绘制原图
PorterDuffXfermode 有以下几种操作可以选择:
PorterDuff.Mode.CLEAR
绘制不会提交到画布上
PorterDuff.Mode.SRC
只显示绘制源图像
PorterDuff.Mode.DST
只显示目标图像,即已在画布上的初始图像
PorterDuff.Mode.SRC_OVER
正常绘制显示,即后绘制的叠加在原来绘制的图上
PorterDuff.Mode.DST_OVER
上下两层都显示但是下层(DST)居上显示
PorterDuff.Mode.SRC_IN
取两层绘制的交集且只显示上层(SRC)
PorterDuff.Mode.DST_IN
取两层绘制的交集且只显示下层(DST)
PorterDuff.Mode.SRC_OUT
取两层绘制的不相交的部分且只显示上层(SRC)
PorterDuff.Mode.DST_OUT
取两层绘制的不相交的部分且只显示下层(DST)
PorterDuff.Mode.SRC_ATOP
两层相交,取下层(DST)的非相交部分和上层(SRC)的相交部分
PorterDuff.Mode.DST_ATOP
两层相交,取上层(SRC)的非相交部分和下层(DST)的相交部分
PorterDuff.Mode.XOR
挖去两图层相交的部分
PorterDuff.Mode.DARKEN
显示两图层全部区域且加深交集部分的颜色
PorterDuff.Mode.LIGHTEN
显示两图层全部区域且点亮交集部分的颜色
PorterDuff.Mode.MULTIPLY
显示两图层相交部分且加深该部分的颜色
PorterDuff.Mode.SCREEN
显示两图层全部区域且将该部分颜色变为透明色
Bitmap和Matrix
除了给图片设置圆角外,在开发中还常有其他涉及到图片的操作,比如图片的旋转,缩放,平移等等,这些操作可以结合 Matrix 来实现。
在此举个例子,看看利用 matrix 实现图片的平移和缩放。
private void drawBitmapWithMatrix(Canvas canvas){
Paint paint = new Paint();
paint.setAntiAlias(true);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.scale);
int width = bitmap.getWidth();
int height = bitmap.getHeight();
Matrix matrix = new Matrix();
canvas.drawBitmap(bitmap, matrix, paint);
matrix.setTranslate(width/2, height);
canvas.drawBitmap(bitmap, matrix, paint);
matrix.postScale(0.5f, 0.5f);
//matrix.preScale(2f, 2f);
canvas.drawBitmap(bitmap, matrix, paint);
}
这里主要涉及到 Matrix,我们可以通过这个矩阵来实现对图片的一些操作。利用 Matrix 实现了图片的平移是和使用 cancas.translate() 方法一样将坐标系进行了平移吗?当然不是的,Matrix 所操作的是原图的每个像素点,它和坐标系是没有关系的。比如 Scale 是对每个像素点都进行了缩放,例如:
matrix.postScale(0.5f, 0.5f);
将原图的每个像素点的 X 坐标和 Y 坐标都缩放成了原本的 0.5
使用 Matrix 时常常会用到一系列的 set,pre,post方法,它们有什么区别,它们的调用顺序又会对实际效果有什么影响呢?
set表示清空队列
pre表示在队头插入一个方法
post表示在队尾插入一个方法
队列中只保留该方法,其余方法都清除
当执行了一次 set 后 pre 总是插入到 set 之前的队列的最前面;post 总是插入到 set 之后的队列的最后面
set 在队列的中间位置,pre 执行队头插入,post 执行队尾插入
当绘制图像时系统会按照队列中从头至尾的顺序依次调用这些方法
为了便于理解,下面通过几个例子来解释:
Matrix matrix = new Matrix();
matrix.setRotate(45);
matrixm.setTranslate(80, 80);
调用了两个不同的 set 方法,因为队列中只保留该方法,其余方法都清除。所以只执行 setTranslate(80, 80);
Matrix matrix = new Matrix();
matrix.setTranslate(80, 80);
matrix.preRotate(45);
执行了一次 set 方法后,pre 方法插入到set方法队首,所以先执行 matrix.preRotate(45); 再执行 matrixm.setTranslate(80, 80);
Matrix matrix = new Matrix();
matrix.setTranslate(80, 80);
matrix.postRotate(45);
执行了一次 set 方法后,pre 方法插入到 set 方法队尾,所以先执行 matrixm.setTranslate(80, 80); 再执行 matrix.postRotate(45);
Matrix m = new Matrix();
matrix.preScale(2f,2f);
matrix.preTranslate(50f, 20f);
matrix.postScale(0.2f, 0.5f);
matrix.postTranslate(20f, 20f);
这里使用了两个 set 系列方法,两个 post 系列方法,pre 系列方法插入到队列的队首,post 系列方法插入到队尾,所以执行顺序为 matrix.preTranslate(50f, 20f); —>matrix.preScale(2f,2f);—>matrix.postScale(0.2f, 0.5f);—>matrix.postTranslate(20f, 20f);
Matrix m = new Matrix();
matrix.preScale(2f,2f);
matrix.preTranslate(50f, 20f);
matrix.postScale(0.2f, 0.5f);
matrix.postTranslate(20f, 20f);
Shader
Shader 可以帮助我们现图像的渐变效果
看一下 Google 官方文档对 Shader 的描述不:
Shader is the based class for objects that return horizontal spans of colors during drawing. A subclass of Shader is installed in a Paint calling paint.setShader(shader). After that any object (other than a bitmap) that is drawn with that paint will get its color(s) from the shader.
Shader 类主要用于渲染图像以及几何图形
Shader 的主要子类如下:
BitmapShader———图像渲染
LinearGradient——–线性渲染
RadialGradient——–环形渲染
SweepGradient——–扫描渲染
ComposeShader——组合渲染
调用 paint.setShader(Shader shader) 就可以实现渲染效果,在此以常用的 BitmapShader 为示例实现圆形图片
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setAntiAlias(true);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.girl);
int radius = bitmap.getWidth()/2;
BitmapShader bitmapShader = new BitmapShader(bitmap,Shader.TileMode.REPEAT,Shader.TileMode.REPEAT);
paint.setShader(bitmapShader);
canvas.translate(250,430);
canvas.drawCircle(radius, radius, radius, paint);
}
以上代码主要可以分成以下几步分:
生成 BitmapShader
为 Pain 设置 Shader
画出圆形图片
TileMode 有三种模式:
REPEAT :重复
MIRROR :镜像
CLAMP:拉伸
这三种效果类似于给电脑屏幕设置屏保时,若图片太小可选择重复,拉伸,镜像。
若选择 REPEAT(重复 ):横向或纵向不断重复显示 bitmap
若选择 MIRROR(镜像):横向或纵向不断翻转重复
若选择 CLAMP(拉伸) :横向或纵向拉伸图片在该方向的最后一个像素。这点和设置电脑屏保有些不同
第三个参数:
tileY 表示在位图上Y方向渲染器平铺模式(TileMode),tileX 同理。
这里调用的资源图片的大小会影响到整体图片效果,各位可以自行调整图片像素进行尝试,这里举个例子,如使用 Shader 实现圆形图片时,像素应为 200*200,才能够实现占满整个图像,而不会呈现重复,镜像,和拉伸的情况。