Solutions for rich file tags in Flutter
Inscription
- Holding the sword in the world, starting from your accumulation, you will strive for perfection wherever you go.
【x1】Click to view the tips
[x2] Various series of tutorials
A programmer's practice diary
In actual business development, there is often such a tag in Html format, see the following picture:
In Flutter , I'm a little worried, because the Text and RichText provided by Flutter can't parse this format, but you can't use the WebView plug-in. If you use it, you will embed a browser kernel in each Item, no matter how strong it is. The mobile phone will also be stuck, of course, it must not be done, because this is the wrong way.
After a lot of attempts and thinking, the editor finally wrote a plug-in that can be analyzed, and now I will share it with you.
1 Basic use implementation
1.2 Add dependencies
The editor is still the same, here is a pub method: [Needless to say, the shortcut entry is here] [Of course there is also github] [There is also video support for exaggeration]
dependencies:
flutter_html_rich_text: ^1.0.0
- 1
- 2
1.3 Load and parse HTML fragment tags
The core method is as follows:
///htmlText is your HTML fragment
HtmlRichText (
htmlText : txt ,
) ,
- 1
- 2
- 3
- 4
The following code listing 1-3-1 is the effect of the above figure:
/// Code Listing 1-3-1
class TestHtmlPage extends StatefulWidget {
@override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestHtmlPage> {
String txt =
"<p>long-distance wheel<h4>high-speed drive</h4><span style='background-color:#ff3333'>"
"<span style='color:#ffffff;padding:10px'> 3 Immediately Reduced Tire Purchase Raffle</span></span></p>"
"<p>Long-distance high-speed drive wheels<span><span style='color:#cc00ff;'> 3 Immediately Reduced Tire Purchase Raffle Draw</span> span></span></p>" ;
@override
Widget build ( BuildContext context ) {
return Scaffold (
/// a title
appBar : AppBar ( title : Text ( 'A Page' ) , ) ,
body : Center (
/// a list
child : ListView . builder (
itemBuilder : ( BuildContext context , int postiont ) {
return buildItemWidget ( postiont);
},
itemCount: 100,
),
),
);
}
///ListView's item
Widget buildItemWidget ( int postiont ) {
return Container (
/// Content margin
padding : EdgeInsets . all ( 8 ) ,
child : Column (
/// Left-aligned child Widget
crossAxisAlignment : CrossAxisAlignment . start ,
///Content wrapping
mainAxisSize : MainAxisSize .min , children
: [ Text ( " Test Title $postiont " ,
style : TextStyle ( fontWeight : FontWeight . w500 ) , ) ,
///html Rich Text Label
Container (
margin : EdgeInsets . only ( top : 8 ) ,
child : HtmlRichText (
htmlText : txt ,
) ,
)
] ,
) ,
) ;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
The following is the practice of analysing thinking and burning the brain
2 Brain-burning thinking practice one
The Flutter application is loaded by the Android iOS platform. In native Android, the parsing can be easily implemented using TextView (Listing 2-1 below), and of course UILabel can be easily implemented in iOS (Listing 2-2 below).
// Core method of loading Html in Android native TextView
// Code Listing 2-1
// A TagHandler defined by MxgsaTagHandler is used to handle click events
lTextView . setText ( Html . fromHtml ( myContent , null , new MxgsaTagHandler ( context ) ) ) ;
lTextView .setClickable ( true ) ;
lTextView.setMovementMethod ( LinkMovementMethod.getInstance ( ) ) ; _ _ _ _ _
- 1
- 2
- 3
- 4
- 5
- 6
// The core method of loading Html in iOS native UILabel
// Code Listing 2-2
// The returned HTML text is such as <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];
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
Then for Flutter, the native View can be loaded smoothly [ there is a description here ], as shown in the following code listing 2-3, which is implemented in Flutter through AndroidView and UiKitView.
//The core method of loading native View in Flutter
//Code Listing 2-3
buildAndroidView ( ) {
return AndroidView (
//Set the logo
viewType : "com.studyon./text_html" ,
//The encoding method of parameters
creationParamsCodec : const StandardMessageCodec ( ) ,
) ;
}
/// Load iOS native View through
UiKitView buildUIKitView ( ) {
return UiKitView (
//identify
viewType : "com.studyon./text_html" ,
//parameter encoding
creationParamsCodec : const StandardMessageCodec ( ) ,
) ;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
So the editor developed the first wave of operations, and developed such a plug-in to call the native View to render rich text labels [source code here] . The use of this plug-in is very simple, as shown below:
HTMLTextWidet (
htmlText : "test it" ,
)
- 1
- 2
- 3
This step is really a so-called show operation. In fact, I thought it was inappropriate before the development. However, due to the personality of the editor, I had to try to verify it. Now the result is out. HTMLTextWidet will have a short black screen effect, and the memory is too much, as shown in the following figure:
Why is the screen black, the Xianyu technical team has discussed the correct posture of embedding Native components in Flutter and the article In -depth understanding of Flutter interface development is discussed in detail .
So the result is: not feasible.
3 Brain-burning thinking practice 2
Use the idea of Java to parse String to process HTML strings, process them into small fragments, and then use Text combined with the flow layout Wrap to combine. The core code is shown in the following list 3-1 for parsing:
/*
Parse tags
*/
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 ) {
// / Parsing exceptions do not have to be handled
}
}
}
}
LogUtil.e("${tagColorModelList.length} \n\n ");
return tagColorModelList;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
Then TagColorModel is defined as shown in Listing 3-2:
///Code Listing 3-2
class TagColorModel {
///Background
Color backGroundColor ;
///Text Color
Color textColor ;
///Text
String text ;
TagColorModel(
{this.text = "",
this.backGroundColor = Colors.transparent,
this.textColor = Colors.white});
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
Then use Wrap to use the parsed content, as shown in Listing 3-3 below:
///Code Listing 3-3
///Get Background Color
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),
),
));
}
/// Then use Wrap to wrap
Wrap (
alignment : WrapAlignment . spaceBetween ,
children : tagList ,
) ,
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
Practical result: feasible, but has poor compatibility and low efficiency.
Of course, the Xianyu team has an article on how to implement rich text in Flutter at low cost. It is enough to read this article! is also discussed in detail.
4 Brain-burning thinking practice III
When Dart extracts data from a website in Flutter, the html dependency library is a good choice. html is an open source Dart package, which is mainly used to extract data from HTML, obtain node attributes, text and HTML, and various The content of the node. Html pub repository
dependencies:
html: ^0.14.0+3
- 1
- 2
So Xiaobian also started to try, first of all, use the Html library to parse the HTML text block, and traverse all the node nodes of the parsed Document recursively, as shown in the following code listing 4-1:
Code Listing 4-1
import 'package:html/parser.dart' as parser ;
import 'package:html/dom.dart' as dom ;
List < Widget > parse ( String originHtmlString ) {
// Space replacement removes all br tags and replaces them with \n,
originHtmlString = originHtmlString . replaceAll ( '<br/>' , '\n' ) ;
originHtmlString = originHtmlString . replaceAll ( '< br>' , '\n' ) ;
originHtmlString = originHtmlString . replaceAll ( '<br />' , '\n' ) ;
///html Dependency library parsing
dom . Document document = parser . parse ( originHtmlString ) ;
///Get the node node
dom in DOM . Node cloneNode = document . body . clone ( true ) ;
// Note: Pre-order traversal finds all key nodes (because it is passed by reference, so you need to get the hashCode again)
List < dom . Node > keyNodeList = new List < dom . Node > ( ) ;
int nodeIndex = 0 ;
// / Recursively traverse
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 ) ;
// Note: images that occupy the entire row are also treated as key nodes
} else if ( childNode is dom . Element &&
childNode . localName == 'img' &&
checkImageNeedNewLine ( childNode ) ) {
print ('TEST: one line image nodeIndex = ${nodeIndex++}');
keyNodeList.add(childNode);
}
});
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
/// Recursively traverse
void parseNodesTree ( dom . Node node ,
{ NodeTreeCallBack callBack = printNodeName } ) {
/// Traverse Node nodes
for ( var i = 0 ; i < node . nodes . length ; ++ i ) {
dom . Node item = node . nodes [ i ] ;
callBack ( item ) ;
parseNodesTree(item, callBack: callBack);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
Then it is to map the obtained node node with the Flutter component, use TextSpan for text, Image for image, and then use TextStyle to map the style, and finally wrap the parsed result component with Wrap to achieve the current plugin flutter_html_rich_text
The comprehensive implementation idea is to use the HTML library to improve the analysis in [Brain-burning Thinking Practice II].
The analysis is long, and if you are interested, you can look at the github source code.
At present, Xiaobian publishes a series of Flutter tutorials for free on the watermelon video, which is updated daily. Welcome to pay attention to receiving reminders and click to view various series of tutorials.
2020.09.12 Development Notes
- Loading images in Flutter in iOS
- Flutter Component Essay [01] Overview of MateriaApp Usage
- Flutter Component Essay [02] Basic Use of MaterialApp Component
- Flutter components lecture [03] MateriaApp component routing routes configuration
- Flutter Component Essay [04] MateriaApp Configuration Default Startup Page
- Flutter Component Essay [05] Page Jump of MateriaApp
- Flutter Component Essay [06] MaterialApp Component Configuration 404 Page-01
- Flutter Component Essay [07] MaterialApp Component Configuration 404 Page-02