Oct
23
在安卓的某一些需求中,我们需要实现textview中的图文混排效果。分三种
- 在文字行中插入一张图片,比如聊天记录中的表情...。
- 第二是在类似于新闻客户端中,单列一行,居中的新闻图片。
- 第三是wiki或者论文等描述性文章中,环绕嵌入的图片。这也是最复杂的一种情况。
这三种情况依次从简单到复杂,均有不同的或合适或不合适的解决方案,按实现需求的从轻到重,在此一一列举。
1.Span插入图片
这是学习span的时候了解的方法,可以使用于前两个使用场景。
span,主要是ImageSpan,作为字符的样式span,它直接将一部分字符替换为图片资源。使用方法如下:
由于要代替一段文字,我们常常使用某一种约定字符串来标注以便于替换,比如大家耳熟能详的emoji。。。。。或者常见的bbcode或者就是html。用普通查找或者正则查找找到在字符串中的位置顺便把有可能存在的url之类的信息给抽出来,再或同步或异步加载图片,并绑定在spanstring上设置回textview。
在两个使用场景上,第一种直接插入图片就可以了,第二种需要两边加上回车符以让图片独立一行,并且使用new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER)的span让这个图片居中,相对来说在字符串处理上还是相当的麻烦了。
2.fromhtml插入图片
既然方法一那么麻烦,有没有更容易接受的方法呢?对,作为方法一的语法糖,fromhtml方法就可以“大显神通”了。fromhtml本来就是为我们自动设置span的方法,又有img标签可以识别。
那么我们需要做的,就是把需要插入图片的地方,替换为img标签,其中的src属性用特定的规则进行编写(比如my://emoji),然后在重写imagegetter的时候,处理一下url字符串,针对不同的head的url采用不同的解决方案,就可以加载本地资源,网络资源等各种各样的图片了。和span效果一模一样。针对不同的排版,我们可以适当加入<br>或者自定义<center>等标签来处理,达到我们实现场景1和场景2的需求。
嗯。。真要说实现复杂度的话和原始方法一并不相上下,但是这个方法更适合工厂化数据处理和代码编写吧。
3.Listview排列图片
在需求2中,图片和文字是排布在纵向的不同行上的,那我们可以想到,用一个多种type的listview(一般是recyclerview),两种类型的view,一种放文字段落,一种放图片,不就可以解决这个问题了么。
因此,我们拿到原始字符串后需要将原始字符串拆解,以图片前,图片后,其他\n为界的地方把原始的字符串按预定的规则拆解为数组,再将这个数组投入已经事先定义好样式的listview中,就大功告成了。
但这个方法只使用于场景2,也就是新闻或者类似图文描述段落,使用范围不广,另外一半来说新闻类的listview需对上下显示内容进行优化缓存预加载之类的,配合图片显示写起来并不一定简单高效。
4.多个textview环绕
下面开始的方法适用于实现场景3的环绕效果。首先想到的是,在需要图片的范围左右排列图片和textview,用多个textview来实现图文环绕的效果。这也是在indesign等排版软件中非常常见的排版手法。
因此我们需要开始在textview1中填写需求字符串,直到填写满。。。。好问题1,我们如何知道在何时何地textview被填写满了呢?这涉及到text的渲染方法了。
渲染textview或者说渲染所有的文字有三个方法:
- BoringLayout:最简单的渲染方法,它负责渲染单行的文本,它有一个静态方法isBoring可以帮助判断这行文字到底是不是boring的。
- DynamicLayout:用来处理叠加了span的文本。
- StaticLayout:静态文本,一般来说用到的多行文本都是它,它的好处是渲染一次就不再管了,性能高~
在多textview的排布情况下,我们显然一般textview中会用到StaticLayout来渲染文本,诶,不管用哪种方式渲染文本,我们可以通过textView.getLayout()得到layout在从中获取到已经渲染的高度宽度和截段的长度,接下来我们可以使用这些数据来调整上下textview的间距大小,然后从截断的textview的位置接着处理下一个textview。
似乎是一个很麻烦的过程,但是不失为一种方法。
5.LeadingMarginSpan制造伪环绕
首先LeadingMarginSpan这个span是在段落的头多少多少行空出多少多少dp的。那么,如果我们把每个需要插入图片的地方放在屏幕左侧,并单列出段落,那么这个段落就可以用这个span为图片空出一个位置,就可以实现伪环绕了。具体来说,我们需要先放置图片到段落开始位置,然后实现一个满足需求的LeadingMarginSpan,设置初始化:
就可以实现左侧环绕嵌入图片那样的效果了。这个方法最大的弱点也是这个。。。只能在左侧嵌入。。。
6.自己写layout计算
重写textview和layout计算方法,当然,这是最究极的大招了,不过,还真有开发者这样实现了的。(比如这位:http://mobile.51cto.com/abased-375949.htm 和 这位:http://blog.sina.com.cn/s/blog_6034139401012kq1.html(前者实现得更加优雅))
由于我们几乎要重新写一个view,我们就不用封装为layout直接在view的layout和draw中重写计算方法就行了。最核心只处还是计算图片的覆盖位置,再依次计算每一行的位置。上面两位都是实现的单图片,如果是多图片,那更加复杂了。
7.webview大法
作弊级别的方法,太复杂的情况,用webview可以解决一切问题~~~生成html并载入,或者用一定html显示模版再注入数据,一切就任意了~
- 在文字行中插入一张图片,比如聊天记录中的表情...。
- 第二是在类似于新闻客户端中,单列一行,居中的新闻图片。
- 第三是wiki或者论文等描述性文章中,环绕嵌入的图片。这也是最复杂的一种情况。
这三种情况依次从简单到复杂,均有不同的或合适或不合适的解决方案,按实现需求的从轻到重,在此一一列举。
1.Span插入图片
这是学习span的时候了解的方法,可以使用于前两个使用场景。
span,主要是ImageSpan,作为字符的样式span,它直接将一部分字符替换为图片资源。使用方法如下:
String text = "abcdefg[img]abcdefg";
Drawable drawable = getResources().getDrawable(R.drawable.example);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
int where = text.indexOf("[img]");
SpannableString spannable = new SpannableString(text.toString() + "[img]");
ImageSpan span = new ImageSpan(drawable, ImageSpan.ALIGN_BASELINE);
spannable.setSpan(span, where, where + "[img]".length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
textView.setText(spannable);
由于要代替一段文字,我们常常使用某一种约定字符串来标注以便于替换,比如大家耳熟能详的emoji。。。。。或者常见的bbcode或者就是html。用普通查找或者正则查找找到在字符串中的位置顺便把有可能存在的url之类的信息给抽出来,再或同步或异步加载图片,并绑定在spanstring上设置回textview。
在两个使用场景上,第一种直接插入图片就可以了,第二种需要两边加上回车符以让图片独立一行,并且使用new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER)的span让这个图片居中,相对来说在字符串处理上还是相当的麻烦了。
2.fromhtml插入图片
既然方法一那么麻烦,有没有更容易接受的方法呢?对,作为方法一的语法糖,fromhtml方法就可以“大显神通”了。fromhtml本来就是为我们自动设置span的方法,又有img标签可以识别。
那么我们需要做的,就是把需要插入图片的地方,替换为img标签,其中的src属性用特定的规则进行编写(比如my://emoji),然后在重写imagegetter的时候,处理一下url字符串,针对不同的head的url采用不同的解决方案,就可以加载本地资源,网络资源等各种各样的图片了。和span效果一模一样。针对不同的排版,我们可以适当加入<br>或者自定义<center>等标签来处理,达到我们实现场景1和场景2的需求。
嗯。。真要说实现复杂度的话和原始方法一并不相上下,但是这个方法更适合工厂化数据处理和代码编写吧。
3.Listview排列图片
在需求2中,图片和文字是排布在纵向的不同行上的,那我们可以想到,用一个多种type的listview(一般是recyclerview),两种类型的view,一种放文字段落,一种放图片,不就可以解决这个问题了么。
因此,我们拿到原始字符串后需要将原始字符串拆解,以图片前,图片后,其他\n为界的地方把原始的字符串按预定的规则拆解为数组,再将这个数组投入已经事先定义好样式的listview中,就大功告成了。
但这个方法只使用于场景2,也就是新闻或者类似图文描述段落,使用范围不广,另外一半来说新闻类的listview需对上下显示内容进行优化缓存预加载之类的,配合图片显示写起来并不一定简单高效。
4.多个textview环绕
下面开始的方法适用于实现场景3的环绕效果。首先想到的是,在需要图片的范围左右排列图片和textview,用多个textview来实现图文环绕的效果。这也是在indesign等排版软件中非常常见的排版手法。
因此我们需要开始在textview1中填写需求字符串,直到填写满。。。。好问题1,我们如何知道在何时何地textview被填写满了呢?这涉及到text的渲染方法了。
渲染textview或者说渲染所有的文字有三个方法:
- BoringLayout:最简单的渲染方法,它负责渲染单行的文本,它有一个静态方法isBoring可以帮助判断这行文字到底是不是boring的。
- DynamicLayout:用来处理叠加了span的文本。
- StaticLayout:静态文本,一般来说用到的多行文本都是它,它的好处是渲染一次就不再管了,性能高~
在多textview的排布情况下,我们显然一般textview中会用到StaticLayout来渲染文本,诶,不管用哪种方式渲染文本,我们可以通过textView.getLayout()得到layout在从中获取到已经渲染的高度宽度和截段的长度,接下来我们可以使用这些数据来调整上下textview的间距大小,然后从截断的textview的位置接着处理下一个textview。
似乎是一个很麻烦的过程,但是不失为一种方法。
5.LeadingMarginSpan制造伪环绕
首先LeadingMarginSpan这个span是在段落的头多少多少行空出多少多少dp的。那么,如果我们把每个需要插入图片的地方放在屏幕左侧,并单列出段落,那么这个段落就可以用这个span为图片空出一个位置,就可以实现伪环绕了。具体来说,我们需要先放置图片到段落开始位置,然后实现一个满足需求的LeadingMarginSpan,设置初始化:
float fontSpacing = textView.getPaint().getFontSpacing();
lines = (int) (finalHeight / (fontSpacing));
MyLeadingMarginSpan2 span = new MyLeadingMarginSpan2(lines, finalWidth + 10);
就可以实现左侧环绕嵌入图片那样的效果了。这个方法最大的弱点也是这个。。。只能在左侧嵌入。。。
6.自己写layout计算
重写textview和layout计算方法,当然,这是最究极的大招了,不过,还真有开发者这样实现了的。(比如这位:http://mobile.51cto.com/abased-375949.htm 和 这位:http://blog.sina.com.cn/s/blog_6034139401012kq1.html(前者实现得更加优雅))
由于我们几乎要重新写一个view,我们就不用封装为layout直接在view的layout和draw中重写计算方法就行了。最核心只处还是计算图片的覆盖位置,再依次计算每一行的位置。上面两位都是实现的单图片,如果是多图片,那更加复杂了。
7.webview大法
作弊级别的方法,太复杂的情况,用webview可以解决一切问题~~~生成html并载入,或者用一定html显示模版再注入数据,一切就任意了~