這個標題有點長,乍一看這么個標題你可能沒明白啥意思,且聽我慢慢道來。
公司的項目中新增了一個“心動” 的功能,用戶初次使用時需要給一個引導頁,就是下面圖中的這個樣子(這就是做完之后的效果了)。
在上圖中整體實現的時候使用的是popUpWindow。
該popupWindow整體使用相對布局,里面再用一個相對布局布局嵌套了三個TextView:"啊哦。。。。pass" 用一個TextView,中間灰色的上傳頭像的提示用了一個TextView,底部“我知道了” 也是一個TextView。
上面的左划示意圖使用above 放在 包含TextView的相對布局上方,並通過負的margin值將它下移並覆蓋在包含TextView相對布局上。
這個界面並沒有什么難度,這里重點說的是第一個TextView中的圖文混排,並讓圖片的橫向中間線與該行文字的橫向中間線對齊,也就是說,讓文字與那個💔 圖片的中間在水平方向對齊。
1、圖文混排的方式有哪些?
通常我們向TextView中插入圖片實現圖文混排有如下方式:
1. 使用drawableLeft等屬性設置,這種方式對應的java方法是 setCompoundDrawablesWithIntrinsicBounds(leftDrawble,topDrawable,rightDrawable,bottomDrawable);
2. 使用 SpannableString ,先將圖片轉成ImageSpan對象,然后通過setSpan插入到SpannableString 中,最后再將SpannableString通過setText設置給TextView。(SpannableString 繼承自CharSquence)
3. 此外,還有一種利用Html.ImageGetter格式化圖片的方式。(截止目前為止,我沒用過這種方式,如果想了解的話,可以參考http://wangleyiang.iteye.com/blog/1771439中的第二點)
2、使用SpannableString+ImageSpan怎么實現圖文混排?
(1)基本實現方式
效果圖如下:
實現方式很簡單,我們只需要在xml布局文件中定義一個TextView,然后在代碼中獲取該TextView並創建一個含有圖片的SpannableString,並將該SpannableString通過setText( )設置給TextView即可。
代碼如下:
xml布局文件中只給了一個普通的TextView,代碼省略。
在上面的代碼中,我們通過ImageSpan的構造方法得到了一個ImageSpan對象。該構造方法中傳入的兩個參數分別是上下文和圖片的id。(imageSpan的構造方法還有很多)
SpannbaleString的setSpan方法中,傳入的四個參數分別是 ImageSpan對象、將ImageSpan插入到的起始位置(start)、將ImageSpan插入到的終點位置(end)、是否應用字體樣式。
具體將ImageSpan對象插入到哪個位置,由第二個和第三個參數確定,插入的時候會覆蓋從 start 位置開始(不包含該位置)到終止位置間的內容(包含該位置)。
第四個參數是在你插入文本的時候使用的,用來控制新插入的文本與已有文本內容的字體樣式是否一致的如果你插入的是圖片,這里就可以隨便選擇一種模式。
經過上面雖然實現了圖文混排,但是,細心的你可能發現了,這時候的文字和圖片是基於底部對齊的(由於圖片的原因,圖片底部與邊框有一點點的間距)。那么如果我想更改對齊方式怎么辦呢?
(2)更改圖片與文本的對齊方式--ALIGN_BASELINE對齊
設置對齊方式的方法很簡單,在構造ImageSpan對象的時候,傳入第三個參數ALIGN_BASELINE 即可,代碼如下:
1 ImageSpan imageSpan = new ImageSpan(this, 2 R.mipmap.ic_launcher, 3 DynamicDrawableSpan.ALIGN_BASELINE);
設置對齊方式為ALIGN_BASELINE后的效果圖:
咦,看着跟上面的圖沒啥區別啊?那么我再把上面沒設置對齊方式的圖拉下來看下:
仔細對比下,我們發現,設置對齊方式之后,圖往上跑了一點點。
其實,在ImageSpan 中,官方只給出了兩中對齊方式:
一種是 ALIGN_BOTTOM , 表示與文字內容的底部對齊,如果在構造ImageSpan時沒有傳入對齊方式,那么默認就是這種底部對齊。
另一中就是 ALIGN_BASELINE, 表示與文字內容的基線對齊。那么,你可能會問我基線是啥?請繼續往下看:
3、Paint.FontMetrics 是啥?
(1) Paint.FontMetrics基本介紹
要說基線呢,我們先了解這個Paint.FontMetircs, 官方對該類的解釋是:Class that describes the various metrics for a font at a given text size., 意思是說,這玩意兒是繪制文本內容時存儲該文本內容位置信息的一個類。這個類中有如下五個字段:
(2) BaseLine 基線到底是啥?
上圖中這5個字段除了leading 外,其他四個都是相對於 基線BaseLine來確定的,那么,到底啥是基線??
先來看一張圖:
如上圖,標准的英文書寫是基於四線三格,其中,我們書寫英文的時候,都是以第三條線為基准,也就是說,基線就是這個四線三格中的第三條線!!
(3)Paint.FontMetrics中字段的含義及示意圖
官方文檔中對這幾個字段的解釋很簡單,但理解起來挺費勁,直接上圖,圖中的標注都是跑代碼之后確定的,如果有不准確的地方,歡迎指正:
根據上圖可知:
-
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的居中位置,而且如果圖片高於文本的話,圖片會顯示不全!!如下圖:
參考了那么多了,終於該給出我的終極方案了!!
根據上面鏈接中兩位前輩的分析,其實我們自定義的時候,需要做的事情是 獲取文本內容的中間線以及圖片的中間線,然后獲取兩者差值,然后在draw方法中繪制圖片時將差值作為canvas.translate(x, transY) 中的transY;同時要重寫 getSize( )。
這樣最終實現的效果是,不論是否設置行間距,不論圖片大於文本還是文本大於圖片,都能實現文本和圖片的居中對齊!
看最終效果圖:
上代碼:
在Activity中使用:
xml布局文件:
上面的已經是完整代碼了,如果想直接下載運行,請到gitHub下載:https://github.com/CnPeng/CrazyAndroid
該倉庫中的b_01_spannableString_ImageSpan 對應該文中的內容