前言

在客户端开发中,我们往往需要对一个TextView的文字的部分内容进行特殊化处理,比如加粗、改变颜色、加链接、下划线等。iOS为我们提供了AttributedString,而Android则提供了SpannableString。

在Android的android.text.style包下为我们提供了各种各样的span(可以参考这篇文章),例如:

AbsoluteSizeSpan(int size) —— 设置字体大小,参数是绝对数值,相当于Word中的字体大小

RelativeSizeSpan(float proportion) —— 设置字体大小,参数是相对于默认字体大小的倍数,比如默认字体大小是x, 那么设置后的字体大小就是x*proportion,这个用起来比较灵活,proportion>1就是放大(zoom in), proportion<1就是缩小(zoom out)

BackgroundColorSpan(int color) —— 背景着色,参数是颜色数值,可以直接使用android.graphics.Color里面定义的常量,或是用Color.rgb(int, int, int)

ForegroundColorSpan(int color) —— 前景着色,也就是字的着色,参数与背景着色一致

问题

网上已经有着很多使用这些span的教程了,所以没必要在这里继续探讨这些基础使用了。但是,如果使用了AbsoluteSizeSpan(int size) 在同一个TextView中定义了不同字体大小,就会默认显示成底部对齐的方式:

说到这里,第一反应肯定是tv.setGravity(Gravity.CENTER_VERTICAL),但是很不幸,怎么试都不凑效。那么到底有没有办法使用Span让不同字体大小的垂直居中呢?

答案是:当然可以,得用ReplacementSpan

分析

为何是ReplacementSpan?

它是系统提供给我们的一个抽象类。通过名字我们可以知道其实用于是用于替换。指示我们可以把文本的某一部分替换成我们想要的内容。这也许是我们想要的。

Relpacement的定义很简单:

public abstract class ReplacementSpan extends MetricAffectingSpan {

public abstract int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm);

public abstract void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint);

public void updateMeasureState(TextPaint p) { }

public void updateDrawState(TextPaint ds) { }

}

我们在继承它的时候,需要实现两个方法getSize()和draw()。通过方法名,我们也许能够知道其作用:getSize()用于确定span的大小(实际上只是一个宽度),draw()用于绘制我们想要的内容。

但是问题来了,这些方法的传参是什么?为何getSize()只返回了一个int值?

了解了这两个问题,就基本弄懂了自定义span。来回答这两个问题前,我们首先要明确的一件事情是:span是用于SpannableString中,并且最终被用于TextView中。所以在定义span时,我们的大小、绘制内容都应该依赖于使用时的环境。我们假设自定义span使用的环境为A,那么A将包换一些信息,例如:baseline、Paint、FontMetricsInt等信息。

那我们现在来看看getSize()方法。getSize()的返回值是int,其实这个值指的是自定义span的宽度,那它的高度呢?其实高度是已知的,那就是外界环境A带来的字的高度。但我某些情况我们希望改变span的高度,我们该怎么做呢? 如果对Android上字体绘制有一定了解的同学会知道,一个字的高度取决于绘制这个子的Paint.FontMetricsInt

什么是 Paint.FontMetrics

它表示绘制字体时的度量标准。google的官方api文档对它的字段说明如下:

Type

Fields

public float

ascent - The recommended distance above the baseline for singled spaced text.

public float

bottom - The maximum distance below the baseline for the lowest glyph in the font at a given text size.

public float

descent - The recommended distance below the baseline for singled spaced text.

public float

leading - The recommended additional space to add between lines of text.

public float

top - The maximum distance above the baseline for the tallest glyph in the font at a given text size.

其中:

ascent : 字体最上端到基线的距离,为负值。

descent:字体最下端到基线的距离,为正值。

如上图,中间那条线(Baseline)就是基线,基线到上面那条线的距离就是ascent,基线到下面那条线的距离就是descent。

回到我们的主题, 我们发现getSize()方法的参数中有Paint.FontMetricsInt,那我们是否就可以通过改变传入的Paint.FontMetricsInt的asent和desent来达到改变高度的目的呢?答案是可行的。

解决方法

按照上面的分析,我们继承ReplacementSpan 自定义一个Span

/** * 使TextView中不同大小字体垂直居中 */

public class CustomVerticalCenterSpan extends ReplacementSpan {

private int fontSizeSp; //字体大小sp

public CustomVerticalCenterSpan(int fontSizeSp){

this.fontSizeSp = fontSizeSp;

}

@Override

public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {

text = text.subSequence(start, end);

Paint p = getCustomTextPaint(paint);

return (int) p.measureText(text.toString());

}

@Override

public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {

text = text.subSequence(start, end);

Paint p = getCustomTextPaint(paint);

Paint.FontMetricsInt fm = p.getFontMetricsInt();

canvas.drawText(text.toString(), x, y - ((y + fm.descent + y + fm.ascent) / 2 - (bottom + top) / 2), p); //此处重新计算y坐标,使字体居中

}

private TextPaint getCustomTextPaint(Paint srcPaint) {

TextPaint paint = new TextPaint(srcPaint);

paint.setTextSize(ViewUtils.getSpPixel(mContext, fontSizeSp)); //设定字体大小, sp转换为px

return paint;

}

}

解释下形参:

x:要绘制的image的左边框到textview左边框的距离。

y:要替换的文字的基线(Baseline)的纵坐标。

top:替换行的最顶部位置。

bottom:替换行的最底部位置。注意,textview中两行之间的行间距是属于上一行的,所以这里bottom是指行间隔的底部位置。

paint:画笔,包含了要绘制字体的度量信息。

所以就有:

y + fm.descent:得到字体的descent线坐标;

y + fm.ascent:得到字体的ascent线坐标;

(y + fm.descent + y + fm.ascent) / 2 也就是字体中间线的纵坐标

((y + fm.descent + y + fm.ascent) / 2 - (bottom + top) / 2) 就是字体需要向上调整的距离

使用方式

SpannableString ss = new SpannableString(disStr + unitString);

ss.setSpan(new AbsoluteSizeSpan(40, true), 0, disStr.length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

//垂直居中显示文字

ss.setSpan(new CustomVerticalCenterSpan(23), disStr.length(), ss.length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

看看效果:

【参考资料】

Android中设置字体居中,【Android】TextView中不同大小字体如何上下垂直居中?相关推荐

  1. Android中设置显示文本,Android文本显示控件-TextView属性详解

    android:autoLink //设置是否当文本为URL链接/email/电话号码/map时,文本显示为可点击的链接.可选值(none/web /email/phone/map/all) andr ...

  2. androidstudio图片居中_android 在代码中设置布局居中layout_gravity,layout_margin的方法...

    在代码中设置布局居中,翻看api可以知道view中有setGravity,setPadding,但是没有直接的setLayoutGravity,setMargin等方法.下面将在代码中实现类似布局中l ...

  3. android canvas添加文字居中,android Canvas drawText 文字居中

    1首先利用canvas获取画布的宽高, //获取屏幕的宽和高 int width = canvas.getWidth(); int height = canvas.getHeight(); 2获取文字 ...

  4. 在 PyCharm 2017.2.3 中设置 Anaconda 5.0.0 中配置的多Python环境

    Anaconda 相关文章请先阅读 安全的安装Anaconda3 5.0.0 Windows x86_64 http://blog.csdn.net/hu_zhenghui/article/detai ...

  5. android按钮设置下划线,Android开发如何给textView设置下划线或中划线

    关键字:android,android开发,下划线,中划线 我们在开发应用的时候,尤其是在做商城项目的时候,需要用到原价格以及降价后的价格,这就不可避免用到中划线或者下划线,在原价格上做一个中划线,这 ...

  6. android 布局: LinearLayout如何使TextView中的内容居中显示

    在LinearLayout布局中: <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android&quo ...

  7. android控件位置居中,Android 的布局中如何使控件居中

    首先要分两种不同情况,在两种不同的布局方式下:LinearLayout 和RelativeLayout 1. LinearLayout a). android:layout_gravity=" ...

  8. android如何设置自动补,Android Studio 中设置代码块自动补齐

    AS中很多提示键,并不如Eclipse中做的好,需要我们自己去自定义.这里以switch...case为例,讲解一下如何设置代码自动补全. 1.进入settings -->  Editor -- ...

  9. android动态设置文本居中显示图片,Android DrawableTextView图片文字居中显示实例

    在我们开发中,TextView设置Android:drawableLeft一定使用的非常多,但Drawable和Text同时居中显示可能不好控制,有没有好的办法解决呢? 小编的方案是通过自定义Text ...

  10. android动态设置文本居中显示图片,android按钮图片和文本居中的代码-你躺枪了吗...

    最近优化项目代码时,发现一个比较诡异的现象:每当界面进入ActivityA时,cpu在不断的消耗,内存在不断的缓慢增长(虽然每次增长的量非常小).如下图: 屏幕快照 2016-07-24 12.26. ...

最新文章

  1. ECSHOP头部调用会员的消费积分
  2. linux zabbix_agentd命令 监控服务器参数 简介
  3. Vmware workstation 网络连接类型
  4. android studio table居中代码_CSS 之 居中
  5. 基于SLIC分割的特征点检测
  6. Python入门之中文乱码
  7. 23 Refs的应用场景与选用思考
  8. 好程序员大数据教程分享之Hadoop优缺点
  9. 内存区划分、内存分配、常量存储区、堆、栈、自由存储区、全局区[C++][内存管理][转载]...
  10. gettype php,gettype
  11. 什么是华为认证?华为技术认证工程师可以做什么?
  12. MySql打开局域网及广域网端口
  13. Lua和C/C++交互的注意点_艾孜尔江撰
  14. mysql朋友圈数据库设计_实现微信朋友圈可见不可见的数据库设计及ORM语句
  15. win10 通过命令打开画图工具
  16. SQL*Loader总结sqlldr
  17. 浏览器中新开标签页(Tab)
  18. 【学习笔记】明哥聊求职第二季
  19. 健身App应该具备的功能模块
  20. 25-day24黑马javaweb笔记-redis

热门文章

  1. 显示技术全倒装COB小间距LED与正装COB小间距的优势对比。
  2. Ta-lib学习笔记02--K线模式识别
  3. 【吴恩达deeplearning.ai】Course 5 - 3.3 集束搜索
  4. 汇编语言笔记(王爽)
  5. 字符串类型的数字的加减乘除运算
  6. 路由器和交换机有什么区别
  7. 摩尔纹的原理与产生条件
  8. root账号无法通过SSH登录阿里云ECS
  9. C语言—飞机大战小游戏
  10. 打造高性能高可靠的 Ceph 块存储系统