概述
在TextView显示丰富多彩的文字(一)——如何使用CharacterStyle格式化字符和TextView显示丰富多彩的文字(二)——如何使用ParagraphStyle格式化段落中,知道了如何格式化文本,给文本中的某一范围设置样式,比如前景、背景、图片等;或者给整个段落设置格式,比如BulletSpan、QuoteSpan等。不了解的朋友可以先去看一下。在这篇博客中,我们将自定义我们自己的格式。
两个特殊的CharacterStyle——ImageSpan和URLSpan
下图是CharacterStyle的类继承关系
ImageSpan的特别之处在于它能显示图片吗?不是的,在TextView显示丰富多彩的文字(一)——如何使用CharacterStyle格式化字符里可以看到虽然在文本里面有“图片”两字,但是最后显示却只有图片没有文字了;URLSpan的特别之处在于它能够被点击,可以跳转到设置的URL中。可以参考ImageSpan和URLSpan的类结构。
下图是ImageSpan的继承关系:
其中DynamicDrawableSpan和ReplacementSpan都是抽象的类,DynamicDrawable的子类负责显示具体的Drawable,ReplacementSpan用于替换,所以可以解释为什么ImageSpan只显示了图片而没有显示文字。
下图是URLSpan的继承关系:
URLSpan继承自CliableSpan,实现了其点击事件,源码中就是获取之前设置的URL然后再调用系统浏览器显示URL。可参考URLSpan源码中的点击事件:
@Override
public void onClick(View widget) {
Uri uri = Uri.parse(getURL());
Context context = widget.getContext();
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
Log.w("URLSpan", "Actvity was not found for intent, " + intent.toString());
}
}
在源码中可以看到,点击事件就是获取URL后,再调用系统浏览器显示网页。
自定义ReplacementSpan——TextReplacementSpan
我们知道SpannableString是不可以替换文字的,而SpannableStringBuilder是可以替换文字的,那么我们想实现一种文本替换的格式。因为需要实现替换的功能,所以TextReplacmentSpan继承ReplacmentSpan。可以查看ReplacementSpan的文档。先看实现代码:
/**
* 用文字替换文字
* Created by Xingfeng on 2016-09-11.
*/
public class TextReplacementSpan extends ReplacementSpan {
private CharSequence mRelaceText;
private int mReplaceTextColor;
private int mReplaceTextSize;
public TextReplacementSpan(CharSequence mRelaceText) {
this(mRelaceText, Color.BLACK, 30);
}
public TextReplacementSpan(CharSequence mRelaceText, int mReplaceTextColor, int mReplaceTextSize) {
this.mRelaceText = mRelaceText;
this.mReplaceTextColor = mReplaceTextColor;
this.mReplaceTextSize = mReplaceTextSize;
}
@Override
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
paint.setColor(mReplaceTextColor);
paint.setTextSize(mReplaceTextSize);
int width = (int) paint.measureText(mRelaceText, 0, mRelaceText.length());
if (fm != null) {
fm.ascent = (int) paint.ascent();
fm.descent = (int) paint.descent();
fm.top = fm.ascent;
fm.bottom = fm.descent;
}
return width;
}
@Override
public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
paint.setTextSize(mReplaceTextSize);
paint.setColor(mReplaceTextColor);
canvas.drawText(mRelaceText, 0, mRelaceText.length(), x, y, paint);
}
}
可以看到我们不仅可以设置替换的文本,还可以设置文本的颜色和文本的字体大小,并且有默认的颜色和大小。ReplacementSpan是抽象类,有两个抽象方法,getSize和draw。其中getSize的说明如下:
可以看到返回值是Span的宽度,对于我们这个就是所设置文本的宽度。另外还说了子类可以通过更新Paint.FontMetricsInt属性来设置Span的高度。如果Span覆盖了整个文字的高度,而高度没有设置的话,那么draw方法不会被调用。所以我们的实现就是根据设置的文本和文本尺寸得到宽度,并且设置FontMetricsInt的属性(参考DynamicDrawableSpan的实现)。下面是DynamicDrawableSpan的实现。
@Override
public int getSize(Paint paint, CharSequence text,
int start, int end,
Paint.FontMetricsInt fm) {
Drawable d = getCachedDrawable();
Rect rect = d.getBounds();
if (fm != null) {
fm.ascent = -rect.bottom;
fm.descent = 0;
fm.top = fm.ascent;
fm.bottom = 0;
}
return rect.right;
}
另外一个方法draw的文档如下:
该方法用于将格式添加进Canvas中,而我们这儿需要做的就是把替换的文字写进去,不过要记得在这之前先设置Paint的颜色和文字尺寸。
下面是用“阿里巴巴”替换“百度”的的前后效果。前面一张为原始效果,下面一张为替换效果。
代码如下:
SpannableString spannableString = new SpannableString(text);
ReplacementSpan replacementSpan = new TextReplacementSpan("阿里巴巴", Color.RED,50);
spannableString.setSpan(replacementSpan,0,2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
urlTextView.setText(spannableString);
就是使用自定义的TextReplacementSpan替换文字。
这里有个用的很多的格式,就是MetricAffectingSpan类,很多类都继承自这个类,这个类主要负责影响文字的外观,比如颜色、尺寸等。
使用广泛的ParagraphStyle——LeadingMarginSpan
在TextView显示丰富多彩的文字(二)—————在段落级别改变中有很多LeadingMarginSpan的实现类,都可以实现类似paddingLeft的效果,只不过是绘画的东西不同。BulletSpan是一种类似无序段落的格式,下面我们将实现一种类似有序段落的格式。
自定义LeadingMarginSpan——OrderSpan
先看LeadingMarginSpan接口。如下:
可以看到LeadingMarginSpan接口有两个方法,第一个用于绘画margin部分,第二个方法用于返回margin的宽度。==需要注意的是,当TextView每显示一行的时候都会调用这两个方法==
下面先看BulletSpan的实现,源码如下:
public class BulletSpan implements LeadingMarginSpan, ParcelableSpan {
private final int mGapWidth;
private final boolean mWantColor;
private final int mColor;
private static final int BULLET_RADIUS = 3;
private static Path sBulletPath = null;
public static final int STANDARD_GAP_WIDTH = 2;
public BulletSpan() {
mGapWidth = STANDARD_GAP_WIDTH;
mWantColor = false;
mColor = 0;
}
public BulletSpan(int gapWidth) {
mGapWidth = gapWidth;
mWantColor = false;
mColor = 0;
}
public BulletSpan(int gapWidth, int color) {
mGapWidth = gapWidth;
mWantColor = true;
mColor = color;
}
public BulletSpan(Parcel src) {
mGapWidth = src.readInt();
mWantColor = src.readInt() != 0;
mColor = src.readInt();
}
public int getSpanTypeId() {
return getSpanTypeIdInternal();
}
/** @hide */
public int getSpanTypeIdInternal() {
return TextUtils.BULLET_SPAN;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
writeToParcelInternal(dest, flags);
}
/** @hide */
public void writeToParcelInternal(Parcel dest, int flags) {
dest.writeInt(mGapWidth);
dest.writeInt(mWantColor ? 1 : 0);
dest.writeInt(mColor);
}
public int getLeadingMargin(boolean first) {
return 2 * BULLET_RADIUS + mGapWidth;
}
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir,
int top, int baseline, int bottom,
CharSequence text, int start, int end,
boolean first, Layout l) {
if (((Spanned) text).getSpanStart(this) == start) {
Paint.Style style = p.getStyle();
int oldcolor = 0;
if (mWantColor) {
oldcolor = p.getColor();
p.setColor(mColor);
}
p.setStyle(Paint.Style.FILL);
if (c.isHardwareAccelerated()) {
if (sBulletPath == null) {
sBulletPath = new Path();
// Bullet is slightly better to avoid aliasing artifacts on mdpi devices.
sBulletPath.addCircle(0.0f, 0.0f, 1.2f * BULLET_RADIUS, Direction.CW);
}
c.save();
c.translate(x + dir * BULLET_RADIUS, (top + bottom) / 2.0f);
c.drawPath(sBulletPath, p);
c.restore();
} else {
c.drawCircle(x + dir * BULLET_RADIUS, (top + bottom) / 2.0f, BULLET_RADIUS, p);
}
if (mWantColor) {
p.setColor(oldcolor);
}
p.setStyle(style);
}
}
}
BulletSpan的getLeadingMargin方法返回的是圆点的直径和距文本的距离。其中参数为true的,代表的是第一行,否则代表其他行。(LeadingMarginSpan.Standard区分了这个参数,所以可以实现首行缩进的功能)drawLeadingMargin就是画个圆点,其中还区分了是否设置了硬件加速。下面我们仿造BulletSpan实现我们的OrderSpan。
/**
* 有序段落格式
* Created by Xingfeng on 2016-09-11.
*/
public class OrderSpan implements LeadingMarginSpan {
private final int mOrder;//序号
private int mOrderColor;//序号颜色
private int mGapWidth;
private boolean hasSetColor;
public static final int STANDARD_GAP_WIDTH = 20;
public OrderSpan() {
this(1);
}
public OrderSpan(int mOrder) {
this(mOrder, STANDARD_GAP_WIDTH);
}
public OrderSpan(int mOrder, int mGapWidth) {
this.mOrder = mOrder;
this.mGapWidth = mGapWidth;
hasSetColor = false;
}
public OrderSpan(int mOrder, int mOrderColor, int mGapWidth) {
this.mOrder = mOrder;
this.mOrderColor = mOrderColor;
this.mGapWidth = mGapWidth;
hasSetColor = true;
}
/**
* 返回Margin的宽度,文本宽度+gapWidth
*
* @param first
* @return
*/
@Override
public int getLeadingMargin(boolean first) {
return 20 + mGapWidth;
}
@Override
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
if (((Spanned) text).getSpanStart(this) == start) {
Paint.Style style = p.getStyle();
int oldcolor = p.getColor();
if (hasSetColor) {
p.setColor(mOrderColor);
}
String orderText = String.valueOf(mOrder);
c.drawText(orderText, 0, orderText.length(), x, baseline, p);
p.setColor(oldcolor);
p.setStyle(style);
}
}
}
其中可以设置序号的颜色,距文本的距离,但是字体大小和文本大小一样大,不过也可以添加设置文本大小的属性。其中返回的margin是个定值,如果需要首行缩进的话,可以让非首行返回宽度0。其中绘画margin区域时,首先保存了Paint的样式,再设置了自己的样式后,写完序号后,又恢复了Paint的样式,这是因为在第一行如果我们改变了Paint的样式,而没有恢复的话,那么以后的每一行的Paint都是我们在第一行做出了改变的Paint。这是因为上面所说的,在TextView绘制每一行文本的时候,都会调用这个方法,都会传进来相同的一个Paint对象。下面看示例代码和效果。
private TextView mParagraph1;
private TextView mParagraph2;
private String paragraph = "中新网t北京9月10日电 今天," +
"连通东西部多条铁路干线的郑徐高铁将正式通车运营," +
"中国高铁网络再次得到完善,东中西部民众的高铁出行也" +
"更加便利。与此同时,今天开始,受郑徐高铁开通影响," +
"全国铁路再次迎来一次运行图大调整。";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_paragraph);
mParagraph1 = (TextView) findViewById(R.id.paragraph_1);
mParagraph2 = (TextView) findViewById(R.id.paragraph_2);
//默认实现,序号为1,颜色和尺寸都和文本内容一致
OrderSpan orderSpan1 = new OrderSpan();
SpannableString spannableString = new SpannableString(paragraph);
spannableString.setSpan(orderSpan1, 0, paragraph.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
mParagraph1.setText(spannableString);
OrderSpan orderSpan2 = new OrderSpan(2, Color.RED, 40);
spannableString.removeSpan(orderSpan1);//移除OrderSpan1
spannableString.setSpan(orderSpan2, 0, paragraph.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);//添加OrderSpan2
mParagraph2.setText(spannableString);
}
我们第一个使用的是OrderSpan的默认实现,第二个是使用了自己设置的参数,将序号颜色变为红色,并且设置了距文本的距离。效果如下:
可以看到第二个序号为红色,且距离文字更远了。
总结
通过上面的例子,我们自定义了自己的TextReplacementSpan和OrderSpan,掌握了该如何实现自己的格式。代码可以到我的github查看,链接
最后
以上就是真实大碗为你收集整理的TextView显示丰富多彩的文字(三)——自定义CharacterStyle和ParagraphStyle显示效果两个特殊的CharacterStyle——ImageSpan和URLSpan使用广泛的ParagraphStyle——LeadingMarginSpan总结的全部内容,希望文章能够帮你解决TextView显示丰富多彩的文字(三)——自定义CharacterStyle和ParagraphStyle显示效果两个特殊的CharacterStyle——ImageSpan和URLSpan使用广泛的ParagraphStyle——LeadingMarginSpan总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复