您的位置:首页 > 产品设计 > UI/UE

SpannableStringBuilder实现图文混排

2016-01-12 20:18 423 查看

概述

SpannableString、SpannableStringBuilder与String的关系

SpannableString、SpannableStringBuilder基本上与String差不多,也是用来存储字符串,但是它们俩的特殊就在于有一个setSpan()函数,能给这些存储的String添加各种样式(Span),将原来的String以不同的样式显示出来,比如在原来的String上加下划线丶加背景色丶改变字体颜色丶用图片把指定的文字给替换掉,等等。所以,总而言之,SpannableString、SpannableStringBuilder与String一样,首先也是传字符串,但是SpannableString、SpannableStringBuilder可以对这些字符串添加额外的样式信息,但String则不行。

注意:如果这些额外信息能被所用的方式支持,比如将SpannableString传给TextView;也有对这些额外信息不支持的,比如Canvas绘制文字,对于不支持的情况,SpannableString和SpannableStringBuilder就是退化为String类型,直接显示原来的String字符串,而不会再显示这些附加的额外信息。

SpannableString与SpannableStringBuilder区别

它们的区别在于SpannableString像一个String一样,构造对象的时候传入一个String,之后再无法更改String的内容,也无法拼接多个SpannableString;而SpannableStringBuilder则更像是StringBuilder,它可以通过其append()方法来拼接多个String。

因为Spannable等最终都实现了CharSequence接口,所以可以直接SpannableString和SpannableStringBuilder通过TextView.setText()设置给TextView。

SetSpan()

void setSpan (Object what, int start, int end, int flags)

函数意义:给SpannableString或SpannableStringBuilder特定范围的字符串设定Span样式,可以设置多个(比如同时加上下划线和删除线等),Falg参数标识了当在所标记范围前和标记范围后紧贴着插入新字符时的动作,即是否对新插入的字符应用同样的样式。

参数说明:

object what :对应的各种Span;

int start:开始应用指定Span的位置,索引从0开始

int end:结束应用指定Span的位置,特效并不包括这个位置。比如如果这里数为3(即第4个字符),第4个字符不会有任何特效。

int flags:取值有如下四个

Spannable.SPAN_EXCLUSIVE_EXCLUSIVE:前后都不包括,即在指定范围的前面和后面插入新字符都不会应用新样式

Spannable.SPAN_EXCLUSIVE_INCLUSIVE :前面不包括,后面包括。即仅在范围字符的后面插入新字符时会应用新样式

Spannable.SPAN_INCLUSIVE_EXCLUSIVE :前面包括,后面不包括。

Spannable.SPAN_INCLUSIVE_INCLUSIVE :前后都包括。

基本使用

activity

package com.wangjian.wjspannablestringbuilder;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends Activity {
private ListView mListView;
private SpannableStringBuilder ssb;
private List<SpannableStringBuilder> mSpannableData;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mListView = (ListView) findViewById(R.id.lvShowText);
setSpannableData();
mListView.setAdapter(new ListViewAdapter(this,mSpannableData));
}

/**
* 初始化数据
*/
private void setSpannableData() {
if(mSpannableData == null){
mSpannableData = new ArrayList<SpannableStringBuilder>();
}

//1.为指定的区间[1,4)设置指定的颜色
ssb = new SpannableStringBuilder("为指定的区间[1,4)设置指定的颜色");
ssb.setSpan(new ForegroundColorSpan(Color.GREEN),1,4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

mSpannableData.add(ssb);

}
}


Activity的setSpannableData()方法里面进行了对字符串设置样式,然后将该带样式字符串添加到了数据集合中。

ListViewAdapter

package com.wangjian.wjspannablestringbuilder;

import android.content.Context;
import android.text.SpannableStringBuilder;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import java.util.List;

/**
* Created by Administrator on 2016/1/12.
*/
public class ListViewAdapter extends BaseAdapter {
private Context context;
private List<SpannableStringBuilder> mSpannableData;

public ListViewAdapter(Context context,List<SpannableStringBuilder> mSpannableData){
this.context = context;
this.mSpannableData = mSpannableData;
}

@Override
public int getCount() {
return mSpannableData.size();
}

@Override
public Object getItem(int position) {
return mSpannableData.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
if(convertView == null){
convertView = View.inflate(context,R.layout.item_listview,null);
viewHolder = new ViewHolder();
viewHolder.textView = (TextView) convertView.findViewById(R.id.tvShowText);
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.textView.setText(mSpannableData.get(position));

return convertView;
}

class ViewHolder{
TextView textView;
}
}


Adapater对TextView进行了数据的填充,由于SpannableStringBuilder实现了CharSequence接口,所以可以直接调用TextView的setText()方法进行设置文本。

*MainActivity的布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ListView
android:id="@+id/lvShowText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="20dp" />
</RelativeLayout>


ListView的item布局

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >

<TextView
android:id="@+id/tvShowText"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

</RelativeLayout>


效果图如下



由于只有一条数据,所以只有这么一项。下面在数据中加入一些我们常用的一些其他的样式。修改Activity中的setSpannableData()方法,为mSpannableData再添加一些数据。

Activity代码

public class MainActivity extends Activity {

//...
private void setSpannableData() {
if(mSpannableData == null){
mSpannableData = new ArrayList<SpannableStringBuilder>();
}

//1.为指定的区间[1,4)设置指定的颜色
ssb = new SpannableStringBuilder("为指定的区间[1,4)设置指定的颜色");
ssb.setSpan(new ForegroundColorSpan(Color.GREEN), 1, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

mSpannableData.add(ssb);

ssb = new SpannableStringBuilder("追加字符");
ssb.append("fuck!");// 基本使用_2:追加字符
ssb.setSpan(new ForegroundColorSpan(Color.RED), 4, 8, Spannable.SPAN_MARK_POINT);
mSpannableData.add(ssb);

ssb = new SpannableStringBuilder("设置字体背景色 ");
ssb.setSpan(new BackgroundColorSpan(Color.GRAY), new String("设置字体").length(), new String("设置字体背景色 ").length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // 设置背景色为青色
mSpannableData.add(ssb);

ssb = new SpannableStringBuilder("设置字体背景色 Long值方式");
ssb.setSpan(new BackgroundColorSpan(Color.GREEN), new String("设置字体").length(), new String("设置字体背景色 ").length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // 设置背景色为青色
mSpannableData.add(ssb);

ssb = new SpannableStringBuilder("设置下划线");
// 设置下划线
ssb.setSpan(new UnderlineSpan(), 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mSpannableData.add(ssb);

ssb = new SpannableStringBuilder("设置删除线");
ssb.setSpan(new StrikethroughSpan(),  0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mSpannableData.add(ssb);

ssb = new SpannableStringBuilder("设置上下标:y=x3+An");

ssb.setSpan(new SuperscriptSpan(), new String("设置上下标:y=x").length(), new String("设置上下标:y=x2").length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // 下标
ssb.setSpan(new SubscriptSpan(),  new String("设置上下标:y=x3+a").length(), new String("设置上下标:y=x3+an").length(),Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // 上标
mSpannableData.add(ssb);

//设置超级链接
ssb = new SpannableStringBuilder("超级链接:电话 ");
ssb.setSpan(new URLSpan("tel:13912345678"), 5, 7, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // 电话
mSpannableData.add(ssb);

ssb = new SpannableStringBuilder("超级链接:邮件 ");
ssb.setSpan(new URLSpan("mailto:webmaster@google.com"),5, 7,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // 邮件
ssb.setSpan(new ForegroundColorSpan(Color.YELLOW),5, 7,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mSpannableData.add(ssb);

ssb = new SpannableStringBuilder("超级链接:网络 ");
ssb.setSpan(new URLSpan("http://www.baidu.com"), 5, 7, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // 网络
ssb.setSpan(new ForegroundColorSpan(Color.LTGRAY),5, 7,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mSpannableData.add(ssb);

ssb = new SpannableStringBuilder("超级链接:短信 ");
ssb.setSpan(new URLSpan("sms:13912345678"), 5, 7, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // 短信
ssb.setSpan(new ForegroundColorSpan(Color.BLUE),5, 7,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mSpannableData.add(ssb);

ssb = new SpannableStringBuilder("超级链接:地图 ");
ssb.setSpan(new URLSpan("geo:38.899533,-77.036476"), 5, 7,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); //
ssb.setSpan(new ForegroundColorSpan(Color.GREEN),5, 7,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mSpannableData.add(ssb);

// 注意:设置链接后,指定区间的文本会变成蓝色,会遮住以前设置的颜色,所以应在设置链接后再为指定区间的文字设置颜色
ssb = new SpannableStringBuilder("设置链接:文本 ");
ssb.setSpan(new URLSpan("cacaca") {
@Override
public void onClick(View widget) {
Toast.makeText(MainActivity.this, "点击了设置的链接", 0).show();
}
}, 5, 7, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new ForegroundColorSpan(Color.RED),5, 7,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mSpannableData.add(ssb);

ssb = new SpannableStringBuilder("设置项目符号");
ssb.setSpan(new BulletSpan(android.text.style.BulletSpan.STANDARD_GAP_WIDTH, Color.GREEN),  0, new String("设置项目符号").length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // 第一个参数表示项目符号占用的宽度,第二个参数为项目符号的颜色
mSpannableData.add(ssb);

ssb = new SpannableStringBuilder("设置字体样式正常,粗体,斜体,粗斜体 ");
// 设置字体样式正常,粗体,斜体,粗斜体
ssb.setSpan(new StyleSpan(android.graphics.Typeface.NORMAL), 6, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // 正常
ssb.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 9, 12, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // 粗体
ssb.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 12, 15, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // 斜体
ssb.setSpan(new StyleSpan(android.graphics.Typeface.BOLD_ITALIC), 15, 18, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // 粗斜体
mSpannableData.add(ssb);

// 设置字体(default,default-bold,monospace,serif,sans-serif)
String str = "设置字体(default,default-bold,monospace,serif,sans-serif)";
ssb = new SpannableStringBuilder(str);
ssb.setSpan(new TypefaceSpan("default"), 0, new String("设置字体(default,").length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new TypefaceSpan("default-bold"), new String("设置字体(default,").length(), new String("设置字体(default,default-bold,").length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new TypefaceSpan("monospace"), new String("设置字体(default,default-bold,").length(), new String("设置字体(default,default-bold,monospace,").length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new TypefaceSpan("serif"), new String("设置字体(default,default-bold,monospace,").length(), new String("设置字体(default,default-bold,monospace,serif,").length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new TypefaceSpan("sans-serif"), new String("设置字体(default,default-bold,monospace,serif,").length(), new String("设置字体(default,default-bold,monospace,serif,sans-serif)").length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mSpannableData.add(ssb);

ssb = new SpannableStringBuilder("设置字体大小(绝对值:单位:像素/单位:像素)");
ssb.setSpan(new AbsoluteSizeSpan(20),new String("设置字体大小(绝对值,").length(), new String("设置字体大小(绝对值,单位:像素,").length(),  Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new AbsoluteSizeSpan(20, true),new String("设置字体大小(绝对值,单位:像素,").length(), new String("设置字体大小(绝对值,单位:像素,单位:像素)").length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // 第二个参数boolean
// dip,如果为true,表示前面的字体大小单位为dip,否则为像素,上同
mSpannableData.add(ssb);

ssb = new SpannableStringBuilder("设置字体大小(相对值:一半/两倍,单位:像素) 参数表示为默认字体大小的多少倍 ");
ssb.setSpan(new RelativeSizeSpan(0.5f), new String("设置字体大小(相对值:").length(), new String("设置字体大小(相对值:一半/").length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // 0.5f表示默认字体大小的一半
ssb.setSpan(new RelativeSizeSpan(2.0f), new String("设置字体大小(相对值:一半/").length(), new String("设置字体大小(相对值:一半/两倍,").length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // 2.0f表示默认字体大小的两倍
mSpannableData.add(ssb);

ssb = new SpannableStringBuilder("我的后面添加图片:  ");
ssb.setSpan(new ImageSpan(this, R.mipmap.ic_launcher), 9, 10, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

mSpannableData.add(ssb);

ssb = new SpannableStringBuilder("我的中 间添加图片  ");
ssb.setSpan(new ImageSpan(this, R.mipmap.ic_launcher), 3, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

mSpannableData.add(ssb);

ssb = new SpannableStringBuilder("图片点击事件的处理  ");
ssb.setSpan(new ImageSpan(this, R.mipmap.ic_launcher), 3, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
Toast.makeText(MainActivity.this, "图片点击事件的处理 ", Toast.LENGTH_SHORT).show();
}
}, 3, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

mSpannableData.add(ssb);

ssb = new SpannableStringBuilder("更复杂的点击效果");
ssb.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
Toast.makeText(MainActivity.this, "更复杂的点击效果", Toast.LENGTH_SHORT).show();
}
}, 3, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

mSpannableData.add(ssb);

ssb = new SpannableStringBuilder("更复杂的点击效果");
ssb.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
Toast.makeText(MainActivity.this, "更复杂的点击效果", Toast.LENGTH_SHORT).show();
}
public void updateDrawState(TextPaint ds) {
ds.setUnderlineText(false);
}
}, 3, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

mSpannableData.add(ssb);
}

}


注意:想要使点击事件有效果,必须在Adapter中的getView中加上以下这句话,即为listView的item中的具体视图调用该方法,否则点击将没有响应。

viewHolder.textView.setMovementMethod(LinkMovementMethod.getInstance());


运行效果如下



参考博文

SpannableString与SpannableStringBuilder
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: