TextView本身是支持图文混排的,在手机上,通过TextView进行图文混排时,排版可能难以达到PC上浏览器的效果,特别是对于一些支持多种标签的发布系统。
1. 网上很容易找到的使用TextView实现图文混排的例子,大多是类似于下面的形式:
TextView tv_Content;
tv_Content.setText(Html.fromHtml(item.getContent(), GetImageGetter(), null));
private ImageGetter imageGetter = null; private Map<String, URLDrawable> imageHashMap = null; private ImageGetter GetImageGetter() { if(imageHashMap == null) { imageHashMap = new HashMap<String, URLDrawable>(2); } if(imageGetter == null) { imageGetter = new ImageGetter() { //通过网络获取图片是一个耗时的操作,最好不要放在主线程中,否则容易引起阻塞。 @Override public Drawable getDrawable(String source) { String key = MD5.EncoderByMD5(source); URLDrawable urlDrawable = imageHashMap.get(key); if(urlDrawable == null) { urlDrawable = new URLDrawable(); imageHashMap.put(key, urlDrawable); // get the actual source ImageGetterAsyncTask.start(mContext, urlDrawable, source, handler); } return urlDrawable; } }; } return imageGetter; } private Handler handler = new Handler() { @Override public void handleMessage(android.os.Message msg) { if(msg.what == ImageGetterAsyncTask.OnDrawablePrepared) { refreshNewsImage(msg); } } }; private void refreshNewsImage(android.os.Message msg) { notifyDataSetChanged(); }
需要设置要显示图片的尺寸:
drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
2. 一般在listViewItem中使用都没有问题,但是如果作为scrollView的子视图的话,在有图像时会抛出异常(在公司测试机上如此,其他环境没有去验证)。建议通过自定义视图的方式来实现,基本思路就是利用SpannableStringBuilder来分割图片及非图片内容,然后逐一创建图片及非图片视图。对于类似于的新闻呈现且需要高度定制UI的场合非常适用。
2.1 content_textview.xml :用于显示图片之外的内容
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" style="@style/Style_NewsText_Content" android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingLeft="10dp" android:paddingRight="10dp" android:typeface="normal" > </TextView>
2.2 content_imageview.xml:用于显示图片及图片说明,如“[图 1]”
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:orientation="vertical" android:paddingTop="5dp"> <ImageView android:id="@+id/content_imageview_image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:adjustViewBounds="true" android:baselineAlignBottom="true" android:contentDescription="@string/xxx" android:minHeight="30dp" android:minWidth="30dp" android:paddingBottom="5dp" android:scaleType="centerInside" > </ImageView> <TextView android:id="@+id/content_imageview_title" style="@style/Style_NewsText_Content" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center" android:paddingBottom="5dp" android:singleLine="false" android:textColor="@color/text_b0b0b0" android:textSize="@dimen/font_small" > </TextView> </LinearLayout>
2.3 vertical_linearlayout.xml:根视图,用于插入待显示内容
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" > </LinearLayout>
2.4 MyImageTextView.jva:实现图文混排的类
public class MyImageTextView extends FrameLayout { //对应的view private LinearLayout mContentView = null; //对应的数据 private CharSequence mData = null; private String[] mImageUrl = null; private ImageView[] mImage = null; private int mImageBaseIndex = 1; //从[图 1]开始 //是否支持超链接点击 private Boolean supportMovementMethod = false; //是否显示图索引 private Boolean showImageIndex = false; public MyImageTextView(Context context) { this(context, null); } public MyImageTextView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyImageTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { setDrawingCacheEnabled(false); setClipChildren(false); mContentView = (LinearLayout) LayoutInflater.from(getContext()).inflate( R.layout.vertical_linearlayout, null); addView(mContentView); } /** * 设置待显示内容 * @param content */ public void setText(CharSequence content) { try { if(TextUtils.isEmpty(content)) { return; } if(content.equals(mData)) { return; } mData = content; mContentView.removeAllViews(); // 首先清理之前加入的子视图 int viewIndex = 0; int len = content.length(); SpannableStringBuilder style = new SpannableStringBuilder(content); ImageSpan[] imgAry = style.getSpans(0, len, ImageSpan.class); if(imgAry == null || imgAry.length <= 0) { addTextView(content, viewIndex); return; } int pos = 0; int start = 0; int end = 0; ImageSpan img = null; mImageUrl = new String[imgAry.length]; mImage = new ImageView[imgAry.length]; for(int i = 0; i < imgAry.length; i++) { img = imgAry[i]; mImageUrl[i] = img.getSource(); start = style.getSpanStart(img); if(pos < start) { addTextView(style.subSequence(pos, start), viewIndex++); } end = style.getSpanEnd(img); addImageView(i, viewIndex++); pos = end + 1; } if(pos > 0 && pos < len) { addTextView(style.subSequence(pos, len), viewIndex); } requestLayout(); invalidate(); //on a UI thread } catch(Exception ex) { } } private void addTextView(CharSequence text, int viewIndex) { TextView tv = (TextView) LayoutInflater.from(getContext()).inflate( R.layout.content_textview, null); mContentView.addView(tv, viewIndex); tv.setText(text); if(supportMovementMethod) { changeLink(tv); } } private void addImageView(int index, int viewIndex) { View parent = LayoutInflater.from(getContext()).inflate( R.layout.content_imageview, null); mImage[index] = (ImageView) parent.findViewById(R.id.content_imageview_image); TextView tvTitle = (TextView)parent.findViewById(R.id.content_imageview_title); if(showImageIndex) { //这里的图片标题,也可以通过<img>标签的title/alt等属性分析出来 tvTitle.setText("[图 " + Integer.toString(mImageBaseIndex + index) + "]"); tvTitle.setVisibility(View.VISIBLE); } else { tvTitle.setVisibility(View.GONE); } mContentView.addView(parent, viewIndex); setImage(parent, mImage[index], mImageUrl[index]); } private void setImage(View parent, ImageView iv, String picUrl){ if(picUrl != null && picUrl.trim().length() > 0) { parent.setVisibility(View.VISIBLE); iv.setImageResource(R.drawable.weibo_pic_loading); Size size = setPic(iv, picUrl); if(size.getHeight() > 0 && size.getWidth() > 0) { parent.requestLayout(); } } else{ parent.setVisibility(View.GONE); } } private Size setPic(ImageView logoView, String logoUrl) { //异步加载图片代码略 return XXXFileManager.getInstance().setImageBitmapWithMemoryCache( getContext(), logoView, logoUrl, XXXFileManager.getImagetLrucache(), getContext().getClass().getName(), false); } /** * 供图片下载完毕时调用 * @param fileURL */ public void setPic(String fileURL) { if(mImage != null && mImageUrl != null && !TextUtils.isEmpty(fileURL)) { String source = null; for(int i = 0; i < mImageUrl.length && i < mImage.length; i++) { source = mImageUrl[i]; if(!TextUtils.isEmpty(source)) { if(fileURL.equals(source)) { setPic(mImage[i], source); mImage[i].getParent().requestLayout(); break; } } } } } /** * 设置是否支持超链接点击 */ public void setSupportMovementMethod(Boolean supportMovementMethod) { this.supportMovementMethod = supportMovementMethod; } /** * 设置是否显示图索引 * @param showImageIndex */ public void setShowImageIndex(Boolean showImageIndex) { this.showImageIndex = showImageIndex; } /** * 设置TextView超链接跳转 * @param tv */ private void changeLink(TextView tv){ tv.setMovementMethod(LinkMovementMethod.getInstance()); CharSequence text = tv.getText(); if (text instanceof Spannable) { int end = text.length(); Spannable sp = (Spannable) tv.getText(); URLSpan[] urls = sp.getSpans(0, end, URLSpan.class); if(urls == null || urls.length <= 0) { return; } SpannableStringBuilder style = new SpannableStringBuilder(text); URLSpan[] urlsn = style.getSpans(0, end, URLSpan.class); if(urlsn == null || urls.length != urlsn.length) { return; } //循环把链接发过去 URLSpan url = null; for(int i = 0; i < urls.length && i < urlsn.length; i++) { url = urls[i]; MyURLSpan myURLSpan = new MyURLSpan(getContext(), url.getURL()); style.removeSpan(urlsn[i]); style.setSpan(myURLSpan, sp.getSpanStart(url), sp.getSpanEnd(url), Spannable.SPAN_EXCLUSIVE_INCLUSIVE); } tv.setText(style); } } public int getImageCount() { int cnt = mImageBaseIndex; if(mImage != null && mImageUrl != null) { cnt += mImage.length; } return cnt; } public void setmImageBaseIndex(int baseIndex) { this.mImageBaseIndex = baseIndex; } public CharSequence getmData() { return mData; } }
2.5 MyURLSpan.java:定义一个可点击的Span,点击超链接时通过浏览器打开改网页/文件。
public class MyURLSpan extends ClickableSpan { private Context context = null; private String mUrl = null;; public MyURLSpan(Context context,String url) { this.context = context; this.mUrl = url; } @Override public void onClick(View widget) { if (URLUtil.isNetworkUrl(mUrl)) { XXXUtils.openMyWebBrowser(this.context, this.context.getResources().getString(R.string.newstext_hyperlink), this.mUrl); } } }
3 使用简单,可以在xml文件中引用,也可以动态创建视图。
3.1 在xml中引用
<ScrollView android:id="@+id/XXX_ScrollView" android:layout_width="fill_parent" android:layout_height="fill_parent" > <LinearLayout android:id="@+id/XXX_Parent" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" android:visibility="gone" > …… <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" android:paddingBottom="5dp" android:paddingTop="10dp" > <XXX.textview.MyImageTextView android:id="@+id/XXX_Content" android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingLeft="10dp" android:paddingRight="10dp" > </XXX.textview.MyImageTextView> <RelativeLayout android:id="@+id/XXX_PayViewParent" android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingBottom="10dp" android:paddingTop="5dp" android:visibility="gone" > <XXX.textview.MyImageTextView android:id="@+id/XXX_PayContent" android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingLeft="10dp" android:paddingRight="10dp" android:visibility="gone" > </XXX.textview.MyImageTextView> <RelativeLayout android:id="@+id/XXX_PayLock" android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="@drawable/xxx_paylock_bg" android:gravity="center_horizontal" > <ImageView android:id="@+id/xxx_Lock" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginRight="5dp" android:contentDescription="@string/xxx" android:padding="5dp" android:src="@drawable/xxx_paylock_icon" > </ImageView> …… </RelativeLayout> </RelativeLayout> </LinearLayout> </LinearLayout> </ScrollView>
3.2 java代码,设置显示内容
itvFreeContent = (MyImageTextView) this.findViewById(R.id.XXX_Content); <span style="white-space:pre"> </span>itvFreeContent.setSupportMovementMethod(true); <span style="white-space:pre"> </span>//itvFreeContent.setShowImageIndex(true); <span style="white-space:pre"> </span>itvFreeContent.setText(Html.fromHtml(formatContent(content)));
当然这里还需要加入图片异步下载完成后的代码,如:
private void initHandler() { this.mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case KLoadImageOver: itvFreeContent.setPic(msg.getData().getString("fileURL")); break; default: break; } } }; }
实际效果图(截取部分):
4 第二种方式需要扩展的是,如果显示的内容有超链接,且超链接时中的显示对象是图片,那么需要给图片增加点击事件,点击的跳转参照MyURLSpan.onClick。
关于图片的下载,这里推荐一个第三方库Android-Universal-Image-Loader。
自定义图文混排视图MyImageTextView,布布扣,bubuko.com