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>
<?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;
}
}
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);
itvFreeContent.setSupportMovementMethod(true);
//itvFreeContent.setShowImageIndex(true);
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;
}
}
};
}
实际效果图(截取部分):
关于图片的下载,这里推荐一个第三方库Android-Universal-Image-Loader。