記
—— 執劍天涯,從你的點滴積累開始,所及之處,必精益求精。
在實際業務開發中,時常會有這種一段Html格式的標簽,看下圖的情況 :
在 Flutter 中,有點發愁,因為 Flutter 提供的 Text 與 RichText 還解析不了這種格式的,但是你也不能使用 WebView 插件,如果使用了,你會在每一個Item中嵌入一個瀏覽器內核,再強的手機,也會卡,當然肯定不能這樣做,因為這樣就是錯誤的做法。
小編經過大量的嘗試與思考,終于寫出來了一個插件可以來解析了,現分享給大家。
小編依舊,來個pub方式:
dependencies:
flutter_html_rich_text: ^1.0.0
核心方法如下:
///htmlText 就是你的 HTML 片段了
HtmlRichText(
htmlText: txt,
),
如下代碼清單 1-3-1 就是上述圖中的效果:
/// 代碼清單 1-3-1
class TestHtmlPage extends StatefulWidget {
@override
_TestPageState createState()=> _TestPageState();
}
class _TestPageState extends State<TestHtmlPage> {
String txt="<p>長途輪 <h4>高速驅動</h4><span style='background-color:#ff3333'>"
"<span style='color:#ffffff;padding:10px'> 3條立減 購胎抽獎</span></span></p>"
"<p>長途高速驅動輪<span ><span style='color:#cc00ff;'> 3條立減 購胎抽獎</span></span></p>";
@override
Widget build(BuildContext context) {
return Scaffold(
///一個標題
appBar: AppBar(title: Text('A頁面'),),
body: Center(
///一個列表
child: ListView.builder(
itemBuilder: (BuildContext context, int postiont) {
return buildItemWidget(postiont);
},
itemCount: 100,
),
),
);
}
///ListView的條目
Widget buildItemWidget(int postiont) {
return Container(
///內容邊距
padding: EdgeInsets.all(8),
child: Column(
///子Widget左對齊
crossAxisAlignment: CrossAxisAlignment.start,
///內容包裹
mainAxisSize: MainAxisSize.min,
children: [
Text(
"測試標題 $postiont",
style: TextStyle(fontWeight: FontWeight.w500),
),
///html富文本標簽
Container(
margin: EdgeInsets.only(top: 8),
child: HtmlRichText(
htmlText: txt,
),
)
],
),
);
}
}
以下是解析思考 燒腦的實踐
Flutter 應用程序被 Android iOS平臺加載,在原生 Android 中,使用TextView就可輕松實現解析(如下代碼清單2-1),當然在iOS中使用UILabel也可輕松實現(如下代碼清單2-2)。
// Android 原生 TextView加載Html的核心方法
//代碼清單2-1
// MxgsaTagHandler 定義的一個 TagHandler 用來處理點擊事件
lTextView.setText(Html.fromHtml(myContent, null, new MxgsaTagHandler(context)));
lTextView.setClickable(true);
lTextView.setMovementMethod(LinkMovementMethod.getInstance());
iOS UILable
// iOS 原生 UILabel加載Html的核心方法
//代碼清單2-2
//返回的HTML文本 如 <font color='red'></font>
NSString *str=@"htmlText";
NSString *HTMLString=[NSString stringWithFormat:@"<html><body>%@</body></html>", str ];
NSDictionary *options=@{NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute : @(NSUTF8StringEncoding)
};
NSData *data=[HTMLString dataUsingEncoding:NSUTF8StringEncoding];
NSMutableAttributedString * attributedString=[[NSMutableAttributedString alloc] initWithData:data options:options documentAttributes:nil error:nil];
NSMutableParagraphStyle *paragraphStyle=[[NSMutableParagraphStyle alloc] init]; // 調整行間距
paragraphStyle.lineSpacing=8.0;
paragraphStyle.alignment=NSTextAlignmentJustified;
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, attributedString.length)];
[attributedString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:15] range:NSMakeRange(0, attributedString.length)];
_uiLabel.backgroundColor=[UIColor cyanColor];
_uiLabel.numberOfLines=0;
_uiLabel.attributedText=attributedString;
[_uiLabel sizeToFit];
然后對于 Flutter 來講是可以順利的加載原生 View的 ,如下代碼清單 2-3所示就是在Flutter中通過 AndroidView 與 UiKitView來實現。
//Flutter中加載原生View核心方法
//代碼清單2-3
buildAndroidView() {
return AndroidView(
//設置標識
viewType: "com.studyon./text_html",
//參數的編碼方式
creationParamsCodec: const StandardMessageCodec(),
);
}
/// 通過 UiKitView 來加載 iOS原生View
buildUIKitView() {
return UiKitView(
//標識
viewType: "com.studyon./text_html",
//參數的編碼方式
creationParamsCodec: const StandardMessageCodec(),
);
}
于是小編開發了第一波操作,開發了這樣的一個插件來調用原生 View 實現渲染富文本標簽,這個插件使用方式很簡單,如下所示:
HTMLTextWidet(
htmlText: "測試一下",
)
這一步操作真是所謂的騷操作,其實小編在開發前就覺得不太合適,不過以小編的個性,非得嘗試驗證一下,現結果出來了,就是在加載時,由于應用在列表中,使用 HTMLTextWidet 會有短暫的黑屏效果,而且內存出吃不消,如下圖所示:
為什么會黑屏,閑魚技術團隊有過論述在《Flutter中嵌入Native組件的正確姿勢》 以及 文章 《深入了解Flutter界面開發中有詳細論述》 。
所以結果是 :不可行。
用 Java 的思想來解析 String 的方式來處理 HTML 字符串,處理成小片段,然后使用Text結合 流式布局 Wrap 來組合,核心代碼如下清單 3-1 所示為解析:
/*
解析標簽
*/
List<TagColorModel> findBackGroundColor(String htmlStr) {
List<TagColorModel> tagColorModelList=[];
List<String> colorSpiltList=[];
String driverAdvertisement=htmlStr;
if (driverAdvertisement !=null) {
colorSpiltList=driverAdvertisement.split("background-color");
for (var i=0; i < colorSpiltList.length; i++) {
TagColorModel itemColorModel=TagColorModel();
String colorsStr=colorSpiltList[i];
List<String> itemSpiltList=colorsStr.split(":#");
for (var j=0; j < itemSpiltList.length; ++j) {
String item=itemSpiltList[j];
String itemColor="";
String itemText="";
try {
if (item.length >=6) {
itemColor=item.toString().substring(0, 6);
if (itemColor.trim().toUpperCase()=="FFFFFF") {
itemColorModel.backGroundColor=ColorUtils.getRandomColor();
} else {
itemColorModel.backGroundColor=new Color(
int.parse(itemColor.trim(), radix: 16) + 0xFF000000);
}
int startIndex=item.indexOf("\">");
int endIndex=item.indexOf("</");
if (startIndex !=-1 && endIndex >=startIndex) {
LogUtil.e("startIndex $startIndex endIndex $endIndex ");
itemText=item.substring(startIndex + 2, endIndex);
LogUtil.e("itemColor $itemColor itemText $itemText ");
itemColorModel.text=itemText;
tagColorModelList.add(itemColorModel);
}
}
} catch (e) {
///解析異常的 不必處理
}
}
}
}
LogUtil.e("${tagColorModelList.length} \n\n ");
return tagColorModelList;
}
然后 TagColorModel 的定義如下代碼清單 3-2所示:
///代碼清單 3-2
class TagColorModel {
///背景
Color backGroundColor;
///文本顏色
Color textColor;
///文本
String text;
TagColorModel(
{this.text="",
this.backGroundColor=Colors.transparent,
this.textColor=Colors.white});
}
然后就是使用 Wrap 來使用解析的內容,如下代碼清單3-3所示:
///代碼清單 3-3
///獲取背景顏色
List<TagColorModel> colorList=findBackGroundColor(htmlStr);
List<Widget> tagList=[];
for (var i=0; i < colorList.length; ++i) {
TagColorModel model=colorList[i];
tagList.add(Container(
margin: EdgeInsets.only(right: 2, left: 4, top: 4),
padding: EdgeInsets.only(left: 6, right: 6),
decoration: BoxDecoration(
color: model.backGroundColor,
borderRadius: BorderRadius.all(Radius.circular(2)),
),
child: Text(
"${model.text}",
style: TextStyle(fontSize: 12, color: model.textColor),
),
));
}
///然后再使用 Wrap 包裹
Wrap(
alignment: WrapAlignment.spaceBetween,
children: tagList,
),
實踐結果:可行,但是有兼容性差,效率低。
當然閑魚團隊在文章《如何低成本實現Flutter富文本,看這一篇就夠了!》 中也有詳細論述過,與上述的思路差不多。
當在Flutter中 Dart 從網站中提取數據時,html依賴庫是一個不錯的選擇,html 是一個開源的 Dart 包,主要用于從 HTML 中提取數據,從中獲取節點的屬性、文本和 HTML以及各種節點的內容。
dependencies:
html: ^0.14.0+3
于是乎小編也開始嘗試,首先是使用 Html 庫解析 HTML文本塊,將解析的 Document 通過遞歸方式遍歷出來所有的 node 節點,如下代碼清單4-1所示:
代碼清單4-1
import 'package:html/parser.dart' as parser;
import 'package:html/dom.dart' as dom;
List<Widget> parse(String originHtmlString) {
// 空格替換 去除所有 br 標簽用 \n 代替,
originHtmlString=originHtmlString.replaceAll('<br/>', '\n');
originHtmlString=originHtmlString.replaceAll('<br>', '\n');
originHtmlString=originHtmlString.replaceAll('<br />', '\n');
///html 依賴庫解析
dom.Document document=parser.parse(originHtmlString);
///獲取 DOM 中的 node 節點
dom.Node cloneNode=document.body.clone(true);
// 注意: 先序遍歷找到所有關鍵節點(由于是引用傳值,所以需要重新獲取一遍 hashCode)
List<dom.Node> keyNodeList=new List<dom.Node>();
int nodeIndex=0;
///遞歸遍歷
parseNodesTree(cloneNode, callBack: (dom.Node childNode) {
if (childNode is dom.Element &&
truncateTagList.indexOf(childNode.localName) !=-1) {
print('TEST: truncate tag nodeIndex=${nodeIndex++}');
keyNodeList.add(childNode);
// 注意: 對于占據整行的圖片也作為關鍵節點處理
} else if (childNode is dom.Element &&
childNode.localName=='img' &&
checkImageNeedNewLine(childNode)) {
print('TEST: one line image nodeIndex=${nodeIndex++}');
keyNodeList.add(childNode);
}
});
}
///遞歸遍歷
void parseNodesTree(dom.Node node,
{NodeTreeCallBack callBack=printNodeName}) {
///遍歷 Node 節點
for (var i=0; i < node.nodes.length; ++i) {
dom.Node item=node.nodes[i];
callBack(item);
parseNodesTree(item, callBack: callBack);
}
}
然后就是將 得出的 node 節點 與 Flutter 組件映射,文本使用 TextSpan ,圖片使用 Image ,然后將 樣式使用 TextStyle 映射,然后最后將解析的結果組件使用 Wrap 來包裹,就達到了現在的插件 flutter_html_rich_text 。
綜合實現思路就是 使用 HTML 庫完善了【燒腦思考實踐二】中的解析。
解析篇幅較長,大家有興趣可以看下 github 源碼。
完畢
者博客
http://www.jianshu.com/u/0fa6f5d09040
文章目錄
前言
場景
實現方式
drawable屬性
Spannable使用
HTML顯示
總結
0
前言
在使用TextView的時候,我們經常需要在TextView中進行圖文混排,比如在QQ中聊天的消息中的表情,底部tab圖標等。
1
場景
2
實現方式
Android官方對TextView的圖文混排提供了支持,我們可以從以下三種方式實現TextView的圖文混排:
1.在TextView中使用Compound Drawable屬性;
2.在TextView中使用Spannable多樣式顯示;
3.在TextView中顯示HTML文本。
3
drawable屬性
在TextView中使用Compound Drawable屬性可以在文字的上下左右放置drawable,效果如下:
一共有兩種方式可以實現:XML布局設置和Java代碼設置。
1. xml布局
2. java代碼
注意:必須setBounds測量圖片邊界,否則不顯示。
3.缺陷
當TextView設置成固定大小時,由于文字距離邊界的距離過大,會導致文字與圖片之間設置的間距無效,如下圖。
解決方案:
①設置TextView的內填充
通過設置paddingLeft、paddingRight、paddingTop、paddingBottom來縮寫這個間距
②自定義TextView重新布局
a.先自定義屬性iconPadding來設置間距,并提供方法給外部調用。
b.重寫setCompoundDrawablesWithIntrinsicBounds方法來獲取我們設置的drawable寬高。
c.最后重寫onLayout方法。
可以先參考:Android技巧之drawablePadding的那些事(https://yuxingxin.com/2015/11/05/DrawablePadding/),該篇文章只解決了左右失效的問題。后期會整理個解決圖文混排的工具庫,里面會有具體方案。
4
Spannable使用
1.簡介
setText(CharSequence text)中接收的是CharSequence。而SpannableString和SpannableStringBuilder是其實現類,是可以直接賦值的。并且兩者的setSpan方法可以設置一些格式對象(例如字體大小、下劃線、替換為圖片等),這就可以實現富文本了。
Spannable實現子類:SpannableString,SpannableStringBuilder(可變,類似于StringBuilder)。
Spannable中定義了抽象方法:setSpan(Object what, int start, int end, int flags)和removeSpan(Object what)。這兩個方法實現了對字符串的靈活編輯。
其中setSpan方法包含如下參數:
flags常用的有四種
通常在insert方式才生效,平時不生效,具體看:Explain the meaning of Span flags like SPAN_EXCLUSIVE_EXCLUSIVE。(https://stackoverflow.com/questions/9879233/explain-the-meaning-of-span-flags-like-span-exclusive-exclusive)
2.常用span類
3.使用方式
其中ImageSpan默認對其方式有兩種:ALIGN_BOTTOM及ALIGN_BASELINE。很可惜我們平常用的居中對其的方式沒有,不過可以通過自定義實現,后續會在開源出來。
4.效果
5
HTML顯示
一般顯示HTML內容有兩種方式:
使用 Android 提供的 WebView 控件。
通過將 HTML 內容轉化為 Spanned 格式在 TextView 中進行顯示。
現在大多數都用WebView的方式。但是并不是所有的場景下都適合使用 WebView 來顯示 HTML 內容,例如,如果應用要顯示的內容只是一部分 HTML 片段,就可以利用 TextView 來進行顯示,并且效率較高。
由于這種方式不太常用,就不深入介紹,里面可以實現的效果還是很好的。
1.簡介
Android 中的 TextView 組件常用于顯示文本內容,其實它也可以顯示 HTML 的內容。
簡單來講,這就需要先把 HTML 的內容以字符串的形式獲取后,經過 android.text.Html.fromHtml轉化成 Spanned 的格式,然后將其傳遞到 TextView 的 setText方法中,這樣就可以在 TextView 中顯示 HTML 頁面的內容了。
需要注意的是,并不是所有的 HTML 標簽在 TextView 中都是支持的,且官方文檔并沒有明確的說明支持 HTML 標簽列表,通過查看 Android 源代碼,可以得到簡單的支持列表。
下面的示例來介紹如何在 TextView 中顯示一段 HTML 內容,要顯示的這段 HTML 內容即包含超鏈接內容,也包含有圖片。
2.使用
fromHtml方法
source,就是包含 HTML 內容的字符串。Html.ImageGetter 和 Html.TagHandler 是兩個接口,提供給開發者繼承使用。
imageGetter, 如果要顯示圖片是需要被繼承的,重寫 getDrawable(String source)方法,用于獲取 HTML 里面的圖片來顯示在 TextView 中。
tagHandler,其作用是把 HTML 帶標記的文本內容字符串轉化成可以顯示效果的的 Spanned 字符串 。由于并非所有的 HTML 標簽都可以轉化,所以在使用時,用戶需要自己添加一些必要的標簽和處理方法時才會繼承使用的。
繼承ImageGetter
繼承于 ImageGetter,重寫 getDrawable (String source) 方法。通過異步操作,讀取本地/網絡資源,獲得drawable對象。
繼承TagHandler
繼承于 TagHandler,重寫了 handleTag方法。為了支持更多的標簽,例如為了支持<ul><ol><dd>和<li>標簽,這四個標簽是在 formHtml方法中本身是不支持。
如果開發者認為安卓 TagHandler 提供的默認標簽解析已經夠用,直接在 fromHtml方法中第三個參數的地方填寫 既可。
最后,通過 formHtml方法將 HTML 內容轉化為可供顯示的 SpannableString,將 SpannableString 通過 setText 方法放入 TextView 中,就可以顯示圖文并茂的內容了。
用戶交互
formHtml方法已經將 HTML 內容中的超鏈接和圖片轉義成為 UrlSpan 和 ImageSpan,進而在 TextView 中完成顯示。但是此時是沒有任何用戶交互的,用戶只能看到 HTML 的內容,下面介紹如何添加用戶交互功能。
要完成用戶交互,這里我們需要在 TextView 中還需要調用textView.setMovementMethod方法。
Android 提供了 LinkMovementMethod 類以實現了對于文本內容中超鏈接的遍歷,并且支持對于超鏈接的點擊事件。
所以只要在添加下面一行代碼,就可以使點擊 UrlSpan 能夠觸發打開鏈接的功能。
如果想要更多的用戶交互效果,可以自定義LinkMovementMethod 類,重寫onTouchEvent方法來實現。
3.效果
關于HTML顯示這部分,沒做具體實現。具體可以看:靈活高效的在 Android Native App 開發中顯示 HTML 內容(https://www.ibm.com/developerworks/cn/web/1407_zhangqian_androidhtml/index.html),里面有具體源碼可以下載,HTML部分內容也是參考該篇文章完成的。
開源庫:html-textview
https://github.com/PrivacyApps/html-textview
6
總結
以上就是關于圖文混排的一些解決方案,相信通過這些了解,對于工作中的實際場景的使用大家會有適當的解決方案。由于實際應用較少,所以認識較為淺顯,可能有些地方描述不當,后期會考慮封裝個解決圖文混排的工具類,加深下理解。
TextView添加背景,或者是給TextView添加添加邊框,以及怎么樣設置TextView的形狀。怎么在java代碼部分設置TextView的背景,和TextView的形狀及邊框。
方法如下:
怎么在Java代碼部分怎么設置TextView的背景顏色,其實很簡單的就一句話。
tvTemp.setBackgroundColor(Color.parseColor("#00FF00"));
在xml布局文件中就可以直接調用drawable文件代碼如下:
android:background="@drawable/textview"
在設置背景的時候, 我們都知道使用 setBackgroundColor()方法,但是,方法里面的參數,必須是RGB HTML格式的值,如果我們用drawable,它會提示drawable是int類型的。(其實如果可以的話我們不妨使用ImageView組件,這個組件相對TextView更好用)。
接下來就來看看怎么給讓TextView顯示邊框,并且怎么樣讓其顯示圓形。這里我們就需要在drawable里面,新建一個.xml文件。
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval"
android:useLevel="false">
<solid android:color="#00FF00" />
<stroke android:width="1dip" android:color="#000000" />
<size
android:width="15dp"
android:height="15dp" />
</shape>
這只是一個圓形,其中, stroke屬性,是設置的他的邊框顏色和寬度,在xml布局中顯示的是如圖所示的樣式:
xml樣式
可以在xml文件中利用drawable調用。其中在xml中不僅可以設置圓形,而且還可以設置圓角,
<corners android:radius="15dp" />屬性就是設置圓角
我們在介紹一些關于shape里面的知識:
gradient -- 顏色漸變
startcolor 起點顏色
endcolor 終點顏色
android:angle 角度 0是從左到右,90是從下到上
solid -- 填充
stroke -- 描邊
corners -- 圓角
padding -- 內容離邊界的距離
當需要在java代碼中需要設置TextView時,發現通過上面的方法設置,圓角就會消失,在這里怎么讓圓角不會消失,我們需要:
*請認真填寫需求信息,我們會在24小時內與您取得聯系。