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

来源:互联网 发布:淘宝网防晒衣女 编辑:程序博客网 时间:2024/06/10 10:49

先直接上效果图

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

 

上图为实现目标,实现了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;
    }
    
    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部分删除即可使用,我注释的挺清楚了

 

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

0 0
原创粉丝点击