先直接上效果图
上图为实现目标,实现了Android图文混排,文字环绕,支持Span的识别,表情的嵌入,支持文字字体大小的设置等。
由于项目中需要用到图文混排技术,在此稍微研究了两天,出来一个效果还算不错的东西
图文混排技术,在不少Android应用中都已经实现,说穿了其实就是两个TextView加一个ImageView的布局罢了,代码里面实现下String的剪切就可以了,不过我这里的这个除了要实现混排效果外,还要支持Span,支持表情等,这就有点麻烦了。下面慢慢分解。先贴出RichTextImageView的布局。
<?xml version="1.0"encoding="utf-8"?>
<!-- com.demonzym.richtextdemo.RichTextImageView-->
<com.demonzym.richtextdemo.RichTextImageViewxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:id="@+id/richview" >
<LinearLayout
android:id="@+id/linearLayout1"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<RelativeLayout
android:id="@+id/layout_preimage_isgif_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:visibility="gone" >
<ImageView
android:id="@+id/preimage_statues_left"
android:layout_width="134dip"
android:layout_height="134dip"
android:background="@drawable/preview_back"
android:cropToPadding="true"
android:scaleType="centerCrop"
android:src="@drawable/preview_back_small" />
<ImageView
android:id="@+id/preimage_isgif_left"
android:layout_width="24dip"
android:layout_height="17dip"
android:layout_alignBottom="@+id/preimage_statues"
android:layout_alignRight="@+id/preimage_statues"
android:layout_marginBottom="9dip"
android:layout_marginRight="7dip" />
</RelativeLayout>
<com.demonzym.richtextdemo.RichTextView
android:id="@+id/lefttext"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_weight="1"/>
<RelativeLayout
android:id="@+id/layout_preimage_isgif_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:visibility="gone" >
<ImageView
android:id="@+id/preimage_statues_right"
android:layout_width="134dip"
android:layout_height="134dip"
android:background="@drawable/preview_back"
android:cropToPadding="true"
android:scaleType="centerCrop"
android:src="@drawable/preview_back_small" />
<ImageView
android:id="@+id/preimage_isgif_right"
android:layout_width="24dip"
android:layout_height="17dip"
android:layout_alignBottom="@+id/preimage_statues"
android:layout_alignRight="@+id/preimage_statues"
android:layout_marginBottom="9dip"
android:layout_marginRight="7dip" />
</RelativeLayout>
</LinearLayout>
<com.demonzym.richtextdemo.RichTextView
android:id="@+id/bottomtext"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</com.demonzym.richtextdemo.RichTextImageView>
布局中的RichTextView是另外封装的一个实现Span的TextView,就是实现效果图中表情啊,@啊,#话题#之类的,了解Span的都懂的,就不细说了。读者研究这个图文混排的时候,可以直接用TextView替代。
RichTextImageView 即本文中的图文混排的View。继承于LinearLayout,该类实现的关键代码如下:
public void setText(String t){
mTopText.setText("");
mBottomText.setText("");
mTopText.setVisibility(View.INVISIBLE);
mBottomText.setVisibility(View.GONE);
bRequestBottomLayout =true;
if (t ==null)
t = "";
// Log.e("setText", t);
mTextContent = t;
mTopText.setText(mTextContent);
requestLayout();
}
public void setImage(int id) {
mImageContent = id;
mPreviewImage.setImageResource(id);
mPreview.setVisibility(View.VISIBLE);
if(!Util.isStringEmpty(mTextContent))
setText(mTextContent);
}
private void iniViews() {
mLinearLayout = (LinearLayout)findViewById(R.id.linearLayout1);
mTopText = (RichTextView)findViewById(R.id.lefttext);
mBottomText = (RichTextView)findViewById(R.id.bottomtext);
if(IMAGE_LOCATION ==IMAGE_LOCATION_RIGHT){
mPreview =(RelativeLayout)findViewById(R.id.layout_preimage_isgif_right);
mPreviewImage= (ImageView) findViewById(R.id.preimage_statues_right);
mPreviewImageIsGif= (ImageView) findViewById(R.id.preimage_isgif_right);
}else if(IMAGE_LOCATION ==IMAGE_LOCATION_LEFT){
mPreview =(RelativeLayout)findViewById(R.id.layout_preimage_isgif_left);
mPreviewImage= (ImageView) findViewById(R.id.preimage_statues_left);
mPreviewImageIsGif= (ImageView)findViewById(R.id.preimage_isgif_left);
}
}
public void setImageLocation(int l){
if(l != IMAGE_LOCATION_RIGHT&& l != IMAGE_LOCATION_LEFT)
return;
else{
IMAGE_LOCATION= l;
mPreview.setVisibility(View.GONE);
iniViews();
setImage(mImageContent);
}
}
@Override
protected void onLayout(boolean changed, int l,int t, int r, int b) {
// TODO Auto-generated methodstub
super.onLayout(changed, l, t,r, b);
if(Util.isStringEmpty(mTextContent))
return;
// Log.e("toptext 宽 高",
// mTopText.getWidth()+ " " + mTopText.getHeight());
// Log.e("top text 单行高度", "" +mTopText.getLineHeight());
// Rect rect =getStringRect(mTextContent, mTopText.getPaint());
// Log.e("文本的宽 高", rect.width()+ " " + rect.height());
// toptext最多能显示的行数
int topTextMaxRows =mTopText.getHeight() / mTopText.getLineHeight();
// 显示这些内容真实占用的行数
int textRows =mTopText.getLineCount();
// Log.e("top text最多能显示的行数", ""+ topTextMaxRows);
// Log.e("当前文本实际占用的行数", "" +textRows);
//由于文本可能带有表情,重新计算显示行数,以保证显示正确
Rect lineR = new Rect();
int realH =0; //toptext真实高度
int i = 0;
for(i = 0; i <topTextMaxRows; i++){
try{
mTopText.getLineBounds(i,lineR);
}catch(IndexOutOfBoundsExceptione){
break;
}
realH +=lineR.height();
if(realH>= mTopText.getHeight())
break;
}
// Log.e("当前view实际能显示的行数为", "" +i);
topTextMaxRows = i;
//如果toptext显示不下的话,显示到bottomtext里面去
if (textRows >=topTextMaxRows &&bRequestBottomLayout) {
//toptext最后一个可见字符的位置
int lastindex= mTopText.getLayout().getLineVisibleEnd(
topTextMaxRows- 1);
ClickableSpan[]cs = mTopText.getSpans();
int spanstart= 0;
int spanend =0;
spanstring ="";
STATE = 0; //1网页 2人名 3话题
for(ClickableSpan c : cs) {
spanstart= mTopText.getSpanStart(c);
spanend= mTopText.getSpanEnd(c);
if(spanstart <= lastindex&& spanend >lastindex) {
if(c instanceof LinkClickableSpan) {
// Log.e("转角span类型","网页");
spanstring= ((LinkClickableSpan) c).getLink();
STATE= 1;
}
if(c instanceof NameClickableSpan) {
// Log.e("转角span类型","人名");
spanstring= ((NameClickableSpan) c).getName();
STATE= 2;
}
if(c instanceof TopicClickableSpan) {
// Log.e("转角span类型","话题");
spanstring= ((TopicClickableSpan) c).getTopic();
STATE= 3;
}
break;
}
}
mTopText.setText(mTextContent.substring(0,lastindex));
mTopText.setVisibility(View.VISIBLE);
//当行数不是整数时,调整内容会有下面半行没遮住,所以干脆都处理完以后再显示出来
mBottomText.setText(mTextContent.substring(lastindex,
mTextContent.length())+ mBottomText.getText().toString());
if(mBottomText.getText().length() > 0&& bRequestBottomLayout){
mBottomText.setVisibility(View.VISIBLE);
mHandler.post(newRunnable() {
@Override
publicvoid run() {
//TODO Auto-generated method stub
mBottomText.requestLayout();
bRequestBottomLayout= false;
}
});
}
if (STATE !=0 || !bRequestBottomLayout) {
Log.e("spanstring",spanstring);
// Log.e("","移除转角span");
mTopText.getSpannable().removeSpan(mTopText.getSpans()[mTopText.getSpans().length- 1]);
switch(STATE) {
case1:
mTopText.getSpannable().setSpan(
mTopText.newLinkClickableSpan(spanstring),
spanstart,
lastindex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mBottomText.getSpannable().setSpan(
mBottomText.newLinkClickableSpan(spanstring),
0,
spanend- lastindex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// Log.e("","网页");
break;
case2:
mTopText.getSpannable().setSpan(
mTopText.newNameClickableSpan(spanstring),
spanstart,
lastindex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mBottomText.getSpannable().setSpan(
mBottomText.newNameClickableSpan(spanstring),
0,
spanend- lastindex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// Log.e("","人名");
break;
case3:
mTopText.getSpannable().setSpan(
mTopText.newTopicClickableSpan(spanstring),
spanstart,
lastindex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mBottomText.getSpannable().setSpan(
mBottomText.newTopicClickableSpan(spanstring),
0,
spanend- lastindex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// Log.e("","话题");
break;
}