《Android自定义控件入门到精通》文章索引 ☞ https://blog.csdn.net/Jhone_csdn/article/details/118146683
《Android自定义控件入门到精通》所有源码 ☞ https://gitee.com/zengjiangwen/Code
文章目录
Paint基础知识
paint画笔,跟我们Ps中的画笔有点类似,比如Ps中的画笔工具和图案图章工具。
常用基础函数
我们先来系统的了解下Paint的一些基础的函数:
mPaint.setColor(Color.RED);//设置画笔颜色
mPaint.setTextAlign(Paint.Align.CENTER);//设置文本与基点的对其方式
mPaint.setAntiAlias(true);//设置是否抗锯齿
mPaint.setFlags(Paint.ANTI_ALIAS_FLAG);//添加画笔特性标识
mPaint.setDither(true);//设置是否防抖动
mPaint.setAlpha(100);//设置画笔的透明度0~255
mPaint.setARGB(55,100,100,100);//设置画笔颜色和透明度
mPaint.reset();//恢复画笔到默认状态
mPaint.getFontMetrics();//可以获取到ascent descent top bottom值
mPaint.ascent();//ascent值
mPaint.descent();//descent值
mPaint.setTextSize(24);//字体大小,通常会做sp适配
mPaint.setLetterSpacing(0.3f);//字间距,默认是0,Typical values for slight 0.05
mPaint.measureText("test");//获取文本的宽度
mPaint.setUnderlineText(true);//是否添加下划线
mPaint.setStrikeThruText(true);//是否添加删除线
mPaint.setHinting(Paint.HINTING_OFF);//设置画笔的Hint模式(没看出有啥效果)
mPaint.setFakeBoldText(true);//设置伪粗体,有些字体是没有粗体的,可以用伪粗体模拟粗体字的效果
mPaint.setTextScaleX(2f);//放大缩小文本
mPaint.setTextSkewX(2f);//倾斜文本,一般取值-0.25
mPaint.setTextLocale(Locale.CHINA);//指定语言
mPaint.getTextBounds("test",0,3,rect);//获取文本的边界
这些方法我们前面可能接触过一些,没啥难度,看注释应该就能很好的理解了,主要讲下下面这几个基础方法
mPaint.setStyle(Paint.Style.FILL);//描边,填充,描边和填充
mPaint.setStrokeWidth(10);//设置线宽,当Paint.Style.STROKE时有效
mPaint.setStrokeCap(Paint.Cap.ROUND);//设置线帽样式
mPaint.setShadowLayer(0, 0, 0, 0);//设置阴影效果
mPaint.clearShadowLayer();//清除阴影效果
mPaint.breakText();//测量
mPaint.setTypeface();//设置字体
mPaint.setStrokeJoin();//设置转折样式
mPaint.setMaskFilter();//设置滤镜
Paint.setStyle
设置画笔的样式,描边、填充、描边并填充
@Override
protected void onDraw(Canvas canvas) {
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(10);
mPaint.setColor(Color.RED);
canvas.drawRect(new Rect(40,20,100,60),mPaint);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(2);
mPaint.setColor(Color.GREEN);
canvas.drawRect(new Rect(40,20,100,60),mPaint);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawText("Paint.Style.STROKE",160,40,mPaint);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(10);
mPaint.setColor(Color.RED);
canvas.drawRect(new Rect(40,120,100,160),mPaint);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(2);
mPaint.setColor(Color.GREEN);
canvas.drawRect(new Rect(40,120,100,160),mPaint);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawText("Paint.Style.FILL",160,140,mPaint);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setStrokeWidth(10);
mPaint.setColor(Color.RED);
canvas.drawRect(new Rect(40,220,100,260),mPaint);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(2);
mPaint.setColor(Color.GREEN);
canvas.drawRect(new Rect(40,220,100,260),mPaint);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawText("Paint.Style.FILL_AND_STROKE",160,240,mPaint);
}
Paint.setStrokeCap
设置线帽(两端)样式
@Override
protected void onDraw(Canvas canvas) {
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(20);
mPaint.setColor(Color.RED);
mPaint.setStrokeCap(Paint.Cap.ROUND);
canvas.drawLine(40,40,160,40,mPaint);
mPaint.setStrokeCap(Paint.Cap.SQUARE);
canvas.drawLine(40,140,160,140,mPaint);
mPaint.setStrokeCap(Paint.Cap.BUTT);
canvas.drawLine(40,240,160,240,mPaint);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawText("Paint.Cap.ROUND",200,50, mPaint);
canvas.drawText("Paint.Cap.SQUARE",200,150, mPaint);
canvas.drawText("Paint.Cap.BUTT",200,250, mPaint);
mPaint.setColor(Color.GREEN);
mPaint.setStrokeWidth(2);
canvas.drawLine(40,30,40,250,mPaint);
canvas.drawLine(160,30,160,250,mPaint);
}
Paint.setTypeface
设置字体样式
Typeface构造方式:
- Typeface create(String familyName, int style) //直接通过指定字体名来加载系统中自带的文字样式
- Typeface create(Typeface family, int style) //通过其它Typeface变量来构建文字样式
- Typeface createFromAsset(AssetManager mgr, String path) //通过从Asset中获取外部字体来显示字体样式
- Typeface createFromFile(String path)//直接从路径创建
- Typeface createFromFile(File path)//从外部路径来创建字体样式
- Typeface defaultFromStyle(int style)//创建默认字体
自带的字体(Typeface)
/** The default NORMAL typeface object */
public static final Typeface DEFAULT;
/**
* The default BOLD typeface object. Note: this may be not actually be
* bold, depending on what fonts are installed. Call getStyle() to know
* for sure.
*/
public static final Typeface DEFAULT_BOLD;
/** The NORMAL style of the default sans serif typeface. */
public static final Typeface SANS_SERIF;
/** The NORMAL style of the default serif typeface. */
public static final Typeface SERIF;
/** The NORMAL style of the default monospace typeface. */
public static final Typeface MONOSPACE;
Style(字体样式)的枚举值如下:
// Style
public static final int NORMAL = 0;//正常体
public static final int BOLD = 1;//粗体
public static final int ITALIC = 2; //斜体
public static final int BOLD_ITALIC = 3; //粗斜体
示例:
@Override
protected void onDraw(Canvas canvas) {
mPaint.setColor(Color.WHITE);
mPaint.setTextSize(30);
mPaint.setStyle(Paint.Style.FILL);
String text="Android字体演示";
int destant=50;
int position=100;
AssetManager mgr=getContext().getAssets();
Typeface face0= Typeface.createFromAsset(mgr, "jzk.ttf");//简中楷字体
mPaint.setTypeface(face0);
canvas.drawText(text,50,position, mPaint);
Typeface face1 = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL);
mPaint.setTypeface(face1);
canvas.drawText(text,50,position+destant, mPaint);
Typeface face2 = Typeface.create(Typeface.DEFAULT_BOLD, Typeface.BOLD);
mPaint.setTypeface(face2);
canvas.drawText(text,50,position+destant*2, mPaint);
Typeface face3 = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);
mPaint.setTypeface(face3);
canvas.drawText(text,50,position+destant*3, mPaint);
Typeface face4 = Typeface.create(Typeface.SERIF, Typeface.NORMAL);
mPaint.setTypeface(face4);
canvas.drawText(text,50,position+destant*4, mPaint);
Typeface face5 = Typeface.create(Typeface.MONOSPACE, Typeface.NORMAL);
mPaint.setTypeface(face5);
canvas.drawText(text,50,position+destant*5, mPaint);
Typeface face6 = Typeface.create(Typeface.MONOSPACE, Typeface.BOLD_ITALIC);
mPaint.setTypeface(face6);
canvas.drawText(text,50,position+destant*6, mPaint);
}
Paint.breakText
用途:给定宽度的情况下,看看能放下多少内容,多用于配合文本的截取功能
@Override
protected void onDraw(Canvas canvas) {
mPaint.setColor(Color.RED);
mPaint.setTextSize(16);
mPaint.setStyle(Paint.Style.FILL);
//需要测量的字符串
String text="安卓程序员";
//用来接受测量结果,测量结果回放在widths[0]
float widths[]=new float[1];
//给定40的宽度,看看能放下几个字,这几个字的宽度为多少?
float maxWidth=40;
int index=mPaint.breakText(text,true,maxWidth,widths);
String log="给定宽度 "+maxWidth+" 时,能放下 "+index+" 个字,这 "+index+" 个字的宽度为 "+widths[0]+" ,整个字符串的宽度为 "+mPaint.measureText(text);
canvas.drawText(log,50,50,mPaint);
float maxWidth1=60;
int index1=mPaint.breakText(text,true,maxWidth1,widths);
String log1="给定宽度 "+maxWidth1+" 时,能放下 "+index1+" 个字,这 "+index1+" 个字的宽度为 "+widths[0]+" ,整个字符串的宽度为 "+mPaint.measureText(text);
canvas.drawText(log1,50,100,mPaint);
float maxWidth2=200;
int index2=mPaint.breakText(text,true,maxWidth2,widths);
String log2="给定宽度 "+maxWidth2+" 时,能放下 "+index2+" 个字,这 "+index2+" 个字的宽度为 "+widths[0]+" ,整个字符串的宽度为 "+mPaint.measureText(text);
canvas.drawText(log2,50,150,mPaint);
}
Paint.setStrokeJoin
设置拐角样式
@Override
protected void onDraw(Canvas canvas) {
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(20);
//写文字的画笔
Paint textPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setTextSize(14);
textPaint.setColor(Color.WHITE);
Path path1=new Path();
path1.moveTo(20,20);
path1.lineTo(100,20);
path1.lineTo(100,60);
mPaint.setStrokeJoin(Paint.Join.ROUND);
canvas.drawPath(path1,mPaint);
canvas.drawText("ROUND",120,40,textPaint);
canvas.translate(0,60);
mPaint.setStrokeJoin(Paint.Join.BEVEL);
canvas.drawPath(path1,mPaint);
canvas.drawText("BEVEL",120,40,textPaint);
canvas.translate(0,60);
mPaint.setStrokeJoin(Paint.Join.MITER);
canvas.drawPath(path1,mPaint);
canvas.drawText("MITER",120,40,textPaint);
}
Paint.setMaskFilter
有两个实现类:
- BlurMaskFilter
- EmbossMaskFilter
BlurMaskFilter
BlurMaskFilter(float radius, Blur style)
设置边界模糊效果
- radius:模糊半径
- style:模糊样式(NORMAL、SOLID、OUTER、INNER)
示例:
@Override
protected void onDraw(Canvas canvas) {
setLayerType(View.LAYER_TYPE_SOFTWARE,null);//关闭硬件加速
Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#ff0000"));
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
//写文字的画笔
Paint textPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setTextSize(14);
textPaint.setColor(Color.WHITE);
int radius=40;
int blurRadius=20;
mPaint.setMaskFilter(new BlurMaskFilter(blurRadius, BlurMaskFilter.Blur.OUTER));
canvas.drawCircle(60,60,radius,mPaint);
canvas.drawText("Blur.OUTER",140,60,textPaint);
canvas.translate(0,100);
mPaint.setMaskFilter(new BlurMaskFilter(blurRadius, BlurMaskFilter.Blur.INNER));
canvas.drawCircle(60,60,radius,mPaint);
canvas.drawText("Blur.INNER",140,60,textPaint);
canvas.translate(0,100);
mPaint.setMaskFilter(new BlurMaskFilter(blurRadius, BlurMaskFilter.Blur.SOLID));
canvas.drawCircle(60,60,radius,mPaint);
canvas.drawText("Blur.SOLID",140,60,textPaint);
canvas.translate(0,100);
mPaint.setMaskFilter(new BlurMaskFilter(blurRadius, BlurMaskFilter.Blur.NORMAL));
canvas.drawCircle(60,60,radius,mPaint);
canvas.drawText("Blur.NORMAL",140,60,textPaint);
}
一定要关闭硬件加速,否则无效果
硬件加速
硬件加速的主要原理,就是通过底层软件代码,将CPU不擅长的图形计算转换成GPU专用指令,由GPU完成。
-
Android3.0(API level 11)开始,2D渲染管道支持硬件加速,也就是说绘制操作可以使用GPU绘制在View的canvas上。使用硬件加速需要更多的资源,所以app会消耗更多内存。
-
硬件加速在Target API >= 14时是默认开启的。
-
硬件加速还不支持所有的2D绘图命令,开启后可能会影响自定义View和绘图操作。异常通常是不可见元素、运行异常、或者错误的像素点。
-
如果只使用系统的View和Drawable,则没有任何副作用。
-
如果app里有自定义的绘图操作,需要在开启硬件加速的设备上测试来发现问题。
总之一句话,不是所有的绘图方法都支持硬件加速,Android默认开启使用了硬件加速,当某些绘图方法不支持,就要关闭硬件加速功能,才能实现效果
关闭打开硬件加速有四种方法:
全局级别关闭(可打开可关闭)
<application
...
android:hardwareAccelerated="false"
...
>
Activity级别关闭(可打开可关闭)
<activity
android:name=".MainActivity"
android:hardwareAccelerated="false">
Window级别(只能打开硬件加速)
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
View级别(只能关闭硬件加速)
view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
一般我们只在View级别关闭即可
EmbossMaskFilter
类似浮雕效果
EmbossMaskFilter(float[] direction,float ambient, float specular, float blurRadius)
- direction:指定光源的方向,由3个量构成的[x,y,z]设置
- ambient:背景光的亮度,取值区间[0,1],决定背景的明暗程度
- specular:高光系数,值越小反射越强,那么亮度也相对偏亮
- blurRadius:阴影延伸半径(模糊半径)
示例:
@Override
protected void onDraw(Canvas canvas) {
setLayerType(View.LAYER_TYPE_SOFTWARE,null);//关闭硬件加速
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(30);
paint.setTextSize(200);
float[] direction = new float[] { 1, 1, 0 };
// 环境光亮度
float light = 0.3f;
// 反射等级
float specular = 5;
//模糊
float blur = 8;
EmbossMaskFilter embossMaskFilter = new EmbossMaskFilter(direction, light, specular, blur);
paint.setMaskFilter(embossMaskFilter);
canvas.drawText("好",100,200,paint);
}
direction光源方向,[x,y,z]:
- x为正,光源在水平方向的左边,反之,x为负,光源在右边
- y为正,光源在垂直方向的上边,反之,y为负,光源在下边
- z为正,光源在z轴方向屏幕外边,反正,z为负,光源在屏幕里边
Paint.setShadowLayer
设置阴影效果
Paint.setShadowLayer(radius,dx,dy, int shadowColor)
- radius 阴影模糊半径,越大越模糊
- dx 阴影在X轴上的偏移量
- dy 阴影在Y轴上的偏移量
- shadowColor 阴影颜色
示例:
@Override
protected void onDraw(Canvas canvas) {
setLayerType(View.LAYER_TYPE_SOFTWARE,null);
Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.RED);
paint.setTextSize(80);
paint.setShadowLayer(10,10,5, Color.BLACK);
canvas.drawText("看到阴影效果了吗?",20,120,paint);
}