概述
Android中的TextView,本身就支持部分的Html格式标签。这其中包括常用的字体大小颜色设置,文本链接等。使用起来也比较方便,只需要使用Html类转换一下即可。比如:
textView.setText(Html.fromHtml(str));
然而,有一种场合,默认支持的标签可能不够用。比如,我们需要在textView中点击某种链接,返回到应用中的某个界面,而不仅仅是网络连接,如何实现?
经过几个小时对android中的Html类源代码的研究,找到了解决办法,并且测试通过。
先看Html类的源代码中有这样一段:
- /**
- * Is notified when HTML tags are encountered that the parser does
- * not know how to interpret.
- */
- public static interface TagHandler {
- /**
- * This method will be called whenn the HTML parser encounters
- * a tag that it does not know how to interpret.
- */
- public void handleTag(boolean opening, String tag,
- Editable output, XMLReader xmlReader);
这里定义了一个接口,接口用于什么呢?
再继续看代码,看到对Html的tag进行解析部分的代码:
- private void handleStartTag(String tag, Attributes attributes) {
- if (tag.equalsIgnoreCase("br")) {
- // We don't need to handle this. TagSoup will ensure that there's a </br> for each <br>
- // so we can safely emite the linebreaks when we handle the close tag.
- } else if (tag.equalsIgnoreCase("p")) {
- handleP(mSpannableStringBuilder);
- } else if (tag.equalsIgnoreCase("div")) {
- handleP(mSpannableStringBuilder);
- } else if (tag.equalsIgnoreCase("em")) {
- start(mSpannableStringBuilder, new Bold());
- } else if (tag.equalsIgnoreCase("b")) {
- start(mSpannableStringBuilder, new Bold());
- } else if (tag.equalsIgnoreCase("strong")) {
- start(mSpannableStringBuilder, new Italic());
- } else if (tag.equalsIgnoreCase("cite")) {
- start(mSpannableStringBuilder, new Italic());
- } else if (tag.equalsIgnoreCase("dfn")) {
- start(mSpannableStringBuilder, new Italic());
- } else if (tag.equalsIgnoreCase("i")) {
- start(mSpannableStringBuilder, new Italic());
- } else if (tag.equalsIgnoreCase("big")) {
- start(mSpannableStringBuilder, new Big());
- } else if (tag.equalsIgnoreCase("small")) {
- start(mSpannableStringBuilder, new Small());
- } else if (tag.equalsIgnoreCase("font")) {
- startFont(mSpannableStringBuilder, attributes);
- } else if (tag.equalsIgnoreCase("blockquote")) {
- handleP(mSpannableStringBuilder);
- start(mSpannableStringBuilder, new Blockquote());
- } else if (tag.equalsIgnoreCase("tt")) {
- start(mSpannableStringBuilder, new Monospace());
- } else if (tag.equalsIgnoreCase("a")) {
- startA(mSpannableStringBuilder, attributes);
- } else if (tag.equalsIgnoreCase("u")) {
- start(mSpannableStringBuilder, new Underline());
- } else if (tag.equalsIgnoreCase("sup")) {
- start(mSpannableStringBuilder, new Super());
- } else if (tag.equalsIgnoreCase("sub")) {
- start(mSpannableStringBuilder, new Sub());
- } else if (tag.length() == 2 &&
- Character.toLowerCase(tag.charAt(0)) == 'h' &&
- tag.charAt(1) >= '1' && tag.charAt(1) <= '6') {
- handleP(mSpannableStringBuilder);
- start(mSpannableStringBuilder, new Header(tag.charAt(1) - '1'));
- } else if (tag.equalsIgnoreCase("img")) {
- startImg(mSpannableStringBuilder, attributes, mImageGetter);
- } else if (mTagHandler != null) {
- mTagHandler.handleTag(true, tag, mSpannableStringBuilder, mReader);
- }
- }
- private void handleEndTag(String tag) {
- if (tag.equalsIgnoreCase("br")) {
- handleBr(mSpannableStringBuilder);
- } else if (tag.equalsIgnoreCase("p")) {
- handleP(mSpannableStringBuilder);
- } else if (tag.equalsIgnoreCase("div")) {
- handleP(mSpannableStringBuilder);
- } else if (tag.equalsIgnoreCase("em")) {
- end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));
- } else if (tag.equalsIgnoreCase("b")) {
- end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));
- } else if (tag.equalsIgnoreCase("strong")) {
- end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
- } else if (tag.equalsIgnoreCase("cite")) {
- end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
- } else if (tag.equalsIgnoreCase("dfn")) {
- end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
- } else if (tag.equalsIgnoreCase("i")) {
- end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
- } else if (tag.equalsIgnoreCase("big")) {
- end(mSpannableStringBuilder, Big.class, new RelativeSizeSpan(1.25f));
- } else if (tag.equalsIgnoreCase("small")) {
- end(mSpannableStringBuilder, Small.class, new RelativeSizeSpan(0.8f));
- } else if (tag.equalsIgnoreCase("font")) {
- endFont(mSpannableStringBuilder);
- } else if (tag.equalsIgnoreCase("blockquote")) {
- handleP(mSpannableStringBuilder);
- end(mSpannableStringBuilder, Blockquote.class, new QuoteSpan());
- } else if (tag.equalsIgnoreCase("tt")) {
- end(mSpannableStringBuilder, Monospace.class,
- new TypefaceSpan("monospace"));
- } else if (tag.equalsIgnoreCase("a")) {
- endA(mSpannableStringBuilder);
- } else if (tag.equalsIgnoreCase("u")) {
- end(mSpannableStringBuilder, Underline.class, new UnderlineSpan());
- } else if (tag.equalsIgnoreCase("sup")) {
- end(mSpannableStringBuilder, Super.class, new SuperscriptSpan());
- } else if (tag.equalsIgnoreCase("sub")) {
- end(mSpannableStringBuilder, Sub.class, new SubscriptSpan());
- } else if (tag.length() == 2 &&
- Character.toLowerCase(tag.charAt(0)) == 'h' &&
- tag.charAt(1) >= '1' && tag.charAt(1) <= '6') {
- handleP(mSpannableStringBuilder);
- endHeader(mSpannableStringBuilder);
- } else if (mTagHandler != null) {
- mTagHandler.handleTag(false, tag, mSpannableStringBuilder, mReader);
- }
- }
可以看到,如果不是默认的标签,会调用mTagHandler的handleTag方法。所以,我们可以实现此接口,来解析自己定义的标签类型。
再看一段我实现的对<game>标签进行解析的示例代码:
- public class GameTagHandler implements TagHandler {
- private int startIndex = 0;
- private int stopIndex = 0;
- @Override
- public void handleTag(boolean opening, String tag, Editable output,
- XMLReader xmlReader) {
- if (tag.toLowerCase().equals("game")) {
- if (opening) {
- startGame(tag, output, xmlReader);
- } else {
- endGame(tag, output, xmlReader);
- }
- }
- }
- public void startGame(String tag, Editable output, XMLReader xmlReader) {
- startIndex = output.length();
- }
- public void endGame(String tag, Editable output, XMLReader xmlReader) {
- stopIndex = output.length();
- output.setSpan(new GameSpan(), startIndex, stopIndex,
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- private class GameSpan extends ClickableSpan implements OnClickListener {
- @Override
- public void onClick(View v) {
- // 跳转某页面
- }
- }
上面这段代码,是对<game>…</game>的自定义标签进行解析。
具体调用方法:
textView.setText(Html.fromHtml(“点击<game>这里</game>跳转到游戏”,
null, new GameTagHandler()));
textView.setClickable(true);
textView.setMovementMethod(LinkMovementMethod.getInstance());
运行后,能够看到文本中的字符串“这里”带了超链接,点击链接后,GameSpan类的onClick()方法被调用。就可以在这个方法中进行跳转了。
看了一下第一种方式,直接使用SpannableString明显是不可行的,因为我们必须知道他的具体长度,那么只能够换一种方式实现了,相信有写过Html的大神们都知道其实Android有一个类叫Html,里面是支持我们Html格式的字符串转换为文本的,那么这时候思路就很清晰了,我们只需要接收Html格式的String,然后使用Html.fromHtml方法就可以将他转换为我们想要的多样式TextView!,马上动手试试。
3.3、代码
我们定义一个String假装他是服务器传递过来的数据进行显示看看结果是怎么样的,首先是来一个html格式的字符串
String htmlStr = "<font color='#0000FF' size='50px'>我是蓝色的文本</font><br><font color='#ff0000' size='40px'>我是红色的文本</font><br><font color='#000000' size='29px'>我是黑色的文本</font>"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = new TextView(this); mTextView.setText(Html.fromHtml(htmlStr)); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
哈哈,这么简单的代码对于我们来说不就是分分钟就搞定吗,马上就来验证一下自己的成果
我擦,颜色出来了,但是字体大小怎么完全没改变,你是不是在逗我,凭我多年写Html的Hello World语句来说我的Html文本肯定没有错!,立马找一下原因是为什么,让我们来看看Html.fromHtml都做了什么事情。
从源码里可以看到他会去定义一个SAX解析类Parser,然后传递到133行HtmlToSpannedConverter的构造方法里,并且调用这个类的conver()方法,那我们先看看这个类里面都做了什么
简单过一下构造方法,知道他都有什么
如果有写过SAX解析的朋友现在肯定不会陌生,首先是去设置一个文档内容的处理器,进行XML的解析,里面就是一些头节点尾节点元素开头结束等等的XML相关处理,然后调用parser进行解析,然后会走回调方法,就列出我们比较关心的头尾方法
然后进行节点的处理
这时候眼睛比较凌厉的朋友已经发现我们最想要知道的代码了!他是如何去处理font这个标签的,让我们来看看这个方法startFont(mSpannableStringBuilder, attributes);
看到这里的时候我的心里是奔溃的。。尼玛这都什么跟什么,怎么就支持color这个标签,不支持size,还有这face是什么鬼,能支持face难道不能支持我传说中的大size么!!简直是在逗我!!
在这里我们可以看出来他的实现方式其实很简单,首先是使用XML去解析每一个节点,然后使用SpannableStringBuilder去进行拼接。
到了这个时候我们只能够自己去定义实现font以及获取里面的属性了,要怎么做呢,我们可以看到其实他在这一大堆if else的判断里面已经把font这个标签给处理掉了,不会给我们继续处理(不要跟我说修改源码),这时候其实我们看一下if else的最后,他是会进行回调到一个叫TagHandler里面的方法的,那么我们只需要去实现这个接口就可以了,从上面可以看出来,他是一个抽象类,用于给我们扩展的。
上面说到了font标签已经被处理掉了,不会再回调给我们,所以我们就需要自己去定义一个标签,当然了,后端给予我们得一样可以是font的,我们只是自己去进行替换,类似这样子
htmlStr = htmlStr.replaceAll("font", "bluefont");
mTextView.setText(Html.fromHtml(htmlStr, null, new HtmlTagHandler()));
- 1
- 2
首先是替换成一个源码里不会进行处理的标签
/**
* Created by blue.
*/
public class HtmlTagHandler implements Html.TagHandler { private static final String TAG_BLUE_FONT = "bluefont"; private int startIndex = 0; private int stopIndex = 0; final HashMap<String, String> attributes = new HashMap<String, String>(); @Override public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) { processAttributes(xmlReader); if(tag.equalsIgnoreCase(TAG_BLUE_FONT)){ if(opening){ startFont(tag, output, xmlReader); }else{ endFont(tag, output, xmlReader); } } } public void startFont(String tag, Editable output, XMLReader xmlReader) { startIndex = output.length(); } public void endFont(String tag, Editable output, XMLReader xmlReader){ stopIndex = output.length(); String color = attributes.get("color"); String size = attributes.get("size"); size = size.split("px")[0]; if(!TextUtils.isEmpty(color) && !TextUtils.isEmpty(size)){ output.setSpan(new ForegroundColorSpan(Color.parseColor(color)), startIndex, stopIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } if(!TextUtils.isEmpty(size)){ output.setSpan(new AbsoluteSizeSpan(Integer.parseInt(size)), startIndex, stopIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } private void processAttributes(final XMLReader xmlReader) { try { Field elementField = xmlReader.getClass().getDeclaredField("theNewElement"); elementField.setAccessible(true); Object element = elementField.get(xmlReader); Field attsField = element.getClass().getDeclaredField("theAtts"); attsField.setAccessible(true); Object atts = attsField.get(element); Field dataField = atts.getClass().getDeclaredField("data"); dataField.setAccessible(true); String[] data = (String[])dataField.get(atts); Field lengthField = atts.getClass().getDeclaredField("length"); lengthField.setAccessible(true); int len = (Integer)lengthField.get(atts); for(int i = 0; i < len; i++){ attributes.put(data[i * 5 + 1], data[i * 5 + 4]); } } catch (Exception e) { } } }
- 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
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
使用startIndex和stopIndex来进行判断每一个标签头和尾的位置,对里面的文本进行相对应的样式处理。然后让我们来运行一下代码试试
恩!?为什么第一行的样式不起效果呢??其实这个只是SAX解析的一些小bug而已,具体原理的话稍后我再贴上来,解决的方案也很简单:
1)在这一段Html格式的字符串前面再加上随意一个标签,例如<p>
标签等等
2)发送html格式的字符串过来的时候将<html><body>
也就是一整个网页需要的信息传递过来,也可以解决这个问题
那么修复了这个小bug后让我们来看看我们的最终成功,一个字段显示多种不同样式的文本
四、总结
上面的这些都是抛砖引玉,带来一些思路,我们可以自己进行扩展所有的Html标签,Android自带能支持的标签实在是太少了,而且连</br>
都不能带斜杠得写成<br>
不然不能正常得换行。
Spannable setSpan用到的这个类,百度多了解下
转载于:https://www.cnblogs.com/cfas/p/7814166.html
最后
以上就是辛勤电脑为你收集整理的Android中为TextView增加自定义的HTML标签Spannable setSpan用到的这个类,百度多了解下的全部内容,希望文章能够帮你解决Android中为TextView增加自定义的HTML标签Spannable setSpan用到的这个类,百度多了解下所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复