- top
- right
- bottom
在left、top、right、bottom围成的区域内绘制一个椭圆。
- drawOval(RectF oval, Paint paint)
- 将第一个重载方法的left、top、right、bottom封装到RectF类中,与扇形的重载方法异曲同工。
RectF rectF2 = new RectF(300, 600, 700, 800); // 创建一个RectF
canvas.drawOval(rectF2, mPaint);
效果如下图:
####1.7 绘制矩形
- drawRect(float left, float top, float right, float bottom, Paint paint)
- drawRect(Rect r, Paint paint)
- drawRect(RectF rect, Paint paint)
drawRect的参数非常好理解,这里就不啰嗦了,直接上代码看效果:
canvas.drawRect(rectF2, mPaint);
注:这里的rectF2即上文绘制椭圆时创建的RectF对象。
####1.8 绘制圆角矩形
- drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint)
- drawRoundRect(RectF rect, float rx, float ry, Paint paint)
drawRoundRect是绘制圆角矩形,用法和drawRect类似,唯一不同的是多了两个参数:
- rx:x轴方向的圆角弧度
- ry:y轴方向的圆角弧度
上代码,看效果:
canvas.drawRoundRect(rectF2, 60, 30, mPaint);
这里为了突出两个方向的圆角弧度,特地将rx和ry设置差距比较大,效果如下图:
####1.9 绘制直线
- drawLine(float startX, float startY, float stopX, float stopY, Paint paint)
- drawLines(float[] pts, int offset, int count, Paint paint)
- drawLines(float[] pts, Paint paint)
drawLine和drawLines一个是绘制一个点,一个是绘制一组点,其中drawLines中的float数组中四个值为一组点,其用法可以参照drawPoints。
canvas.drawLine(100, 820, 800, 820, mPaint);
float[] lines = new float[]{
100f, 850f, 800f, 850f,
100f, 900f, 800f, 900f,
100f, 950f, 800f, 950f
};
canvas.drawLines(lines, mPaint); // 按floats数组中,四个数为1组,绘制多条线
效果如下图:
####1.10 drawPath() 绘制不规则图形
上面的这些Canvas方法固然已经很强大了,但是我们如果想要绘制一些不规则的图形怎么办,这时候就要用到强大的drawPath()方法了,通过对Path进行设置不同的坐标、添加不同图形,最后传入drawPath方法中可以绘制出复杂的且不规则的形状。以下是drawPath的方法及参数:
- drawPath(Path path, Paint paint)
这里的关键参数就是Path,Path类的方法较多,大部分用法类似,这里挑几个说一下:
- Path类
- addArc(RectF oval, float startAngle, float sweepAngle) 往path里面添加一个圆弧
- addCircle(float x, float y, float radius, Path.Direction dir) 添加一个圆形
- addOval(RectF oval, Path.Direction dir) 添加一个椭圆
- addRect(RectF rect, Path.Direction dir) 添加一个矩形
- lineTo(float x, float y) 连线到坐标(x,y)
- moveTo(float x, float y) 将path绘制点移动到坐标(x,y)
- close() 用直线闭合图形,调用此方法后,path会将最后一处点与起始用直线连接起来,path起始点为moveTo()方法的坐标上,如果没有调用moveTo()起始点将默认为(0,0)坐标。
- …
接下来使用drawPath绘制一个楼梯:
// 使用 Path 绘制一个楼梯
Path path = new Path();
path.lineTo(0, 1000);
path.lineTo(100, 1000);
path.lineTo(100, 1100);
path.lineTo(200, 1100);
path.lineTo(200, 1200);
path.lineTo(300, 1200);
path.lineTo(300, 1300);
path.lineTo(400, 1300);
path.lineTo(400, 1400);
path.lineTo(0, 1400);
path.lineTo(0, 1000);
path.close();
canvas.drawPath(path, mPaint);
效果如下图:
再用drawPath方法绘制一个Android小机器人:
/ 使用 Path 绘制一个Android机器人
// 绘制两个触角
path.reset();
path.moveTo(625, 1050);
path.lineTo(650, 1120);
path.moveTo(775, 1050);
path.lineTo(750, 1120);
path.addArc(new RectF(600, 1100, 800, 1300), 180, 180); // 绘制头部
path.addCircle(666.66f, 1150, 10, Path.Direction.CW); // 绘制眼睛,CW:顺时针绘制, CCW:逆时针绘制
path.addCircle(733.33f, 1150, 10, Path.Direction.CW);
path.addRect(new RectF(600, 1200, 800, 1300), Path.Direction.CW); // 身体
canvas.drawPath(path, mPaint);
效果图如下:
最后,上文中Canvas示例的全部代码如下:
public class StudyView extends View {
private Paint mPaint;
private Context mContext;
public StudyView(Context context) {
super(context);
init(context);
}
public StudyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
mContext = context;
mPaint = new Paint();
mPaint.setAntiAlias(true); // 消除锯齿
mPaint.setStrokeWidth(5); // 设置笔尖宽度
mPaint.setStyle(Paint.Style.STROKE); // 不填充
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/** 1、drawArc */
// 绘制扇形
canvas.drawArc(0, 0, 200, 200, 0, 90, true, mPaint);
RectF rectF = new RectF(0, 0, 200, 200);
canvas.drawArc(rectF, 180, 90, true, mPaint);
// 绘制圆弧
canvas.drawArc(300, 0, 500, 200, 0, 90, false, mPaint);
RectF rectF1 = new RectF(300, 0, 500, 200);
canvas.drawArc(rectF1, 180, 90, false, mPaint);
/** 2、drawBitmap */
Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), android.R.mipmap.sym_def_app_icon);
// 绘制图片
canvas.drawBitmap(bitmap, 0, 300, null);
// 将图片拉伸平铺在RectF矩形内
canvas.drawBitmap(bitmap, null, new RectF(200, 300, 500, 500), null);
// 截取图片的四分之一拉伸平铺在RectF矩形内
canvas.drawBitmap(bitmap, new Rect(0, 0, bitmap.getWidth()/2, bitmap.getHeight()/2), new RectF(500, 300, 800, 500), null);
Matrix matrix = new Matrix();
matrix.postTranslate(800, 300); // 将bitmap平移到此位置
canvas.drawBitmap(bitmap, matrix, mPaint);
// 为防止oom,及时回收bitmap
bitmap.recycle();
/** 3、drawCircle */
canvas.drawCircle(100, 700, 100, mPaint);
/** 4、绘制一个点 */
canvas.drawPoint(100, 700, mPaint); // 绘制一个点
float[] points = new float[] {
130, 700,
160, 700,
190, 700,
210, 700,
240, 700
};
canvas.drawPoints(points, 2, 4, mPaint); // 绘制一组点(代表跳过前两个值,处理4个值,也就是实际绘制2个点)
RectF rectF2 = new RectF(300, 600, 700, 800); // 创建一个RectF
/** 5、drawOval 绘制椭圆 */
canvas.drawOval(rectF2, mPaint);
/** 6、drawRect 绘制矩形*/
canvas.drawRect(rectF2, mPaint);
canvas.drawRoundRect(rectF2, 60, 30, mPaint);
/** 7、drawLine */
canvas.drawLine(100, 820, 800, 820, mPaint);
float[] lines = new float[]{
100f, 850f, 800f, 850f,
100f, 900f, 800f, 900f,
100f, 950f, 800f, 950f
};
canvas.drawLines(lines, mPaint); // 按floats数组中,四个数为1组,绘制多条线
/** 8、drawPath */
// 使用 Path 绘制一个楼梯
Path path = new Path();
path.moveTo(0, 1000);
path.lineTo(100, 1000);
path.lineTo(100, 1100);
path.lineTo(200, 1100);
path.lineTo(200, 1200);
path.lineTo(300, 1200);
path.lineTo(300, 1300);
path.lineTo(400, 1300);
path.lineTo(400, 1400);
path.close();
canvas.drawPath(path, mPaint);
// 使用 Path 绘制一个Android机器人
// 绘制两个触角
path.reset();
path.moveTo(625, 1050);
path.lineTo(650, 1120);
path.moveTo(775, 1050);
path.lineTo(750, 1120);
path.addArc(new RectF(600, 1100, 800, 1300), 180, 180); // 绘制头部
path.addCircle(666.66f, 1150, 10, Path.Direction.CW); // 绘制眼睛,CW:顺时针绘制, CCW:逆时针绘制
path.addCircle(733.33f, 1150, 10, Path.Direction.CW);
path.addRect(new RectF(600, 1200, 800, 1300), Path.Direction.CW); // 身体
canvas.drawPath(path, mPaint);
}
}
完整效果图如下:
其实Canvas除了可以绘制图形之外,还可以绘制文字,Canvas的绘制文字的方法有drawText()、drawTextOnPath()、drawTextRun()等方法,在绘制文字是和Paint的结合更为紧密,所以讲绘制文字的方法放在下文和Paint一起讲可能效果会更好一些,好了,废话不多说了,接下来咱们就开始Paint的篇章。
#二、 Paint
为了更为清晰的讲解Paint的用法,先来新建一个自定义类,暂叫PaintStudyView,接下来创建一个它的大体骨架,在此类中定义了一些变量,变量的意义请见注释:
public class PaintStudyView extends View {
private Paint mTextPaint; // 绘制文字的Paint
private Paint mPointPaint; // 绘制参考点的Paint
private Context mContext;
private final static float Y_SPACE = 100; // y轴方向的间距
public PaintStudyView(Context context) {
super(context);
init(context);
}
public PaintStudyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
mContext = context;
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true); // 消除锯齿
mTextPaint.setStrokeWidth(1); // 设置笔尖宽度
mTextPaint.setStyle(Paint.Style.FILL); // 填充
mTextPaint.setTextSize(30);
mPointPaint = new Paint();
mPointPaint.setAntiAlias(true);
mPointPaint.setStrokeWidth(5);
mPointPaint.setColor(Color.RED); // 将参考点的Paint设置为红色
mPointPaint.setStyle(Paint.Style.STROKE);// 不填充
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
}
####2.1 Canvas的绘制文字的相关方法:
2.1.1drawText()的重载方法
drawText() 是Canvas的绘制文字中的最长用的方法,它只能按照从左至右的普通方式来绘制文字。
- drawText(String text, float x, float y, Paint paint)
- text:待绘制的文字内容
- x:文字绘制位置的x坐标
- y:文字绘制位置的y坐标
- paint:Paint画笔,可以通过Paint.setTextAlign()来决定文字的方位,有:Paint.Align.LEFT(居左),Paint.Align.RIGHT(居右),Paint.Align.CENTER(居中)三个位置。
- drawText(String text, int start, int end, float x, float y, Paint paint)
- start:代表从text中的第几个字符开始截取绘制,包含第start个字符。
- end:代表截取到text的第几个字符,不包含第end个字符。
例如:我是一个自定义View的控件,start=1,end=6,截取后为:是一个自定
下面两个重载方法可以参考第二个很容易就能理解:
- drawText(CharSequence text, int start, int end, float x, float y, Paint paint)
- drawText(char[] text, int index, int count, float x, float y, Paint paint)
以下示例说明了文字的不同位置,同时也说明了第二个和第四个重载方法对字符串截取时的用法:
String str = “我是一个自定义View的控件”;// 待绘制文字
float x = getWidth() / 2;
float y = 100;
canvas.drawPoint(x, y, mPointPaint); // 绘制参考点,便于观察文字处于x,y坐标的位置,从而来学习setTextAlign()方法
mTextPaint.setTextAlign(Paint.Align.LEFT);
canvas.drawText(str, x, y, mTextPaint);
y += Y_SPACE;
canvas.drawPoint(x, y, mPointPaint);
mTextPaint.setTextAlign(Paint.Align.RIGHT);
canvas.drawText(str, 0, 6, x, y, mTextPaint);
y += Y_SPACE;
canvas.drawPoint(x, y, mPointPaint);
mTextPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(str.toCharArray(), 1, 6, x, y, mTextPaint);
效果图如下:
其中的红点为额外添加的参考坐标,目的是为了突出setTextAlign中参数的位置。
2.1.2drawTextOnPath()的重载方法
drawTextOnPath() 由方法名字我们就可以看出来他可以按照Path的走向来绘制文字,例如我们在path中传入一个圆弧,那么绘制出来的文字走向就是圆弧状的,是不是很酷,来看一下它的重载方法:
- drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint)
- text:同drawText的第一个参数。
- path:Path参数,用法在前文已经说过了。
- hOffset:水平方向的偏移量。
- vOffset:垂直方向的偏移量。
关键点:有一点一定要提的就是,这里的hOffset是相对于path路径的水平偏移量,而vOffset也是相对于path路径的垂直偏移量,这么说可能还有点不清楚,结合下面的示例来说明,请仔细体会这里的意思:
// 1、下开口圆弧方向绘制文字
mTextPaint.setTextAlign(Paint.Align.LEFT);
y += Y_SPACE;
Path path = new Path();
path.addArc(new RectF(x - 150, y, x + 150, y + 300), 180,180);
canvas.drawPath(path, mPointPaint); // 参考弧度线
canvas.drawTextOnPath(str, path, 0, 0, mTextPaint); // 按照path路径绘制文字,不偏移
canvas.drawTextOnPath(str, path, 30, 30, mTextPaint);// 向水平、垂直方向各偏移30
canvas.drawTextOnPath(str, path, 60, 60, mTextPaint);// 向水平、垂直方向各偏移60
// 2、上开口圆弧方向绘制文字
path.reset();
y += Y_SPACE;
path.addArc(new RectF(x - 150, y, x + 150, y + 300), 0, 180);
canvas.drawPath(path, mPointPaint); // 参考弧度线
canvas.drawTextOnPath(str, path, 0, 0, mTextPaint);
canvas.drawTextOnPath(str, path, 30, 30, mTextPaint);
canvas.drawTextOnPath(str, path, 60, 60, mTextPaint);
path.close();
// 3、竖直方向绘制文字
path.reset();
path.moveTo(200, y);
path.lineTo(200, y + 4 * Y_SPACE);
canvas.drawPath(path, mPointPaint); // 参考弧度线
canvas.drawTextOnPath(str, path, 0, 0, mTextPaint);
canvas.drawTextOnPath(str, path, 30, 60, mTextPaint);
y += Y_SPACE;
y += Y_SPACE;
y += Y_SPACE;
y += Y_SPACE;
// 4、水平方向绘制文字
path.reset();
path.moveTo(x, y);
path.lineTo(x + 4 * Y_SPACE, y);
canvas.drawPath(path, mPointPaint); // 参考弧度线
canvas.drawTextOnPath(str, path, 0, 0, mTextPaint);
canvas.drawTextOnPath(str, path, 30, 60, mTextPaint);
如下是效果图,注意看图片中的红色部分,红色的线是用代码绘制出来的path参考线,红色的箭头是path的水平和垂直方向的走向,结合下图可以更好的理解drawTextOnPath的hOffset和vOffset参数。
- drawTextOnPath(char[] text, int index, int count, Path path, float hOffset, float vOffset, Paint paint)
这个方法的套路想必不用解释了。
2.1.3drawTextRun()的重载方法
- drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount, float x, float y, boolean isRtl, Paint paint)
- drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint paint)
drawTextRun()可以文字的是从左到右还是从右到左的顺序来绘制,其中倒数第二个参数isRtl就是用来控制方向的,true就是倒序绘制,false就是正序绘制,其他的参数就没啥好说的了,这个方法用法比较简单,这里就不贴代码了。另外这个方法是在API 23才开始添加的,使用时要注意。
到目前为止,Canvas的常用用法基本介绍完了,接下来就可以着重来看Paint的使用了,Paint和Canvas两者是不可分离的,两者协作,相辅相成。所以在下面的用法示例中不免要用到Canvas的相关方法。
###2.2 使用Paint测量文字的尺寸,定位文字
我们在开发自定义控件时,免不了要精确定位文字的文字,例如必须把文字放在某个区域的正中间,或者必须让一行文字的几何中心精确的处于某个点上,这时我们如果不懂这里的窍门可能就要盲目的试位置了,这样一点一点试出来的位置很不可靠,可能换个屏幕尺寸位置就不对了,接下来怎么来看看怎么样用最优雅的姿势来精确的定位文字。
其实在水平方向的定位还比较好说,直接使用Paint.setTextAlign()就能搞定大多需求,主要是在水平方向上稍稍复杂一点,想要定位位置,首先需要先获取文字的高度,要用到Paint的以下两个方法:
- float ascent():根据文字大小获取文字顶端到文字基线的距离(返回的是负值)
- float descent():根据文字大小获取文字底部到文字基线的距离(返回的事正值)
有了这两个方法那就非常好办了,首先用代码结合效果图说明一下基线、ascent、descent和文字的关系:
y += Y_SPACE;
canvas.drawPoint(x, y, mPointPaint);
canvas.drawLine(x - 300, y, x+300, y, mPointPaint);
mTextPaint.setTextAlign(Paint.Align.CENTER);// 水平方向上让文字居中
float ascent = mTextPaint.ascent(); // 根据文字大小获取文字顶端到文字基线的距离(返回的是负值)
float descent = mTextPaint.descent(); // 根据文字大小获取文字底部到文字基线的距离(返回的事正值)
canvas.drawLine(x - 300, y + ascent, x+300, y + ascent, mPointPaint);
canvas.drawLine(x - 300, y + descent, x+300, y + descent, mPointPaint);
canvas.drawText(str, x, y, mTextPaint);
效果图如下,它们之间的关系注意看图片里面的说明。(注:在这里感谢园友在截图中指出的一处错误,现已修正)
接下来就让文字的中心落在参考点上:
// 将文字的中心定位在参考点上
y += Y_SPACE;
canvas.drawPoint(x, y, mPointPaint);
canvas.drawText(str, x, y - ascent / 2 - descent / 2, mTextPaint);
效果图如下,仔细看参考点(红点)和文字的位置:
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上我搜集整理的2019-2021BATJ 面试真题解析,我把大厂面试中常被问到的技术点整理成了PDF,包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
《960全网最全Android开发笔记》
《379页Android开发面试宝典》
历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
腾讯、字节跳动、阿里、百度等BAT大厂 2019-2021面试真题解析
资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数
[外链图片转存中…(img-cD5RONEQ-1715434097718)]
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
[外链图片转存中…(img-sTVwqpTG-1715434097719)]
腾讯、字节跳动、阿里、百度等BAT大厂 2019-2021面试真题解析
[外链图片转存中…(img-UKauZZZU-1715434097719)]
资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!