背景
最近我們的產(chǎn)品來了個新的模塊,給學(xué)生做題提高成績的。需求如下:
- 支持單選、多選、填空題
- 支持圖片文字混排
- 輸入框有交互,排版精致美觀
- 為了體驗優(yōu)化,不能使用網(wǎng)頁實現(xiàn)效果
思路分析
我們的圖文混排控件繼承自TextView,重寫了關(guān)鍵的測量onMeasure和繪制onDraw步驟
測量決定了混排里的元素的位置、尺寸;
繪制決定了如何去渲染這些元素(以及通知輸入框位置)
1:圖片文字混排
系統(tǒng)的TextView通過ImageGetter就可以原生實現(xiàn)對文本img標(biāo)簽的支持,從而達(dá)到圖文混排的效果。
具體實現(xiàn)步驟請參考Github上的一個庫https://github.com/zzhoujay/RichText
2:輸入框混排
如何實現(xiàn)一個自己的TextView,請參考此篇博客http://blog.csdn.net/yellowcath/article/details/27527275
下面是我具體的實現(xiàn)步驟,請在步驟1圖文混排源碼基礎(chǔ)上進(jìn)行閱讀。
最后的UI結(jié)構(gòu)應(yīng)該是這樣子的,用輸入框蓋在透明的占位圖上,達(dá)到了輸入框混排的效果。

如果是列表里展示結(jié)果,不涉及輸入框的。那就只有一個圖文混排控件,把輸入框轉(zhuǎn)換成下劃線____即可
2.0:根據(jù)題目答案數(shù)量,生成對應(yīng)數(shù)量的輸入框,添加到容器中,設(shè)置為不可見
2.1:題目中的輸入框標(biāo)記,通過正則表達(dá)式使用圖片Img標(biāo)簽代替,src設(shè)置為:Input標(biāo)簽+輸入框?qū)?yīng)答案ID+該輸入框?qū)?yīng)答案文字長度
2.2:在ImageGetter中,發(fā)現(xiàn)當(dāng)前圖片包含Input標(biāo)簽,則使用一個透明的占位圖代替,寬度為畫筆的文本尺寸*答案長度
2.3:參考本步驟開始給出的代碼地址,稍微加工下就可以拿來實現(xiàn)自己的TextView,在onDraw事件里,就能獲取當(dāng)前的x、y值。
2.4:在onDraw事件里,遇到當(dāng)前繪制的Span是圖片類型,且src包含Input標(biāo)簽的,獲取當(dāng)前的高度和左邊距,通過下面代碼獲取圖片的寬度。imgSpan.getDrawable().getBounds().width()
2.5:用步驟4取到的數(shù)據(jù),設(shè)置MarginLeft和MarginTop,還有寬度,通過輸入框?qū)?yīng)答案ID來找到2.0對應(yīng)的輸入框,刷新其位置和寬度

最終實現(xiàn)的效果如如下,滿足產(chǎn)品需求


FAQ
下面是一些網(wǎng)友的提問
1 問題:效果圖里,文字垂直居中對齊,怎么做到的
回答:
原生TextView是底部對齊的,我們自己實現(xiàn)的圖文混排是可以自由定制垂直對齊方式的,參考我給出的第二個博客地址,他里面會記錄每一行的行高,然后在繪制的時候,判斷當(dāng)前行高是否大于標(biāo)準(zhǔn)行高(我們填空題或者選擇題都會有一個初始行高。)
如果當(dāng)前行高>默認(rèn)行高,則說明此行有大圖,文本垂直居中=當(dāng)前高度+(行高-文本高度)/2;
如果當(dāng)前行高==默認(rèn)行高,判斷圖片的高度如果小于默認(rèn)行高,則圖片垂直居中=當(dāng)前高度+(行高-圖片高度)/2;
2 請問返回的數(shù)據(jù)有處理不了的標(biāo)簽怎么處理,還有怎么實現(xiàn)如果返回的圖片寬度大于一屏不被截圖的問題呢?
回答:
1:標(biāo)簽和服務(wù)端溝通好,只返回有用的標(biāo)簽。一些數(shù)學(xué)符號用png圖片代替。
2:Html.ImageGetter,在請求返回Bitmap的時,對Bitmap的寬度進(jìn)行判斷(bitmap.getWidth()),如果超過最大寬度,則使用最大寬度,同時拿到縮放比率,對高度進(jìn)行等比處理。
3 填空輸入功能看了您的文章還是不太明白,請問您三個關(guān)于輸入框排版的問題。
1、輸入框怎么拿到input位置。
2、輸入框怎么刷新位置。
3、輸入框怎么與文字對齊居中。
回答:
第一個問題輸入框怎么拿到input位置。
1:input我們實際上也是轉(zhuǎn)換成一個圖片標(biāo)簽<img src="input_A">
2:參考http://blog.csdn.net/yellowcath/article/details/27527275如何實現(xiàn)自定義TextView;里面ondraw的時候,判斷當(dāng)前是個imageSpan,并且src是input_A
3:滿足2,則判定,該image標(biāo)簽是個輸入框標(biāo)簽。如果你仔細(xì)看過2步驟的博客;則知道自定義TextView是逐行繪制標(biāo)簽的,我們在繪制的時候,是可以拿到當(dāng)前的x和y的。
第二個問題輸入框怎么刷新位置。
還記得一步驟的src里面的input_A嗎?這是我們自定義的標(biāo)簽,第二個標(biāo)簽則是input_B。以此類推
input_A 我們對應(yīng)創(chuàng)建一個輸入框A,放進(jìn)一個hashMap。
逐行繪制的時候,我們拿到x、y,則更新該輸入框的x、y就好了。
第三個問題輸入框怎么與文字對齊居中。
參考問題2給出的答案
補(bǔ)充
這個圖文混排模塊,我在17年的時候就已經(jīng)完成上線了,最早發(fā)布在博客園https://www.cnblogs.com/kimmy/p/5027299.html
到今天3年過去,期間的迭代主要圍繞著渲染性能、各種格式文本支持,標(biāo)簽(<u><b><i>)嵌套擴(kuò)展性等迭代,主體測量、繪制流程大體上就是文中所述。
安卓原生的Html.fromHtml在處理一些標(biāo)簽的時候,需要額外預(yù)處理下比如
*
* <p>
* 將不是html標(biāo)簽的< 替換成< 不然{@link android.text.Html#fromHtml(String)}會丟失,比如下面數(shù)據(jù)只有a, "a<b 巴拉巴拉";
* 把不是引號里的空格 替換成 不然{@link android.text.Html#fromHtml(String)}會將多個空格合并成一個
* 把不是引號里的\n 替換成 <br/>;不然{@link android.text.Html#fromHtml(String)}處理后,課后堂堂清渲染引擎識別不了換行符號
* <p>
* 輸入:<b>c<d </br>( ) \n
* 輸出:<b>c<d </br>( )<br/>