安卓自定义控件(一)Canvas、Paint、Shader、Xfermode
关于自定义控件,之前就写过一篇自定义控件,上图下字的Button,图片任意指定大小,但是使用效果还是让人感觉不幸福,这次索性彻彻底底地对自定义控件做一次彻彻底底的总结。
我会花4篇博客来介绍自定义控件,由潜入深慢慢学:
工具类:ViewHelper(View处理常用方法封装)
安卓自定义控件(一)Canvas、Paint、Shader、Xfermode
安卓自定义控件(二)BitmapShader、ShapeDrawable、Shape
安卓自定义控件(三)实现自定义View
4、onLayout(实现自定义ViewGroup)。
这一篇博客就先介绍一下onDraw方法。
概述
自定义控件有三个比较重要的方法
- onDraw 就是在绘制View的样式。
- onMeasure 算出自己需要占用多大的面积。
- onLayout 按照我们想要的规则自定义子View排列。
因为View设计出来肯定是给别人看的,首先我们考虑绘制View,而绘制View就必须先学会Canvas(画布)和Paint(画笔);然后就是设置View的大小,让我们的控件更加强大,更加通用,就像Button,它可以指定大小,也可以不指定;如果你设计的是ViewGroup,那可能还得考虑每一个子View的位置,那就得考虑重写onLayout方法。
最简单的自定义View
源码:
写一个最简单的自定义控件,鼓励一下自己吧。
/**
* 矩形
* Created by ChenSS on 2016/11/24.
*/
public class RectView extends View{
public RectView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.RED);
canvas.drawRect(60, 60, 180, 180, paint);
//或者
//canvas.drawRect(new Rect(200,200,300,300),paint);
}
}
在Activity使用我们的自定义View:
public class RectActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new RectView(this));
}
}
这样就写完了,是不是感觉自己棒棒哒?我们实现了一个最简单的自定义View,不过它非常弱小,只是一个矩形,甚至连长宽都被我们写死了。
小结:
这个自定义控件虽然有点Low,但是我们也有一些收获:
- onDraw的参数是一个Canvas(画布),画布决定了绘制的是一个什么样的图形;
- 画布的使用需要有Paint(画笔)的配合,画笔决定了绘制的颜色;
- 此外,还会涉及到Color(颜色),Bitmap(位图),Shader(着色),Shape(形状)等等。
以上是一个粗略总结,查看API会发现,上面提到的类都属于 android.graphics这个包,接下去就去瞅一瞅他们到底有什么本事。
认识graphics中常用的类
1.Color(颜色)类
Android系统中颜色的常用表示方法有以下4种:
(1)int color = Color.BLUE;
(2)int color = Color.argb(150,200,0,100);
(3)layout.setBackgroundColor(R.color.white),引用颜色资源;
(4)Color.parseColor(“#365663”)。
在实际应用中,我们常用的颜色有以下几种,其颜色常量及其表示的颜色如下所示:
- Color.BLACK 黑色
- Color.GREEN 绿色
- Color.BLUE 蓝色
- Color.LTGRAY浅灰色
- Color.CYAN 青绿色
- Color.MAGENTA 红紫色
- Color.DKGRAY 灰黑色
- Color.RED 红色
- Color.YELLOW 黄色
- Color.TRANSPARENT 透明
- Color.GRAY 灰色
- Color.WHITE 白色
2.Paint(画笔)类
要绘制图形,首先得调整画笔,按照自己的开发需要,设置画笔的相关属性。Pain类的常用属性设置方法如下:
- setAntiAlias(); //设置画笔的锯齿效果
- setColor(); //设置画笔的颜色
- setARGB(); //设置画笔的A、R、G、B值
- setAlpha(); //设置画笔的Alpha值
- setStyle(); //设置画笔的风格(空心或实心)
- setStrokeWidth(); //设置空心边框的宽度
- getColor(); //获取画笔的颜色
- setTextScaleX(float scaleX) // 设置文本缩放倍数,1.0f为原始
- setTextSize(float textSize) // 设置字体大小
setUnderlineText(booleanunderlineText) // 设置下划线
3.Canvas(画布)类
画笔属性设置好之后,还需要将图像绘制到画布上。Canvas绘制常用图形的常用方法如下:
- 绘制直线:canvas.drawLine(float startX, float startY, float stopX, float stopY, Paint paint);
- 绘制矩形:canvas.drawRect(float left, float top, float right, float bottom, Paint paint);
- 绘制圆形:canvas.drawCircle(float cx, float cy, float radius, Paint paint);
- 绘制字符:canvas.drawText(String text, float x, float y, Paint paint);
- 绘制图形:canvas.drawBitmap(Bitmap bitmap, float left, float top, Paint paint);
- 绘制扇形(在一个矩形区域的两个角之间绘制一个弧。):drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
- 绘制圆角矩形:drawRoundRect(RectF rect, float rx, float ry, Paint paint)
- 绘制椭圆:drawOval(float left, float top, float right, float bottom, Paint paint)
补充:绘制任意多边形
其实画笔还有其它功能,这里就不再展开,这里再补充一个绘制多边形的案例。
/**
* 绘制任意多边形(简单地绘制了一个三角形,我比较懒)
* Created by ChenSS on 2016/11/24.
*/
public class RectView extends View{
public RectView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
//设置空心
paint.setStyle(Paint.Style.STROKE);
Path path1=new Path();
path1.moveTo(200, 800);
path1.lineTo(800, 800);
path1.lineTo(800, 200);
path1.close();//封闭
canvas.drawPath(path1, paint);
}
}
Shader(着色器、渲染)
绘制完图形,接下来就要考虑颜色的问题了,虽然Color很强大,但是也并不能满足我们全部的问题,就比如:颜色渐变,这时候就要依靠Shader了,Shader直译是着色器,我就称呼它着色器吧。
Shader它主要实现颜色的渲染效果,此外还提供了颜色渐变、图片拉伸、平铺的功能,它包含以下5个子类:BitmapShader, ComposeShader, LinearGradient, RadialGradient, SweepGradient。
LinearGradient线性渲染
Create a shader that draws a linear gradient along a line.
创建一个着色器,颜色沿一条直线进行渐变。
//创建线性渲染对象
int mColorLinear[] = {Color.RED, Color.GREEN, Color.BLUE, Color.WHITE};
Shader mShader = new LinearGradient(0, 0, 100, 100, mColorLinear, null, Shader.TileMode.REPEAT);
构造函数:
LinearGradient(float x0, float y0, float x1, float y1, int[] colors, float[] positions, Shader.TileMode tile)
- float x0: 渐变起始点x坐标
- float y0:渐变起始点y坐标
- float x1:渐变结束点x坐标
- float y1:渐变结束点y坐标
- int[] colors:颜色 的int 数组
- float[] positions: 相对位置的颜色数组,可为null, 若为null,可为null,颜色沿渐变线均匀分布
- Shader.TileMode tile: 渲染器平铺模式
LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, Shader.TileMode tile)
- float x0: 渐变起始点x坐标
- float y0:渐变起始点y坐标
- float x1:渐变结束点x坐标
- float y1:渐变结束点y坐标
- int color0: 起始渐变色
- int color1: 结束渐变色
- Shader.TileMode tile: 渲染器平铺模式
SweepGradient梯度渲染
A subclass of Shader that draws a sweep gradient around a center point.
创建一个着色器,颜色沿着某个点的旋转方向进行渐变。
//创建线性渲染对象
int mColorLinear[] = {Color.RED, Color.GREEN, Color.BLUE, Color.WHITE};
mSweepGradient = new SweepGradient(240, 360,mColorLinear , null);
构造函数:
SweepGradient(float cx, float cy, int[] colors, float[] positions)
- cx 渲染中心点x 坐标
- cy 渲染中心y 点坐标
- colors 围绕中心渲染的颜色数组,至少要有两种颜色值
- positions 相对位置的颜色数组,可为null, 若为null,可为null,颜色沿渐变线均匀分布
SweepGradient(float cx, float cy, int color0, int color1)
- cx 渲染中心点x 坐标
- cy 渲染中心点y 坐标
- color0 起始渲染颜色
- color1 结束渲染颜色
RadialGradient环形渲染
Create a shader that draws a radial gradient given the center and radius.
创建一个着色器,颜色沿着半径方向进行渐变。
//颜色渐变效果为一圈一圈的圆环
int mColorRadial[] = {Color.GREEN, Color.RED, Color.BLUE, Color.WHITE};
Shader mShader = new RadialGradient(350, 325, 75, mColorRadial, null, Shader.TileMode.REPEAT);
构造函数:
RadialGradient(float centerX, float centerY, float radius, int[] colors, float[] stops, Shader.TileMode tileMode)
- float x: 圆心X坐标
- float y: 圆心Y坐标
- float radius: 半径
- int[] colors: 渲染颜色数组
- floate[] positions: 相对位置数组,可为null, 若为null,可为null,颜色沿渐变线均匀分布
- Shader.TileMode tile:渲染器平铺模式
RadialGradient(float centerX, float centerY, float radius, int centerColor, int edgeColor, Shader.TileMode tileMode)
- float x: 圆心X坐标
- float y: 圆心Y坐标
- float radius: 半径
- int color0: 圆心颜色
- int color1: 圆边缘颜色
- Shader.TileMode tile:渲染器平铺模式
ComposeShader组合渲染
看它的构造方法,显然这个类的作用是Shader组合使用的方法,不过后面两个参数很陌生,后面我们再做介绍
ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode)
ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode)
BitmapShader位图渲染
可以设置图片的CLAMP(拉伸)、REPEAT(重复)、MIRROR( 镜像),关于Shader.TileMode后面也会具体介绍
BitmapShader(Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)
在自定义控件中使用着色器
/**
* 颜色渲染
* Created by ChenSS on 2016/11/24.
*/
public class RectView extends View {
public RectView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//创建线性渲染对象
int mColorLinear[] = {Color.RED, Color.GREEN, Color.BLUE, Color.WHITE};
Shader mShader = new LinearGradient(0, 0, 100, 100, mColorLinear, null, Shader.TileMode.REPEAT);
Paint paint = new Paint();
paint.setShader(mShader);
paint.setColor(Color.RED);
canvas.drawRect(60, 60, 800, 800, paint);
}
}
Shader.TileMode(平铺模式)
emun Shader.TileMode这是一个枚举类,决定了图形的平铺方式,就像我们电脑上更换桌面图片,可以选择拉伸、平铺等等。
3种模式:
static final Shader.TileMode CLAMP: 边缘拉伸。
replicate the edge color if the shader draws outside of its original bounds
static final Shader.TileMode MIRROR:横向不断翻转重复,纵向不断翻转重复,不断对称变化铺满。
repeat the shader’s image horizontally and vertically, alternating mirror images so that adjacent images always seam
Static final Shader.TillMode REPETA:在水平方向和垂直方向重复摆放,两个相邻图像间有缝隙缝隙。
repeat the shader’s image horizontally and vertically
具体的效果,这里不好展开说明,具体案例我放到了下一篇博客。自定义控件(二)
Xfermode(遮罩图层)
前面介绍了ComposeShader组合渲染,不过只列出了两个构造方法,现在开始介绍这几个陌生的家伙:PorterDuff、Xfermode、PorterDuff.Mode。
查看API你会发现Xfermode有3个子类,不过,有两个类已经过时了,而且,在Android Studio上也只能使用其中一个:PorterDuffXfermode,我就只介绍PorterDuffXfermode的使用。
Xfermode能做什么?从下面的效果图,我们可以看出,Xfermode大概就是做一些运算,给他两张图片,然后做出一个组合之后的效果。
效果图
API截图:
常量介绍:
1.PorterDuff.Mode.CLEAR
所绘制不会提交到画布上。
2.PorterDuff.Mode.SRC
显示上层绘制图片
3.PorterDuff.Mode.DST
显示下层绘制图片
4.PorterDuff.Mode.SRC_OVER
正常绘制显示,上下层绘制叠盖。
5.PorterDuff.Mode.DST_OVER
上下层都显示。下层居上显示。
6.PorterDuff.Mode.SRC_IN
取两层绘制交集。显示上层。
7.PorterDuff.Mode.DST_IN
取两层绘制交集。显示下层。
8.PorterDuff.Mode.SRC_OUT
取上层绘制非交集部分。
9.PorterDuff.Mode.DST_OUT
取下层绘制非交集部分。
10.PorterDuff.Mode.SRC_ATOP
取下层非交集部分与上层交集部分
11.PorterDuff.Mode.DST_ATOP
取上层非交集部分与下层交集部分
12.PorterDuff.Mode.XOR
异或:去除两图层交集部分
13.PorterDuff.Mode.DARKEN
取两图层全部区域,交集部分颜色加深
14.PorterDuff.Mode.LIGHTEN
取两图层全部,点亮交集部分颜色
15.PorterDuff.Mode.MULTIPLY
取两图层交集部分叠加后颜色
16.PorterDuff.Mode.SCREEN
取两图层全部区域,交集部分变为透明色
17.PorterDuff.Mode.ADD Saturate(S + D)
18.PorterDuff.Mode.OVERLAY
Xfermode测试Demo
/**
* Xfermode的使用
* Created by ChenSS on 2016/11/24.
*/
public class RectView extends View {
private int screenW, screenH;
private int width = 200;
private int height = 200;
private Paint mPaint;
private Bitmap srcBitmap, dstBitmap;
private PorterDuffXfermode xfermode;
public RectView(Context context) {
this(context, null);
}
public RectView(Context context, AttributeSet attrs) {
super(context, attrs);
//ViewHelper是我写的工具类,顶部已经给出链接
screenW = ViewHelper.getScreenW(context);
screenH = ViewHelper.getScreenH(context);
srcBitmap = makeSrc(width, height);
dstBitmap = makeDst(width, height);
//修改Mode常量,查看不同的效果
xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
mPaint = new Paint();
mPaint.setFilterBitmap(false);
mPaint.setStyle(Paint.Style.FILL);
}
@Override
protected void onDraw(Canvas canvas) {
//左边的图片
canvas.drawBitmap(srcBitmap, (screenW / 3 - width) / 2, (screenH / 2 - height) / 2, mPaint);
//中间的图片
canvas.drawBitmap(dstBitmap, (screenW / 3 - width) / 2 + screenW / 3, (screenH / 2 - height) / 2, mPaint);
//创建一个图层,关于图层的使用,结尾附上其他人的博客链接
int sc = canvas.saveLayer(0, 0, screenW, screenH, null, Canvas.MATRIX_SAVE_FLAG |
Canvas.CLIP_SAVE_FLAG |
Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
Canvas.FULL_COLOR_LAYER_SAVE_FLAG |
Canvas.CLIP_TO_LAYER_SAVE_FLAG);
//合并两个图片
canvas.drawBitmap(dstBitmap, (screenW / 3 - width) / 2 + screenW / 3 * 2, (screenH / 2 - height) / 2, mPaint);
//设置Paint的Xfermode
mPaint.setXfermode(xfermode);
canvas.drawBitmap(srcBitmap, (screenW / 3 - width) / 2 + screenW / 3 * 2, (screenH / 2 - height) / 2, mPaint);
mPaint.setXfermode(null);
// 还原画布
canvas.restoreToCount(sc);
}
/**
* 绘制一个圆形的Bitmap
*/
private Bitmap makeDst(int w, int h) {
Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bm);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
c.drawOval(new RectF(0, 0, w * 3 / 4, h * 3 / 4), paint);
return bm;
}
/**
* 绘制一个矩形的Bitmap
*/
private Bitmap makeSrc(int w, int h) {
Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bm);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.GREEN);
c.drawRect(w / 3, h / 3, w * 19 / 20, h * 19 / 20, paint);
return bm;
}
}
代码中,我将PorterDuff.Mode设置为DST_IN,他是取第二个图层(DST)的交集部分,大家可以修改Mode值,查看每个常量的效果。
ComposeShader组合渲染Demo
ComposeShader的放到这里来介绍,因为它的构造函数我们还不理解,了解了PorterDuff.Mode每一个常量的作用之后,ComposeShader的功能也比较好理解一些。
它的作用就是根据PorterDuff.Mode模式,组合使用两个Shader。
/**
* ComposeShader对象的使用
* Created by ChenSS on 2016/11/24.
*/
public class RectView extends View {
private LinearGradient mLinearGradient;
private BitmapShader mBitmapShader;
private ComposeShader mComposeShader;
private Paint mPaint;
private Bitmap mBitmap;
private int mWidth, mHeight;
public RectView(Context context) {
super(context);
//得到图像
mBitmap = ViewHelper.findBitmapById(context, R.mipmap.view_robot);
mWidth = mBitmap.getWidth();
mHeight = mBitmap.getHeight();
//创建LinearGradient对象
int mColorLinear[] = {Color.WHITE, Color.BLUE, Color.WHITE};
mLinearGradient = new LinearGradient(0, 0, mWidth, mHeight, mColorLinear, null, Shader.TileMode.REPEAT);
// 创建BitmapShader对象
mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);
// 混合渲染 将两个效果叠加,Mode取值DST_ATOP
mComposeShader = new ComposeShader(mBitmapShader, mLinearGradient, PorterDuff.Mode.DST_ATOP);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.GRAY);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制混合渲染效果
mPaint.setShader(mComposeShader);
canvas.drawRect(0, 0, mWidth, mHeight, mPaint);
}
}
Xfermode实战的初步设想:
我们先画一个圆,然后再给他一张Bitmap,然后通过Xfermode,把Bitmap圆形的部分显示出来,实现一个圆形的ImageView。
安卓自定义控件(一)就到此结束,简单地介绍了一下图形绘制、颜色的使用,下一篇进一步介绍BitmapShader位图渲染和Xfermode的实际使用,然后再想办法做出各种形状的图片。
Canvas图层也是十分重要的,这里就不具体展开,这里给出其他人博客的链接。
android canvas layer (图层)详解与进阶
安卓自定义控件(一)Canvas、Paint、Shader、Xfermode的更多相关文章
- 安卓自定义控件(三)实现自定义View
前面两篇博客,把View绘制的方法说了一下,但是,我们只在onDraw里面做文章,控件都是直接传入一个Context,还不能在布局文件里使用自定义View.这一篇博客,就不再讲绘制,在我们原先的基础上 ...
- 安卓自定义控件(二)BitmapShader、ShapeDrawable、Shape
第一篇博客中,我已经Canvas.Paint.Shader.Xfermode这些对象做了总结,而现在这篇文章主要介绍BitmapShader位图渲染,Xfermode如何实际应用,还有形状的绘制.不过 ...
- android 绘图之Canvas,Paint类
Canvas,Paint 1.在android 绘图但中经常要用到Canvas和Paint类,Canvas好比是一张画布,上面已经有你想绘制图画的轮廓了,而Paint就好比是画笔,就要给Canvas进 ...
- 改善用户体验,用图片的自身变化以及进度通知摆脱传统的进度条,okhttp,Canvas,Paint实现
转载请注明出处:王亟亟的大牛之路 从最開始的白页面等待,到后来的进度条告知用户.到如今的WebBO/微信这样的先下缩略图点击才又一次下大图的方式,我们开发人员对用户感知的注意度越来越高.昨天刷微博的时 ...
- 图形绘制 Canvas Paint Path 详解
图形绘制简介 Android中使用图形处理引擎,2D部分是android SDK内部自己提供,3D部分是用Open GL ES 1.0.大部分2D使用的api都在android.grap ...
- 自定义控件之Canvas图形绘制基础练习-青春痘笑脸^_^
对于自定义控件的意义不言而喻,所以对它的深入研究是很有必要的,前些年写过几篇关于UI效果的学习过程,但是中途比较懒一直就停滞了,而对于实际工作还是面试来说系统深入的了解自定义控件那是很有必要的,所以接 ...
- Camera图片特效处理综述(Bitmap的Pixels处理、Canvas/paint的drawBitmap处理、旋转图片、裁截图片、播放幻灯片浏览图片<线程固定时间显示一张>)
一种是直接对Bitmap的像素进行操作,如:叠加.边框.怀旧.(高斯)模糊.锐化(拉普拉斯变换). Bitmap.getPixels(srcPixels, 0, width, 0, 0, width, ...
- 自定义控件之canvas变换和裁剪
1.平移 //构造两个画笔,一个红色,一个绿色 Paint paint_green = generatePaint(Color.GREEN, Paint.Style.STROKE, 3); Paint ...
- 安卓自定义控件(五)触控基础MotionEvent
之前去面试,人家说,我这个事件拦截机制写得太少了,还有一个MotionEvent没写,这个确实也很重要,后来我考虑了一下,决定将这篇文章放到自己定义控件里. 先简单再提一下事件分发,事件分发和拦截主要 ...
随机推荐
- UWP 绘制图形
UWP图形和wpf变化不多 一般用到有椭圆.长方形.多边形.线 不过如果用的好,可以做出很漂亮的界面 一般使用画图都是使用Shape 类,Shape 类具有一个与其关联的画笔并可以呈现到屏幕,包括 L ...
- 用BroadcastReceiver监听手机网络状态变化
android--解决方案--用BroadcastReceiver监听手机网络状态变化 标签: android网络状态监听方案 2015-01-20 15:23 1294人阅读 评论(3) 收藏 举报 ...
- 基于Lua脚本解决实时数据处理流程中的关键问题
摘要 在处理实时数据的过程中需要缓存的参与,由于在更新实时数据时并发处理的特点,因此在更新实时数据时经常产生新老数据相互覆盖的情况,针对这个情况调查了Redis事务和Lua脚本后,发现Redis事务并 ...
- HDU 6047 Maximum Sequence
Maximum Sequence Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) ...
- Intervals
Intervals Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Su ...
- 【转载】以Java的视角来聊聊SQL注入
以Java的视角来聊聊SQL注入 原创 2017-08-08 javatiku Java面试那些事儿 在大二就接触过sql注入,之前一直在学习windows逆向技术,认为web安全以后不是自己的从业方 ...
- Formatting the event object
尽量将IE与DOM函数事件对象不同的性质或方法转成DOM标准 EventUtil.formatEvent = function (oEvent) { if (isIE && ...
- scrollWidth,clientWidth,offsetWiddth,innerWinth 元素定位
getBoundingClientRect()方法.它返回一个对象,其中包含了left.right.top.bottom四个属性,分别对应了该元素的左上角和右下角相对于浏览器窗口(viewport)左 ...
- jQuery选择器(内容过滤选择器)第四节
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/stri ...
- [译]移动API安全终极指南
文章主要讲了移动api调用的授权和验证问题,原文链接:The Ultimate Guide to Mobile API Security 移动API的使用是Stack Overflow和 Stormp ...