原文地址:http://blog.csdn.net/aigestudio/article/details/41447349
详解写字功能。Paint中定义了大量关于“写字”的功能,这些方法总数接近Paint的一半。
意为字体测量。其实是Paint的一个内部类,就定义了五个成员变量:top、ascent、descent、bottom、leading。
Android中,文字的绘制都是从Baseline(基线,Baseline上方的值为负数,下方的值为正数)处开始的,Baseline往上至最高处的距离我们称之为ascent([əˈsɛnt]上坡度),Baseline往下至字符最低处的距离称之为descent([dɪˈsɛnt]下坡度),而leading(行间距)则表示上一行字符的descent到该行字符的ascent之间的距离。TextView在绘制文本时总会在文本的最外层流出一些内边距,为什么要这样做?因为TextView在绘制文本的时候考虑到了类似读音符号,上图中A上面的符号就是一个拉丁文的类似读音符号的东西。top就是指除了Baseline到字符顶端的距离外还应该包含这些符号的高度,bottom也是一个意思。所以top和bottom总会比ascent和descent大一点。而在TextView中,我们可以通过xml设置属性android:includeFontPadding="false"去掉一定的边距值但不能完全去掉。
在绘制文本之前我们便可以获取文本的FontMetrics属性值,也就是说FontMetrics的这些值跟我们要绘制什么文本是无关的,而仅与绘制文本Paint的size和typeface有关。mPaint.setTextSize(70); mPaint.setTypeface(Typeface.SERIF);更改这两个值后,文本的FontMetrics属性值就会变化。
使得文本距离某个区域多少距离,或者让文本绘制在某个区域的中心等等。
Paint的有一个唯一的子类TextPaint就是专门为文本绘制量身定做的“笔”,能在绘制时为文本添加一些额外信息。
最常用的用法是在绘制文本时能实现换行绘制。正常情况下,Android绘制文本是不能识别换行符之类的标识符的,如果想实现换行绘制就得另辟途径使用StaticLayout(是android.text.Layout的一个子类)结合TextPaint实现换行。
1、ascent()、descent()
2、breakText(CharSequence text, int start, int end, boolean measureForwards, float maxWidth, float[] measureWidth):这个方法让我们设置一个最大宽度在不超过这个宽度的范围内返回实际测量值是否停止测量。text表示我们的字符串,start表示从第几个字符开始测量,end表示从测量到第几个字符为止,measureForwards表示向前还是向后测量,maxWdith表示一个给定的最大宽度在这个宽度内能测量出几个字符,measureWidth为一个可选项,可以为空,不为空时返回真实的测量值。同样的方法还有breakText (String text, boolean measureForwards, float maxWidth, float[] measuredWidth)和breakText (char[] text, int index, int count, float maxWidth, float[] measuredWidth)。这些方法在一些结合文本处理的应用里比较常用,比如文本阅读器的翻页效果,我们需要在翻页时动态折断或生成一行文字,这就派上用场了。
3、getFontMetrics()返回FontMetrics对象;getFontMetrics(Paint.FontMetrics metrics)返回的是文本的行间距,如果metrics的值不为空则返回FontMetrics对象的值。
4、getFontMetricsInt()返回一个FontMetricsInt对象,FontMetricsInt和FontMetrics对象一样,只不过FontMetricsInt返回的是int而FontMetrics返回的是float。getFontMetricsInt(Paint.FontMetricsInt fmi)同上
5、getFontSpacing返回行间距
6、setUnderlineText(boolean underlineText)设置下划线
7、setTypeface(Typeface typeface)设置字体类型。Android中有四种样式:BOLD(加粗)、BOLD_ITALIC(加粗并倾斜)、ITALIC(倾斜)、NORMAL(正常);而其为我们提供五种字体:DEFAULT、DEFAULT_BOLD、MONOSPACE、SANS_SERIF、SERIF。那我们可以用自己的字体吗?Typeface这个类中为我们提供了多个方法去个性化我们的字体:
a)defaultFromStyle(int style)就是将上面所说的四种style封装成Typeface。
b)createFromAsset(AssetManager mgr, String path)、createFromFile(String path)、createFromFile(File path)使用自己的字体。
8、setTextSkewX(float skewX)设置文本在水平方向上的倾斜。无具体范围,官方推荐值为-0.25,值为负右倾为正左倾,默认值为0。
9、setTextScaleX(float scaleX)将文本沿X轴水平缩放,默认值为1,当值大于1会沿X轴水平放大文本,当值小于1会沿X轴水平缩小文本。不仅会改变文本宽度,还会拉伸或压缩字符。
10、setTextAlign(Paint.Align align)设置文本的对齐方式:CENTER、LEFT、RIGHT。文本的绘制从baseline开始,但是从哪边开始呢?左端还是右端?这个Align就是为我们定义在baseline绘制文本究竟该从何处开始。上面讲文本水平居中时 用Canvas的宽度的一半减去文本宽度的一半。实际上我们只需设置Paint的文本对齐方式为CENTER。
textPaint.setTextAlign(Align.CENTER); canvas.drawText(TEXT, canvas.getWidth() / 2, baseY, textPaint);三种对其方式效果图:
11、setStrikeThruText(boolean strikeThruText)文本删除线
12、setLinerText(boolean linerText)是否打开线性文本标识。在Android中文本的绘制需要使用一个bitmap作为单个字符的缓存,既然是缓存必定要使用一定的空间,我们可通过setLinerText(true)告诉Android我们不需要这样的缓存。
13、setFakeBoldText(boolean fakeBoldText)设置文本仿粗体。
设置我们在绘图时的抗抖动,也称为递色。就是在两个颜色交接的地方设置一个过渡色,使得颜色交界处不那么突兀。
MaskFilter中没任何方法,而它有两个子类BlurMaskFilter和EmbossMaskFilter,前者为模糊遮罩滤镜(过滤器或滤镜),而后者为浮雕遮罩滤镜。
Android中很多自带控件都有类似阴影的效果,比如Button。它周围就有一圈淡淡的阴影效果,这种效果让控件看起来更真实,那么这是怎么做的呢?其实很简单,使用BlurMaskFilter就可得到类似的效果。
public class MaskFilterView extends View { private Context mContext; private Paint mPaint; public MaskFilterView(Context context) { super(context); } public MaskFilterView(Context context, AttributeSet attrs) { super(context, attrs); // setLayerType(LAYER_TYPE_SOFTWARE, null); initPaint(); } private void initPaint() { mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setStyle(Style.STROKE); mPaint.setColor(Color.RED); //设置画笔遮罩滤镜 mPaint.setMaskFilter(new BlurMaskFilter(20, BlurMaskFilter.Blur.SOLID)); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(Color.GRAY); canvas.drawCircle(100, 100, 100, mPaint); } }效果图:
我们发现并没有效果,为什么呢?AvoidXfermode在API16时已经过时了,因为它不支持硬件加速。要想获得正确的显示效果就得关闭硬件加速。在清单文件中可关闭,但是这样整个应用都不支持硬件加速的。如果只针对某个View关闭硬件加速岂不是更好,可在View中设置:setLayerType(LAYER_TYPE_SOFTWARE, null);来关闭单个View的硬件加速功能。效果图:
第一个参数表示阴影范围。第二个参数表示模糊的类型。SOLID:在图像的Alpha边界外产生一层与Paint颜色一致的阴影效果而不影响图像本身;OUTER:在Alpha边界外产生一层阴影且会将原本的图像变透明;NORMAL:会将整个图像模糊;INNER:则会在图像内部产生模糊。
我们通常会使用混合模式和渐变来获得更完美的引用效果。BlurMaskFilter是根据Alpha通道的边界来计算模糊的,如果是一张图片(Android会把拷贝到资源目录的图片转为RGB565,这里先假设所有提及的图片格式为RGB565)你会发现没有任何效果,那么假使我们需要给图片加一个类似引用效果该如何做呢?只需从Bitmap中获取其Alpha通道,并在绘制Bitmap前先以该Alpha通道绘制一个模糊效果。
public class MaskFilterView extends View { private Context mContext; private Paint mPaint; private Bitmap mBitmap, shadowBitmap; public MaskFilterView(Context context) { super(context); } public MaskFilterView(Context context, AttributeSet attrs) { super(context, attrs); setLayerType(LAYER_TYPE_SOFTWARE, null); initPaint(); } private void initPaint() { mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setStyle(Style.STROKE); mPaint.setColor(Color.RED); //设置画笔遮罩滤镜 mPaint.setMaskFilter(new BlurMaskFilter(20, BlurMaskFilter.Blur.OUTER)); mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher); //获取位图的alpha通道图 shadowBitmap = mBitmap.extractAlpha(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //先绘制阴影 canvas.drawBitmap(shadowBitmap, 100, 100, mPaint); //再绘制位图 //其实先绘制哪个无所谓,一样效果 canvas.drawBitmap(mBitmap, 100, 100, null); } }效果图:
不常用,可实现一种类似浮雕的效果,就是让你绘制的图像是从屏幕中“凸”起来更有立体感。
没有具体的实现,但是有六个子类。
上图从上往下分别是没有PathEffect、CornerPathEffect、DiscretePathEffect、DashPathEffect、PathDashPathEffect、ComposePathEffect、SumPathEffect的效果,代码实现也非常简单:
public class PathEffectView extends View { private float mPhase;// 偏移值 private Paint mPaint;// 画笔对象 private Path mPath;// 路径对象 private PathEffect[] mEffects;// 路径效果数组 public PathEffectView(Context context, AttributeSet attrs) { super(context, attrs); /* * 实例化画笔并设置属性 */ mPaint = new Paint(); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(5); mPaint.setColor(Color.DKGRAY); // 实例化路径 mPath = new Path(); // 定义路径的起点 mPath.moveTo(0, 0); // 定义路径的各个点 for (int i = 0; i <= 30; i++) { mPath.lineTo(i * 35, (float) (Math.random() * 100)); } // 创建路径效果数组 mEffects = new PathEffect[7]; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); /* * 实例化各类特效 */ mEffects[0] = null; mEffects[1] = new CornerPathEffect(10); mEffects[2] = new DiscretePathEffect(3.0F, 5.0F); mEffects[3] = new DashPathEffect(new float[] { 20, 10, 5, 10 }, mPhase); Path path = new Path(); path.addRect(0, 0, 8, 8, Path.Direction.CCW); mEffects[4] = new PathDashPathEffect(path, 12, mPhase, PathDashPathEffect.Style.ROTATE); mEffects[5] = new ComposePathEffect(mEffects[2], mEffects[4]); mEffects[6] = new SumPathEffect(mEffects[4], mEffects[3]); /* * 绘制路径 */ for (int i = 0; i < mEffects.length; i++) { mPaint.setPathEffect(mEffects[i]); canvas.drawPath(mPath, mPaint); // 每绘制一条将画布向下平移250个像素 canvas.translate(0, 250); } // 刷新偏移值并重绘视图实现动画效果 mPhase += 1; invalidate(); } }当我们不设置路径效果时默认效果就如上图第一条线那样直的转折生硬。其他六种效果都有且只有一个含参的构造函数。
a.CornerPathEffect只接受一个参数radius,指定转角处的圆滑程度。
b.DiscretePathEffect离散路径效果相对来说则稍微复杂点,其会在路径上绘制很多“杂点”的突出来模拟一种类似生锈铁丝的效果,接受两个参数:第一个指定这些突出的“杂点”的密度,值越小杂点越密集;第二个参数指定“杂点”突出大小,值越大突出距离越大。
c.DashPathEffect接受两个参数:第一个参数是一个浮点型的数组,我们在定义该参数时只要浮点型数组中元素个数大于等于2即可:
mEffects[3] = new DashPathEffect(new float[] {20, 10}, mPhase);
d.PathDashPathEffect和DashPathEffect类似,不同的是PathDashPathEffect可让我们定义路径虚线的样式,比如我们可将其换成一个个小圆组成的虚线:
Path path = new Path(); path.addCircle(0, 0, 3, Direction.CCW); mEffects[4] = new PathDashPathEffect(path, 12, mPhase, PathDashPathEffect.Style.ROTATE);
心电图:
public class ECGView extends View { private Paint mPaint;// 画笔 private Path mPath;// 路径对象 private int screenW, screenH;// 屏幕宽高 private float x, y;// 路径初始坐标 private float initScreenW;// 屏幕初始宽度 private float initX;// 初始X轴坐标 private float transX, moveX;// 画布移动的距离 private boolean isCanvasMove;// 画布是否需要平移 public ECGView(Context context, AttributeSet set) { super(context, set); /* * 实例化画笔并设置属性 */ mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mPaint.setColor(Color.GREEN); mPaint.setStrokeWidth(5); mPaint.setStrokeCap(Paint.Cap.ROUND); mPaint.setStrokeJoin(Paint.Join.ROUND); mPaint.setStyle(Paint.Style.STROKE); mPaint.setShadowLayer(7, 0, 0, Color.GREEN); mPath = new Path(); transX = 0; isCanvasMove = false; } @Override public void onSizeChanged(int w, int h, int oldw, int oldh) { /* * 获取屏幕宽高 */ screenW = w; screenH = h; /* * 设置起点坐标 */ x = 0; y = (screenH / 2) + (screenH / 4) + (screenH / 10); // 屏幕初始宽度 initScreenW = screenW; // 初始X轴坐标 initX = ((screenW / 2) + (screenW / 4)); moveX = (screenW / 24); mPath.moveTo(x, y); } @Override public void onDraw(Canvas canvas) { canvas.drawColor(Color.BLACK); mPath.lineTo(x, y); // 向左平移画布 canvas.translate(-transX, 0); // 计算坐标 calCoors(); // 绘制路径 canvas.drawPath(mPath, mPaint); invalidate(); } /** * 计算坐标 */ private void calCoors() { if (isCanvasMove == true) { transX += 4; } if (x < initX) { x += 8; } else { if (x < initX + moveX) { x += 2; y -= 8; } else { if (x < initX + (moveX * 2)) { x += 2; y += 14; } else { if (x < initX + (moveX * 3)) { x += 2; y -= 12; } else { if (x < initX + (moveX * 4)) { x += 2; y += 6; } else { if (x < initScreenW) { x += 8; } else { isCanvasMove = true; initX = initX + initScreenW; } } } } } } } }setStrokeCap(Paint.Cap cap):设置画笔的笔触风格。
setStrokeJoin(Paint.Join join):设置结合处的形态,上面虽说画了一条心电图,但是这条心电图其实是由无数条小线条组成的,拼接处的形状就由该方法指定。
setShadowLayer(float radius, float dx, float dy, int shadowColor):为绘制的图形添加一个阴影层效果(不支持硬件加速)。讲MaskFilter的子类BlurMaskFilter也可实现此效果,但是BlurMaskFilter效果更强大。