先直接上效果图

Android <wbr>文字环绕 <wbr>图文混排 <wbr>支持Span折叠  Android <wbr>文字环绕 <wbr>图文混排 <wbr>支持Span折叠

上图为实现目标,实现了Android图文混排,文字环绕,支持Span的识别,表情的嵌入,支持文字字体大小的设置等。

 

 

 

    由于项目中需要用到图文混排技术,在此稍微研究了两天,出来一个效果还算不错的东西

    图文混排技术,在不少Android应用中都已经实现,说穿了其实就是两个TextView加一个ImageView的布局罢了,代码里面实现下String的剪切就可以了,不过我这里的这个除了要实现混排效果外,还要支持Span,支持表情等,这就有点麻烦了。下面慢慢分解。先贴出RichTextImageView的布局。

 

<com.demonzym.richtextdemo.RichTextImageView xmlns: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” />        

        <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” />            

    <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 method stub  super.onLayout(changed, l, t, r, b);

  if (Util.isStringEmpty(mTextContent))   return;

//  Log.e(“top text 宽 高”, //    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(IndexOutOfBoundsException e){    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(

         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(new Runnable() {          @Override     public void run() {      // TODO Auto-generated method stub      mBottomText.requestLayout();      bRequestBottomLayout = false;     }    });   }

   

   if (STATE != 0 || !bRequestBottomLayout) {

    Log.e(“spanstring”, spanstring);

//    Log.e(“”, “移除转角span”);

    switch (STATE) {    case 1:     mTopText.getSpannable().setSpan(       mTopText.new LinkClickableSpan(spanstring),       spanstart,       lastindex,       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);     mBottomText.getSpannable().setSpan(       mBottomText.new LinkClickableSpan(spanstring),       0,       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); //     Log.e(“”, “网页”);     break;    case 2:     mTopText.getSpannable().setSpan(       mTopText.new NameClickableSpan(spanstring),       spanstart,       lastindex,       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);     mBottomText.getSpannable().setSpan(       mBottomText.new NameClickableSpan(spanstring),       0,       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); //     Log.e(“”, “人名”);     break;    case 3:     mTopText.getSpannable().setSpan(       mTopText.new TopicClickableSpan(spanstring),       spanstart,       lastindex,       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);     mBottomText.getSpannable().setSpan(       mBottomText.new TopicClickableSpan(spanstring),       0,       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); //     Log.e(“”, “话题”);     break;    }        mTopText.setText(mTopText.getSpannable(), BufferType.SPANNABLE);    mBottomText.setText(mBottomText.getSpannable(), BufferType.SPANNABLE);   }   

  }   }

 

 

    说明下难点:

    第一,转角处可能存在Span,比如一个话题 #话题话题话题话题话题话题话题话题话题#,可能一半内容在mTopText里面的最后一行显示,而另一半显示不下了,这就需要剪切剩余的String显示到mBottomText里面去,可是Span的内容就会出错,这就需要重新设置下转角处的Span效果了

    第二,由于存在图片Span,会导致行高不正确,所以需要在获取到mTopText能显示的最多行数以后,重新判断一次是否正确,如果不正确的话,需要重新调整,不然mTopText会有一部分内容显示不下。

 

该View实现完成后,在布局中使用include条用,代码中find出来就可以直接使用了

 

不考虑Span的话,直接将代码中转角处理Span部分删除即可使用,我注释的挺清楚了

 

代码写的较急,可能有点乱,仅作交流

文章转自:http://blog.sina.com.cn/s/blog_47021dd401012szb.html