Android中文图混排时文图的居中对齐 FontMetrics以及自定义ImageSpan实现
文章轉(zhuǎn)自:http://www.sohu.com/a/150059234_611601
?
本文作者CnPeng的博客地址:
http://www.jianshu.com/p/2650357f7547
這個(gè)標(biāo)題有點(diǎn)長(zhǎng),乍一看這么個(gè)標(biāo)題你可能沒明白啥意思,且聽我慢慢道來。
公司的項(xiàng)目中新增了一個(gè)“心動(dòng)” 的功能,用戶初次使用時(shí)需要給一個(gè)引導(dǎo)頁,就是下面圖中的這個(gè)樣子(這就是做完之后的效果了)。
在上圖中整體實(shí)現(xiàn)的時(shí)候使用的是popUpWindow。
該popupWindow整體使用相對(duì)布局,里面再用一個(gè)相對(duì)布局布局嵌套了三個(gè)TextView:"啊哦。。。。pass" 用一個(gè)TextView,中間灰色的上傳頭像的提示用了一個(gè)TextView,底部“我知道了” 也是一個(gè)TextView。
上面的左劃示意圖使用above 放在 包含TextView的相對(duì)布局上方,并通過負(fù)的margin值將它下移并覆蓋在包含TextView相對(duì)布局上。
這個(gè)界面并沒有什么難度,這里重點(diǎn)說的是第一個(gè)TextView中的圖文混排,并讓圖片的橫向中間線與該行文字的橫向中間線對(duì)齊,也就是說,讓文字與那個(gè)�� 圖片的中間在水平方向?qū)R。
1
圖文混排的方式有哪些?
通常我們向TextView中插入圖片實(shí)現(xiàn)圖文混排有如下方式:
1. 使用drawableLeft等屬性設(shè)置,這種方式對(duì)應(yīng)的java方法是 setCompoundDrawablesWithIntrinsicBounds(leftDrawble,topDrawable,rightDrawable,bottomDrawable);
2. 使用 SpannableString ,先將圖片轉(zhuǎn)成ImageSpan對(duì)象,然后通過setSpan插入到SpannableString 中,最后再將SpannableString通過setText設(shè)置給TextView。(SpannableString 繼承自CharSquence)
3. 此外,還有一種利用Html.ImageGetter格式化圖片的方式。(截止目前為止,我沒用過這種方式,如果想了解的話,可以參考http://wangleyiang.iteye.com/blog/1771439中的第二點(diǎn))
2
使用SpannableString+ImageSpan怎么實(shí)現(xiàn)圖文混排?
(1)基本實(shí)現(xiàn)方式
效果圖如下:
實(shí)現(xiàn)方式很簡(jiǎn)單,我們只需要在xml布局文件中定義一個(gè)TextView,然后在代碼中獲取該TextView并創(chuàng)建一個(gè)含有圖片的SpannableString,并將該SpannableString通過setText( )設(shè)置給TextView即可。
代碼如下:
xml布局文件中只給了一個(gè)普通的TextView,代碼省略。
在上面的代碼中,我們通過ImageSpan的構(gòu)造方法得到了一個(gè)ImageSpan對(duì)象。該構(gòu)造方法中傳入的兩個(gè)參數(shù)分別是上下文和圖片的id。(imageSpan的構(gòu)造方法還有很多)
SpannbaleString的setSpan方法中,傳入的四個(gè)參數(shù)分別是 ImageSpan對(duì)象、將ImageSpan插入到的起始位置(start)、將ImageSpan插入到的終點(diǎn)位置(end)、是否應(yīng)用字體樣式。
具體將ImageSpan對(duì)象插入到哪個(gè)位置,由第二個(gè)和第三個(gè)參數(shù)確定,插入的時(shí)候會(huì)覆蓋從 start 位置開始(不包含該位置)到終止位置間的內(nèi)容(包含該位置)。
第四個(gè)參數(shù)是在你插入文本的時(shí)候使用的,用來控制新插入的文本與已有文本內(nèi)容的字體樣式是否一致的如果你插入的是圖片,這里就可以隨便選擇一種模式。
經(jīng)過上面雖然實(shí)現(xiàn)了圖文混排,但是,細(xì)心的你可能發(fā)現(xiàn)了,這時(shí)候的文字和圖片是基于底部對(duì)齊的(由于圖片的原因,圖片底部與邊框有一點(diǎn)點(diǎn)的間距)。那么如果我想更改對(duì)齊方式怎么辦呢?
(2)更改圖片與文本的對(duì)齊方式--ALIGN_BASELINE對(duì)齊
設(shè)置對(duì)齊方式的方法很簡(jiǎn)單,在構(gòu)造ImageSpan對(duì)象的時(shí)候,傳入第三個(gè)參數(shù)ALIGN_BASELINE 即可,代碼如下:
ImageSpan imageSpan = new ImageSpan(this, R.mipmap.ic_launcher, DynamicDrawableSpan.ALIGN_BASELINE);
設(shè)置對(duì)齊方式為ALIGN_BASELINE后的效果圖:
咦,看著跟上面的圖沒啥區(qū)別啊?那么我再把上面沒設(shè)置對(duì)齊方式的圖拉下來看下:
仔細(xì)對(duì)比下,我們發(fā)現(xiàn),設(shè)置對(duì)齊方式之后,圖往上跑了一點(diǎn)點(diǎn)。
其實(shí),在ImageSpan 中,官方只給出了兩中對(duì)齊方式:
一種是 ALIGN_BOTTOM , 表示與文字內(nèi)容的底部對(duì)齊,如果在構(gòu)造ImageSpan時(shí)沒有傳入對(duì)齊方式,那么默認(rèn)就是這種底部對(duì)齊。
另一中就是 ALIGN_BASELINE, 表示與文字內(nèi)容的基線對(duì)齊。那么,你可能會(huì)問我基線是啥?請(qǐng)繼續(xù)往下看:
3
Paint.FontMetrics 是啥?
(1) Paint.FontMetrics基本介紹
要說基線呢,我們先了解這個(gè)Paint.FontMetircs, 官方對(duì)該類的解釋是:Class that describes the various metrics for a font at a given text size., 意思是說,這玩意兒是繪制文本內(nèi)容時(shí)存儲(chǔ)該文本內(nèi)容位置信息的一個(gè)類。這個(gè)類中有如下五個(gè)字段:
(2) BaseLine 基線到底是啥?
上圖中這5個(gè)字段除了leading 外,其他四個(gè)都是相對(duì)于 基線BaseLine來確定的,那么,到底啥是基線??
先來看一張圖:
如上圖,標(biāo)準(zhǔn)的英文書寫是基于四線三格,其中,我們書寫英文的時(shí)候,都是以第三條線為基準(zhǔn),也就是說,基線就是這個(gè)四線三格中的第三條線!!
(3)Paint.FontMetrics中字段的含義及示意圖
官方文檔中對(duì)這幾個(gè)字段的解釋很簡(jiǎn)單,但理解起來挺費(fèi)勁,直接上圖,圖中的標(biāo)注都是跑代碼之后確定的,如果有不準(zhǔn)確的地方,歡迎指正:
根據(jù)上圖可知:
-
ascent
文字內(nèi)容的頂部到基線的距離。即 ascent=文字內(nèi)容頂部Y坐標(biāo) - 基線Y坐標(biāo)。由于android中坐標(biāo)系是 右下為正,所以得到的ascent實(shí)際是一個(gè)負(fù)數(shù)。
-
descent
文字內(nèi)容的底部到基線的距離。即 descent=文字內(nèi)容底部Y坐標(biāo) - 基線Y坐標(biāo)。
-
基線
在圖中,基線的坐標(biāo)用Y表示,在ImageSpan父類的 draw( ) 中,會(huì)傳入一個(gè) float Y ,就是這個(gè)基線的坐標(biāo)。實(shí)際上,基線的Y坐標(biāo)=文字內(nèi)容中間線Y坐標(biāo)+1/2 (文字內(nèi)容高度)
-
top
對(duì)應(yīng)圖中 文字所在行的top 坐標(biāo)
-
bottom
對(duì)應(yīng)圖中 文字所在行的bottom 坐標(biāo)
需要注意:如果設(shè)置了行間距,且文本內(nèi)容產(chǎn)生了換行,那么這個(gè)bottom 也會(huì)將行間距包裹。所以, 圖中藍(lán)色的文字內(nèi)容中間線的Y軸坐標(biāo)并不一定等于 (bottom+top)/2
4
自定義ImageSpan實(shí)現(xiàn)文字與圖片居中對(duì)齊
好了,前面說了那么多,終于進(jìn)入正題了。。。
在上面的2 SpannableString+ImageSpan實(shí)現(xiàn)圖文混排中,我們已經(jīng)知道官方并沒有給出文字與圖片居中對(duì)齊的模式,所以需要我們自定義。
關(guān)于自定義ImageSpan的分析,已經(jīng)有前輩講解過了,此處不再贅述,請(qǐng)參考http://blog.csdn.net/gaoyucindy/article/details/39473135。
但是,按照該文章中的代碼實(shí)現(xiàn)的時(shí)候,有個(gè)問題就是:如果給TextView設(shè)置了行間距,且文本產(chǎn)生了換行,那么就無法對(duì)齊了!!
那么,設(shè)置了行間距之后,該如何實(shí)現(xiàn)文本和圖片的居中對(duì)齊呢?也有前輩分析過了,請(qǐng)看:http://www.cnblogs.com/withwind318/p/5541267.html, 但是,這篇文章中的實(shí)現(xiàn)方式?jīng)]有重寫 getSize( ) 方法,所以也有一個(gè)問題:文本和圖片并不是在TextView的居中位置,而且如果圖片高于文本的話,圖片會(huì)顯示不全!!如下圖:
參考了那么多了,終于該給出我的終極方案了!!
根據(jù)上面鏈接中兩位前輩的分析,其實(shí)我們自定義的時(shí)候,需要做的事情是 獲取文本內(nèi)容的中間線以及圖片的中間線,然后獲取兩者差值,然后在draw方法中繪制圖片時(shí)將差值作為canvas.translate(x, transY) 中的transY;同時(shí)要重寫 getSize( )。
這樣最終實(shí)現(xiàn)的效果是,不論是否設(shè)置行間距,不論圖片大于文本還是文本大于圖片,都能實(shí)現(xiàn)文本和圖片的居中對(duì)齊!
看最終效果圖:
上代碼:
在Activity中使用:
xml布局文件:
上面的已經(jīng)是完整代碼了,如果想直接下載運(yùn)行,請(qǐng)到gitHub下載:https://github.com/CnPeng/CrazyAndroid
該倉庫中的b_01_spannableString_ImageSpan 對(duì)應(yīng)該文中的內(nèi)容
寫在最后,最近項(xiàng)目太緊了,過了年一直在加班。這次的總結(jié)也很倉促,本來想寫的更細(xì)一些,并且也想把SpannableString的使用完整總結(jié),but 時(shí)間太緊了,先這樣吧,后面時(shí)間充足了再修正吧!
歡迎各位指正文中錯(cuò)誤的地方,一起交流,一起進(jìn)步!
參考鏈接:
http://wangleyiang.iteye.com/blog/1771439
http://blog.csdn.net/gaoyucindy/article/details/39473135
http://www.cnblogs.com/withwind318/p/5541267.html
http://stackoverflow.com/questions/27631736/meaning-of-top-ascentbaseline-descent-bottom-and-leading-in-androids-font
https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B99%5DDrawText.md
總結(jié)
以上是生活随笔為你收集整理的Android中文图混排时文图的居中对齐 FontMetrics以及自定义ImageSpan实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 解决adb调试显示 Connect
- 下一篇: Android 清除png图片的白色背景