FontMetrics以及自定义ImageSpan实现TextView中文图混排时文图的居中对齐

这个标题有点长,乍一看这么个标题你可能没明白啥意思,且听我慢慢道来。

公司的项目中新增了一个“心动” 的功能,用户初次使用时需要给一个引导页,就是下面图中的这个样子(这就是做完之后的效果了)。

Paste_Image.png

在上图中整体实现的时候使用的是popUpWindow。该popupWindow整体使用相对布局,里面再用一个相对布局布局嵌套了三个TextView:"啊哦。。。。pass" 用一个TextView,中间灰色的上传头像的提示用了一个TextView,底部“我知道了” 也是一个TextView。上面的左划示意图使用above 放在 包含TextView的相对布局上方,并通过负的margin值将它下移并覆盖在包含TextView相对布局上。

这个界面并没有什么难度,这里重点说的是第一个TextView中的图文混排,并让图片的横向中间线与该行文字的横向中间线对齐,也就是说,让文字与那个💔 图片的中间在水平方向对齐。

1. 图文混排的方式有哪些?

通常我们向TextView中插入图片实现图文混排有如下方式:

  1. 使用drawableLeft等属性设置,这种方式对应的java方法是 setCompoundDrawablesWithIntrinsicBounds(leftDrawble,topDrawable,rightDrawable,bottomDrawable);
  • 使用 SpannableString ,先将图片转成ImageSpan对象,然后通过setSpan插入到SpannableString 中,最后再将SpannableString通过setText设置给TextView。(SpannableString 继承自CharSquence)
  • 此外,还有一种利用Html.ImageGetter格式化图片的方式。(截止目前为止,我没用过这种方式,如果想了解的话,可以参考http://wangleyiang.iteye.com/blog/1771439中的第二点)

2. 使用SpannableString+ImageSpan怎么实现图文混排?

(1). 基本实现方式

效果图如下:

Paste_Image.png

实现方式很简单,我们只需要在xml布局文件中定义一个TextView,然后在代码中获取该TextView并创建一个含有图片的SpannableString,并将该SpannableString通过setText( )设置给TextView即可。代码如下:

public class SpannableStringAndImageSpanActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_spannbalestring_imagespan);init();}private void init() {TextView tv_test = (TextView) findViewById(R.id.tv_test);SpannableString spannableString = new SpannableString("点击 按钮有惊喜");ImageSpan imageSpan = new ImageSpan(this, R.mipmap.ic_launcher);//setSpan插入内容的时候,起始位置不替换,会替换起始位置到终止位置间的内容,含终止位置。//Spanned.SPAN_EXCLUSIVE_EXCLUSIVE模式用来控制是否同步设置新插入的内容与start/end 位置的字体样式,此处没设置具体字体,所以可以随意设置spannableString.setSpan(imageSpan, 2, 3, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);tv_test.setText(spannableString);}
}

xml布局文件中只给了一个普通的TextView,代码省略。

  1. 在上面的代码中,我们通过ImageSpan的构造方法得到了一个ImageSpan对象。该构造方法中传入的两个参数分别是上下文和图片的id。(imageSpan的构造方法还有很多)
  2. SpannbaleString的setSpan方法中,传入的四个参数分别是 ImageSpan对象、将ImageSpan插入到的起始位置(start)、将ImageSpan插入到的终点位置(end)、是否应用字体样式。具体将ImageSpan对象插入到哪个位置,由第二个和第三个参数确定,插入的时候会覆盖从 start 位置开始(不包含该位置)到终止位置间的内容(包含该位置)。第四个参数是在你插入文本的时候使用的,用来控制新插入的文本与已有文本内容的字体样式是否一致的如果你插入的是图片,这里就可以随便选择一种模式。

经过上面虽然实现了图文混排,但是,细心的你可能发现了,这时候的文字和图片是基于底部对齐的(由于图片的原因,图片底部与边框有一点点的间距)。那么如果我想更改对齐方式怎么办呢?

(2). 更改图片与文本的对齐方式--ALIGN_BASELINE对齐

设置对齐方式的方法很简单,在构造ImageSpan对象的时候,传入第三个参数ALIGN_BASELINE 即可,代码如下:

ImageSpan imageSpan = new ImageSpan(this, R.mipmap.ic_launcher, DynamicDrawableSpan.ALIGN_BASELINE);

设置对齐方式为ALIGN_BASELINE后的效果图:

咦,看着跟上面的图没啥区别啊?那么我再把上面没设置对齐方式的图拉下来看下:

仔细对比下,我们发现,设置对齐方式之后,图往上跑了一点点。

其实,在ImageSpan 中,官方只给出了两中对齐方式:

  1. 一种是 ALIGN_BOTTOM , 表示与文字内容的底部对齐,如果在构造ImageSpan时没有传入对齐方式,那么默认就是这种底部对齐。
  2. 另一中就是 ALIGN_BASELINE, 表示与文字内容的基线对齐。那么,你可能会问我基线是啥?请继续往下看:

3. Paint.FontMetrics 是啥?

(1). Paint.FontMetrics基本介绍

要说基线呢,我们先了解这个Paint.FontMetircs, 官方对该类的解释是:Class that describes the various metrics for a font at a given text size., 意思是说,这玩意儿是绘制文本内容时存储该文本内容位置信息的一个类。这个类中有如下五个字段:

Paste_Image.png

(2). BaseLine 基线到底是啥?

上图中这5个字段除了leading 外,其他四个都是相对于 基线BaseLine来确定的,那么,到底啥是基线??先来看一张图:

Paste_Image.png

如上图, 标准的英文书写是基于四线三格,其中,我们书写英文的时候,都是以第三条线为基准,也就是说,基线就是这个四线三格中的第三条线!!

(3). Paint.FontMetrics中字段的含义及示意图

官方文档中对这几个字段的解释很简单,但理解起来挺费劲,直接上图,图中的标注都是跑代码之后确定的,如果有不准确的地方,欢迎指正:

Paste_Image.png

根据上图可知:

  • ascent
    文字内容的顶部到基线的距离。即 ascent=文字内容顶部Y坐标 - 基线Y坐标。由于android中坐标系是 右下为正,所以得到的ascent实际是一个负数。

  • descent
    文字内容的底部到基线的距离。即 descent=文字内容底部Y坐标 - 基线Y坐标。

  • ** 基线 **
    在图中,基线的坐标用Y表示,在ImageSpan父类的 draw( ) 中,会传入一个 float Y ,就是这个基线的坐标。实际上,基线的Y坐标=文字内容中间线Y坐标+1/2 (文字内容高度)

  • top
    对应图中 文字所在行的top 坐标

  • bottom
    对应图中 文字所在行的bottom 坐标需要注意:如果设置了行间距,且文本内容产生了换行,那么这个bottom 也会将行间距包裹。所以, 图中蓝色的文字内容中间线的Y轴坐标并不一定等于 (bottom+top)/2

4 自定义ImageSpan实现文字与图片居中对齐

好了,前面说了那么多,终于进入正题了。。。
在上面的2 SpannableString+ImageSpan实现图文混排中,我们已经知道官方并没有给出文字与图片居中对齐的模式,所以需要我们自定义。

关于自定义ImageSpan的分析,已经有前辈讲解过了,此处不再赘述,请参考http://blog.csdn.net/gaoyucindy/article/details/39473135。但是,按照该文章中的代码实现的时候,有个问题就是:如果给TextView设置了行间距,且文本产生了换行,那么就无法对齐了!!

那么,设置了行间距之后,该如何实现文本和图片的居中对齐呢?也有前辈分析过了,请看:http://www.cnblogs.com/withwind318/p/5541267.html , 但是,这篇文章中的实现方式没有重写 getSize( ) 方法,所以也有一个问题:文本和图片并不是在TextView的居中位置,而且如果图片高于文本的话,图片会显示不全!!如下图:

Paste_Image.png

参考了那么多了,终于该给出我的终极方案了!!根据上面链接中两位前辈的分析,其实我们自定义的时候,需要做的事情是 获取文本内容的中间线以及图片的中间线,然后获取两者差值,然后在draw方法中绘制图片时将差值作为canvas.translate(x, transY) 中的transY;同时要重写 getSize( )。这样最终实现的效果是,不论是否设置行间距,不论图片大于文本还是文本大于图片,都能实现文本和图片的居中对齐!

看最终效果图:

Paste_Image.png

上代码:

public class SpannableStringAndImageSpanActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_spannbalestring_imagespan);init();}private void init() {TextView tv_test = (TextView) findViewById(R.id.tv_test);SpannableString spannableString = new SpannableString("点击 按钮有惊喜");//调用自定义的imageSpan,实现文字与图片的横向居中对齐CustomImageSpan imageSpan = new CustomImageSpan(this, R.mipmap.ic_launcher, 2);//setSpan插入内容的时候,起始位置不替换,会替换起始位置到终止位置间的内容,含终止位置。//Spanned.SPAN_EXCLUSIVE_EXCLUSIVE模式用来控制是否同步设置新插入的内容与start/end 位置的字体样式,此处没设置具体字体,所以可以随意设置spannableString.setSpan(imageSpan, 2, 3, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);tv_test.setText(spannableString);}/*** 自定义imageSpan实现图片与文字的居中对齐*/class CustomImageSpan extends ImageSpan {//自定义对齐方式--与文字中间线对齐private int ALIGN_FONTCENTER = 2;public CustomImageSpan(Context context, int resourceId) {super(context, resourceId);}public CustomImageSpan(Context context, int resourceId, int verticalAlignment) {super(context, resourceId, verticalAlignment);}@Overridepublic void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom,Paint paint) {//draw 方法是重写的ImageSpan父类 DynamicDrawableSpan中的方法,在DynamicDrawableSpan类中,虽有getCachedDrawable(),// 但是私有的,不能被调用,所以调用ImageSpan中的getrawable()方法,该方法中 会根据传入的drawable ID ,获取该id对应的// drawable的流对象,并最终获取drawable对象Drawable drawable = getDrawable(); //调用imageSpan中的方法获取drawable对象canvas.save();//获取画笔的文字绘制时的具体测量数据Paint.FontMetricsInt fm = paint.getFontMetricsInt();//系统原有方法,默认是Bottom模式)int transY = bottom - drawable.getBounds().bottom;if (mVerticalAlignment == ALIGN_BASELINE) {transY -= fm.descent;} else if (mVerticalAlignment == ALIGN_FONTCENTER) {    //此处加入判断, 如果是自定义的居中对齐//与文字的中间线对齐(这种方式不论是否设置行间距都能保障文字的中间线和图片的中间线是对齐的)// y+ascent得到文字内容的顶部坐标,y+descent得到文字的底部坐标,(顶部坐标+底部坐标)/2=文字内容中间线坐标transY = ((y + fm.descent) + (y + fm.ascent)) / 2 - drawable.getBounds().bottom / 2;}canvas.translate(x, transY);drawable.draw(canvas);canvas.restore();}/*** 重写getSize方法,只有重写该方法后,才能保证不论是图片大于文字还是文字大于图片,都能实现中间对齐*/public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {Drawable d = getDrawable();Rect rect = d.getBounds();if (fm != null) {Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt();int fontHeight = fmPaint.bottom - fmPaint.top;int drHeight = rect.bottom - rect.top;int top = drHeight / 2 - fontHeight / 4;int bottom = drHeight / 2 + fontHeight / 4;fm.ascent = -bottom;fm.top = -bottom;fm.bottom = top;fm.descent = top;}return rect.right;}}
}

xml布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewandroid:id="@+id/tv_test"android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#fffaa3"android:lineSpacingExtra="@dimen/dp100"android:textSize="16sp"/></LinearLayout>

上面的已经是完整代码了,如果想直接下载运行,请到gitHub下载:https://github.com/CnPeng/CrazyAndroid。该仓库中的b_01_spannableString_ImageSpan 对应该文中的内容


写在最后,最近项目太紧了,过了年一直在加班。这次的总结也很仓促,本来想写的更细一些,并且也想把SpannableString的使用完整总结,but 时间太紧了,先这样吧,后面时间充足了再修正吧!

欢迎各位指正文中错误的地方,一起交流,一起进步!

参考链接:http://wangleyiang.iteye.com/blog/1771439http://blog.csdn.net/gaoyucindy/article/details/39473135http://www.cnblogs.com/withwind318/p/5541267.htmlhttp://stackoverflow.com/questions/27631736/meaning-of-top-ascent-baseline-descent-bottom-and-leading-in-androids-fonthttps://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B99%5DDrawText.md

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/56772.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

单词测试通关学英语的软件,推荐5款最火的英语学习类app

学习英语很累很吃力吗?小编推荐5款最火的英语学习类app,希望对你有所帮助。 如果你是高中生的话,蝶变高中是你必备的app,里面除了正常的励志电台和各科知识点外,还有历年的高考英语听力真题、高中英语课文音频、以及很多英语美文的音频。 1、推荐5款最火的英语学习类app:…

计算机专业英语邱仲潘版英语翻译,计算机英语邱仲潘

本书共分为20章&#xff0c;每章由正文、难句解释、关键词、练习和课外阅读等部分组成&#xff0c;此外&#xff0c;每章还提供了参考译文。本书由一线教师编写&#xff0c;其了解学生的知识水平、接受能力和需求点&#xff0c;而且翻译过大量计算机图书&#xff0c;有丰富的翻…

新编计算机英语,新编计算机英语

《新编计算机英语》以计算机和IT领域的*英语时文和经典原版教材为基础&#xff0c;通过精心挑选难度适中的新闻记者材料和悉心编写的学习指南&#xff0c;配以详尽的注释和练习&#xff0c;使读者能够快速掌握计算机英语的一般特点和大量专业词汇&#xff0c;并提高阅读和检索计…

快讯 | OpenAI 推出漏洞赏金计划,奖励最高 2 万美元;马斯克被曝明面上呼吁暂停 AI 研究,暗中却购买上万个 GPU 推进 AIGC 项目

一分钟速览新闻点 复旦大学与阿里云共建中国高校最大的科研智算平台 科大讯飞将于 5 月 6 日发布讯飞预训练大模型的详细信息 国家网信办就《生成式人工智能服务管理办法&#xff08;征求意见稿&#xff09;》公开征求意见 毫末智行正式发布自动驾驶生成式大模型DriveGPT O…

OpenAI向ChatGPT社区致歉:承认数据泄露 CEO称感觉糟糕

雷递网 乐天 3月26日 人工智能企业OpenAI日前发布公告&#xff0c;承认部分ChatGPT Plus服务订阅用户可能泄露部分个人隐私和支付信息。 OpenAI称&#xff0c;由于开源库中的一个错误&#xff0c;本周早些时候将ChatGPT下线&#xff0c;该错误允许一些用户看到另一个活跃用户的…

如何用 Midjourney 绘制你自己的皮克斯风格头像?

元宇宙时代&#xff0c;有个卡通形式的头像&#xff08;Avatar&#xff09;似乎是刚需了。我看到很多小伙伴都给自己弄了一个头像。大多保持了神似&#xff0c;很是羡慕。 我也想自己弄一个&#xff0c;不过雇人设计绘制太贵&#xff0c;自己画嘛&#xff0c;你知道的…… 我的…

联邦学习(FL)安全威胁

MPC、DP等&#xff1a;通过降低模型性能或系统效率为代价来确保隐私安全 一、FL安全问题 训练过程中&#xff0c;联邦学习仍然存在模型更新过程中向第三方或中央服务器透露敏感信息的情况FL协议设计存在漏洞&#xff0c;任一参与方可能获得全局参数并能控制这些参数的上传模型…

如何准备CKAD和CKA认证?

在准备 CNCF 的 CKAD 或 CKA 认证时&#xff0c;会有很多疑问&#xff0c;首先出现哪个考试&#xff0c;参考哪些资源&#xff0c;避免哪些常见错误等。尤其是如果您没有以前的知识或新手- 根据 Kubernetes 的经验&#xff0c;这可能是一个艰难的处境。InfraCloud 强烈鼓励工程…

朝鲜APT组织使用带后门IDA软件攻击安全研究人员

2021年初&#xff0c;朝鲜APT组织Lazarus通过养推特大V账号&#xff0c;配合定制开发的恶意软件0day漏洞针对安全研究人员进行了一系列的网络攻击活动&#xff0c;详细可见&#xff1a;通过社交媒体针对安全研究人员的社会工程学攻击活动。 而就在2021年11月10日&#xff0c;国…

chatgpt自己杜撰答案(sometimes)?

当我想通过filebeat将mysql表的内容同步到ELK系统中时&#xff0c;我向chatgpt询问时&#xff1a; 在我根据上述回答完成配置文件后启动filebeat后报错如下&#xff1a; ERRORinstance/beat.go:916Exiting: Error while initializing input: Error creating input. No such i…

智能聊天功能——语音聊天篇

如今大部分人都面临着来自生活各方面的压力&#xff0c;时常感到焦虑、孤独&#xff0c;有的甚至患上了抑郁症等心理疾病&#xff0c;他们无法排解&#xff0c;甚至找不到人来诉苦&#xff0c;本系统提供了语音对话功能。 在用户无聊的时候&#xff0c;可以唤醒“依米”&#x…

转【Latex】Texstudio英文拼写错误检查功能出问题的解决方法

【Latex】Texstudio英文拼写错误检查功能出问题的解决方法 2018年03月23日 22:54:49 阅读数&#xff1a;685 问题描述 最近用latex&#xff08;编辑器为Texstudio&#xff09;写论文&#xff0c;相比于本科时常用的word&#xff0c;各种方便&#xff0c;唯一不爽的是texstu…

英文论文纠错:

Grammarly&#xff1a;官网下载&#xff0c;拉到底部product的office版本&#xff0c;下载后安装&#xff0c;word中会多一个插件&#xff0c;就像这样 基本上免费的功能就够用了&#xff0c;它会识别全文&#xff0c;然后给出你可能存在的问题

GPT专业应用:生成百度搜索创意

正文共 1017 字&#xff0c;阅读大约需要 4 分钟 数字营销人员必备技巧&#xff0c;您将在4分钟后获得以下超能力&#xff1a; 生成百度搜索创意 Beezy评级 &#xff1a;B级 *经过简单的寻找&#xff0c; 大部分人能立刻掌握。主要节省时间。 推荐人 | nanako 编辑者 | Linda…

惊!ChatGPT处理文章仅需一秒钟,提取大纲、重写不在话下!

前言 在上篇文章中&#xff0c;我们实现了批量抓取到微信公众号文章的链接地址&#xff0c;那么这篇文章将继续为大家介绍&#xff0c;如何根据链接爬取到文章内容&#xff0c;并且利用chantGPT对文章进行处理。 爬取文章内容 我们已经有了很多文章的链接&#xff0c;这些链…

程序员做自媒体,到底是怎样一种体验?这几个大佬公众号彻底告诉你

人与人之间的差异&#xff0c;很多时候在于认知&#xff0c;说起大厂的程序员&#xff0c;一般技术是会好些&#xff0c;一方面原因能进大厂的程序员大部分学历、专业都过硬&#xff0c;另一方面也是最主要的&#xff0c;他们也会不断利用大的平台学习总结&#xff0c;触类旁通…

自媒体多平台发布怎么做?2022年高级自媒体人都这么玩

现在许多小伙伴开始做自媒体都是在一些自媒体视频或者博主的介绍下才认识到做自媒体可以给自己带来收益&#xff0c;刚刚做得时候可能只会弄一两个账号&#xff0c;发现收益也不高&#xff0c;便不想去做相关的事情了&#xff0c;的确现在做自媒体的人太多了&#xff0c;无论是…

做自媒体月入几万?博主们都在用的几个自媒体工具

为什么新手做自媒体行业月入只有几十块钱&#xff0c;而那些自由博主做自媒体却月入几万呢&#xff1f;想知道你和他们的差距吗&#xff1f;如果是你做了一个月甚至更久的时间不见效果&#xff0c;那就试试博主们都在用的几个工具和网站&#xff0c;或许就能改变你目前的一个现…

玩转自媒体,你需要这样做

文/Fdaxiong大熊 &#xff08;分享个人经验&#xff0c;帮助新人快速了解自媒体平台&#xff09; 自媒体平台有那些&#xff1f;1.微信公众号&#xff0c;2.今日头条&#xff0c;3.一点资讯&#xff0c;4.企鹅平台&#xff0c;6.百家号&#xff0c;7.大鱼号&#xff0c;8.搜狐号…

怎么做自媒体?从这几步做起

新手怎么做自媒体&#xff0c;自媒体新手要从这几步做起&#xff0c;新手怎么做自媒体&#xff0c;现在互联网的发展越来越迅速&#xff0c;做自媒体的人越来越多&#xff0c;走在路上随手可见拍视频的人&#xff0c;身边靠写文章挣钱的人&#xff0c;一大把。小到几岁的小孩&a…