自定义图文混排视图MyImageTextView

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

时间: 2024-08-07 09:08:48

自定义图文混排视图MyImageTextView的相关文章

(一一二)图文混排中特殊文字的点击与事件处理

在上一篇文章(一一一)图文混排基础 -利用正则分割和拼接属性字符串中提到了对attributedText的特殊处理,将其中的话题.URL都用红色进行了表示,如下图所示: 本节要实现的功能是这样的attributedText在点击话题.URL时应该有所响应,而在点击其他位置的时候把事件传递给父类处理. 要获取到点击的位置很简单,只需要重写touchesBegan方法即可拿到触摸点,比较棘手的是判断出触摸位置的文字的范围(CGRect),如果能拿到点击文字的rect,就能对rect进行高亮处理和事件

iOS 图文混排 链接 可点击

对于这个话题 我想到 1 第一个解决方法就是使用 webView 比较经典 把所有复杂工作都交给控件本身去处理了,  但是好像好多需要自定义的地方 没法从 webView获得响应回调 :(估计也可以实现 也比较复杂,而且 这个需要对 html编码进行分析理解剥离等) 2 富文本方式 核心框架 coretext 图文混排 一点问题都没有 关键是怎么对 目标图片 或者链接 进行触发响应 要点: (1)首先要封装的要相对独立 拓展也方便  首当其冲就是  和服务端约定的 数据模型 CoreTextMo

NGUI研究之有点坑爹的图文混排

 最近研究了了一下NGUI的图文混排工具,得出的结论是有点坑爹..大家看看我的测试步骤,我用的是目前NGUI的版本3.6.6 . 当我在 Open BitMapFont Marker 的时候界面上出现Assets/NGUI/Editor/FreeType.dylib is missing 的字样.于是查了一下,大概意思是必须要把FreeType.dylib 文件拷贝在/usr/local/lib/FreeType.dylib路径下面.FreeType.dylib 文件在NGUI/Editor

iOS 图文混排

1) 在iOS 7之前也有一种用于文字排版和渲染的技术——Core Text,而引入Text Kit的目的并非要取代Core Text. Core Text是面????层的文字排版和渲染技术,如果我们需要将文本内容??接渲染到图形上下文时,从性能角度考虑 ??,最佳??方案??是使用Core Text.??是从易??用性角度考虑??,使用Text Kit是最好的选择,因为??能直????接使用UIKit 提供的一些文本控件,例如:UITextView.UILabel和UITextField,对文

XMPP键盘订制实现图文混排

在现阶段的通信服务中,各种标准都有,因此会出现无法实现相互连通,而XMPP(Extensible Message and presence Protocol)协议的出现,实现了整个及时通信服务协议的互通.有了这个协议之后,使用任何一个组织或者个人提供的即使通信服务,都能够无障碍的与其他的及时通信服务的用户进行交流.例如google 公司2005年推出的Google talk就是一款基于XMPP协议的即时通信软件.在前面的系列博文中,我们介绍了XMPP的详细使用(查看系列文章:http://www

iOS火焰动画效果、图文混排框架、StackView效果、偏好设置、底部手势等源码

iOS精选源码 高性能图文混排框架,构架顺滑的iOS应用. 使用OpenGLE覆盖阿尔法通道视频动画播放器视图. 可选最大日期截至当日日期的日期轮选器ChooseDatePicker 简单轻量的图片浏览器YCPhotoBrower 使用偏好设置.属性列表.归档解档保存数据.恢复数据 页面底部手势交互滚动UITableView 使用CoreAnimation来模拟iOS中的StackView. 盒子可以更具长宽高变化的动画 iOS优质博客 iOS导航栏使用总结 目录:一.设置导航栏样式二.自定义导

[Swift通天遁地]八、媒体与动画-(13)CoreText框架实现图文混排

本文将演示CoreText框架实现图文混排.CoreText(富文本)框架并不支持图片的绘制, 需要借助Core Graphics框架来进行图片的绘制. 图文混排的实现原理非常简单,就是在一个富文本中插入一个占位符, 表示此处需要插入一张图片.然后再由另一个图形绘制框架, 在占位符所在位置绘制指定的图片. 在项目文件夹上点击鼠标右键,弹出右键菜单. [New File]->[Cocoa Touch]->[Next]-> [Class]:CTImageView [Subclass of]:

高性能图文混排框架,构架顺滑的iOS应用-b

About GallopGallop是一个功能强大.性能优秀的图文混排框架. Features主要用于解决以下需求: 滚动列表的性能优化.Gallop使用异步绘制.视图层级合并.观察mainRunloop.对布局模型预先缓存等方法,能在实现复杂的图文混排界面时,仍然保持一个相当优秀的滚动性能(FPS基本保持在60). 项目内有使用Gallop构建的微信朋友圈Demo 实现图文混排界面,比如在文本中添加表情,对文字添加点击链接.Gallop还提供了方便的方法可以直接完成表情.URL链接.@用户.#

iOS阅读器实践系列(三)图文混排

本篇介绍coretext中的图文混排,这里暂用静态的内容,即在文本中某一固定位置插入图片,而不是插入位置是根据文本内容动态插入的(要实现这一效果需要写一个文本解析器,将原信息内容解析为某些特定格式的结构来标示出特定的类型(比如文字.图片.链接等),然后按照其结构中的属性配置,生成属性字符串,之后渲染到视图中). 这部分的思路参考唐巧大神的blog. 在第一篇介绍过coretext是离屏渲染的,即在将内容渲染到屏幕上之前,coretext已完成排版工作.coretext排版的第一步是组织数据,即由