高级UI晋升之自定义View实战(五)
更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680
本篇文章将从自定义View利器Canvas和Paint来进行详解
一、Canvas
为了后文更为方便的讲解Canvas的常用方法的使用,我们先来做一些准备工作,创建一个自定义View框架,先初始化一下Paint画笔,并设置相关方法:
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.1 绘制圆弧和扇形
Canvas提供drawArc()方法,通过传递不同的参数可用来绘制圆弧和扇形,此方法有两个重载方法,详细参数如下:
- drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
- left:扇形或圆弧所占区域的左边界线x坐标
- top:扇形或圆弧所占区域的上边界线y坐标
- right:右边界线x坐标
- bottom:下边界线y坐标
- startAngle:扇形或圆弧的起始角度
- sweepAngle:扫过的角度
- userCenter:此参数可以理解为true就是画扇形,false就是画圆弧
- paint:画笔
- drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
此方法第一个参数是一个RectF类,也是边界,就是把一个方法的left,top,right,bottom封装到了RectF类中,剩余参数与上一个方法一致。
接下来用着两个重载方法分别绘制两个90°的扇形和两个90°的圆弧:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制扇形
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);
}
绘制效果如下图所示,另外需要说明的一点是,drawArc的第五个参数startAngle中的角度,0°是指坐标系中第四象限中与x重合的角度,顺时针方向代表角度增大的方向,如下图中红色线条所示。
1.2 绘制Bitmap
在Canvas中提供了drawBitmap方法,此方法可以让我们直接获取一张图片绘制到画布上,有了它可以让我们的自定义View锦上添花,同时也让我们实现一些复杂效果有了一个更加方便的途径。下面是drawBitmap的几个比较常用的重载方法:
- drawBitmap(Bitmap bitmap, float left, float top, Paint paint)
- bitmap:Bitmap资源文件
- left和top:代表了图片左上角落入的位置坐标。
- top:看2
- paint:画笔
- drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
- src:在Bitmap图片上截取一部分作为绘制源,可null
- det:将绘制目标拉伸平铺到det指定的矩形中
drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)
同第二个重载方法,几乎一毛一样。drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)
- matrix:Matrix的参数传入是的drawBitmap功能变得异常强大,让此方法有意思了许多,通过matrix可以实现图片的平移(postTranslate())、缩放(postScale())、旋转(postRotate())、错切(postSkew())等等花式炫酷效果,由于Matrix的用法稍微多一些,篇幅限制,这里就先一带而过了,感兴趣的朋友可以自行探索。
在onDraw方法中drawBitmap的以上重载方法,注意在使用完Bitmap之后记得用Bitmap.recycle()来回收掉资源,以防止oom。
/** 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();
效果如下图(红框内)。
1.4 绘制圆形
- drawCircle(float cx, float cy, float radius, Paint paint)
- cx:圆心x坐标
- cy:圆心y坐标
- radius:半径
canvas.drawCircle(100, 700, 100, mPaint);
效果如下图:
1.5 绘制点
- drawPoint(float x, float y, Paint paint)
- x:点的x坐标
- y:点的y坐标
- drawPoints(float[] pts, Paint paint) 绘制一组点
- pts:float数组,两位为一组,两两结合代表x、y坐标,例如:pts[0]、pts[1]代表第一个点的x、y坐标,pts[2]、pts[3]代表第二个点的x、y坐标,依次类推。
- drawPoints(float[] pts, int offset, int count, Paint paint) 绘制一组点
- pts:float数组,两位为一组,两两结合代表x、y坐标,例如:pts[0]、pts[1]代表第一个点的x、y坐标,pts[2]、pts[3]代表第二个点的x、y坐标,依次类推。
- offset:代表数组开始跳过几个只开始绘制点,注意这里不是指数组的下标,而是代表跳过几个值。
- count:在跳过offset个值后,处理几个值,注意这里的count不是代表点的个数,而是代表数组中值的个数。
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个点)
效果如下图:
1.6 绘制椭圆
- drawOval(float left, float top, float right, float bottom, Paint paint)
- left
- 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);
效果图如下,仔细看参考点(红点)和文字的位置:
2.3利用Paint.setShader()(着色器)绘制渐变色
使用 setShader() 方法可以添加渐变颜色也可以使用图片作为背景,其参数是一个Shader类,传入不同的Shader子类可以实现不同的渐变效果或者添加背景图片,其子类有一下几种:
- LinearGradient:线性渐变
- RadialGradient:放射状渐变
- SweepGradient:扫描渐变
- BitmapShader:添加背景图片
- ComposeShader:多种Shader组合
上面接个Shader的子类在使用方式上都差不多,这里只用LinearGradient为例说明一下,并注意对LinearGradient构造器的最后一个参数传入不同的参数对应的效果图:
/* Shader 渐变 */
y = 100;
Shader shader = new LinearGradient(x - 50, y - 80, x + 50, y + 80,
Color.parseColor("#FFCCBB"), Color.parseColor("#FF0000"), Shader.TileMode.CLAMP);
mTextPaint.setShader(shader);
canvas.drawRect(x - 500, y - 80, x + 500, y + 80, mTextPaint);
y += 3 * Y_SPACE;
Shader shader1 = new LinearGradient(x - 50, y - 80, x + 50, y + 80,
Color.parseColor("#FFCCBB"), Color.parseColor("#FF0000"), Shader.TileMode.REPEAT);
mTextPaint.setShader(shader1);
canvas.drawRect(x - 500, y - 80, x + 500, y + 80, mTextPaint);
y += 3 * Y_SPACE;
Shader shader2 = new LinearGradient(x - 50, y - 80, x + 50, y + 80,
Color.parseColor("#FFCCBB"), Color.parseColor("#FF0000"), Shader.TileMode.MIRROR);
mTextPaint.setShader(shader2);
canvas.drawRect(x - 500, y - 80, x + 500, y + 80, mTextPaint);
效果图如下:
除了以上这些,Paint的用法还有很多很多,一时半会也列不完,博主在这也只能抛砖引玉,感兴趣的朋友可查看官方API自行探索。
更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680
原文链接 https://www.cnblogs.com/codingblock/p/8227598.html
高级UI晋升之自定义View实战(五)的更多相关文章
- 高级UI晋升之自定义View实战(六)
更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680本篇文章将从Android 自定义属性动画&Camera动画来介绍自定义V ...
- 高级UI晋升之自定义View实战(九)
更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680 1.前言: 本文采用自定义view的方法来实现一键清除的动画这个功能. 2.效果 ...
- 高级UI晋升之自定义view实战(七)
更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680本篇文章自定义ViewGroup实现瀑布流效果来进行详解dispatchTouch ...
- 高级UI晋升之自定义View实战(八)
更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680本篇文章自定义流式布局来进行介绍: 一般常见的流式布局由两种,一种是横向的个数固定 ...
- 高级UI晋升之常用View(三)中篇
更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680本篇文章将从ViewPager来介绍常用View:文章目录 一.简介 二.基本使用 ...
- 高级UI晋升之常用View(三)上篇
更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680本篇文章将先从以下两个内容来介绍常用View: [RecycleView] [Ca ...
- 高级UI晋升之常用View(三)下篇
更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680本篇文章将从WebView来介绍常用View: 一.WebView介绍 Andro ...
- 高级UI晋升之View渲染机制(二)
更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680 优化性能一般从渲染,运算与内存,电量三个方面进行,今天开始说聊一聊Android ...
- Android 自定义View (五)——实践
前言: 前面已经介绍了<Android 自定义 view(四)-- onMeasure 方法理解>,那么这次我们就来小实践下吧 任务: 公司现有两个任务需要我完成 (1)监测液化天然气液压 ...
随机推荐
- [LeetCode] 196.删除重复的电子邮箱
编写一个 SQL 查询,来删除 Person 表中所有重复的电子邮箱,重复的邮箱里只保留 Id 最小 的那个. +----+------------------+ | Id | Email | +-- ...
- 简述ArcGIS的空间连接(Spatial Join)与字段映射(Field Map)操作
插个广告,制作ArcGIS的Tool工具学习下面的教程就对了:零基础学习Python制作ArcGIS自定义工具 牢骚一下 在使用ArcMap进行空间连接操作的时候,往往会有两种特殊需求,其一是连接重叠 ...
- mySQL查看存储过程、函数、视图、触发器
一.查看存储过程 1.show procedure status; //查看所有的 2.show create procedure proc_AllUser[proc_name]; 查看proc_Al ...
- Java Web servlet中的cookie
点击submit后: 点击查看Cookies: 在C:\Documents and Settings\Administrator\Cookies目录下面会有一个 hongten@webproj ...
- columns样式属性使用
columns样式属性使用 columns:用于设置元素的列宽和列数.它是column-width和column-count的简写属性. 语法: columns: <'column-width' ...
- js运算符的优先级的顺序列表
优先级权重 运算符 17 ..[].new 16 () 15 ++.-- 14 !.~.+(单目).-(单目).typeof.void.delete 13 %.*./ 12 +(双目).-(双目) 1 ...
- flex 的经典用法
Document 11 21 31 41 51 61 71 81 91 101 111 121 131 141 151 161 171 181 191 201 211 221 231 241 25 ...
- 2018-8-10-WPF-判断USB插拔
title author date CreateTime categories WPF 判断USB插拔 lindexi 2018-08-10 19:16:53 +0800 2018-8-5 13:0: ...
- go语言从例子开始之Example17.指针
Go 支持 指针,允许在程序中通过引用传递值或者数据结构 Example: package main import "fmt" func zeroval(ival int){ iv ...
- OAuth_3
端点: 授权断点.令牌断点.重定向端点 除了重定向端点在客户端应用上,其他在服务器端 授权端点: 资源拥有者所登录的授权服务器,并授权给客户端应用的端点 令牌端点: 授权服务器上为了一个访问令牌,客户 ...