共计 4887 个字符,预计需要花费 13 分钟才能阅读完成。
在开发 Android App 时,常常会遇到各种协定,并且有些文字是灰色的,有些蓝色的,能够点击跳转,对于这种状况,其实咱们是能够对它进行一些封装的,因为这些性能都是通用的,成果如下。
能够看到,协定内容除了各种协定外,还蕴含很多的形容文案。对于这种需要,咱们能够通过 SpannableStringBuilder 来实现。首先,新建一个 TextUtils 工具类,它基于 SpannableStringBuilder 实现,代码如下。
public class TextUtils {public static Builder getBuilder() {return new Builder(); | |
} | |
public static class Builder { | |
private SpannableStringBuilder strBuilder; | |
private Builder() {strBuilder = new SpannableStringBuilder(); | |
} | |
public Builder append(CharSequence text) {strBuilder.append(text); | |
return this; | |
} | |
public Builder append(CharSequence text, int color) {int start = strBuilder.length(); | |
strBuilder.append(text); | |
int end = strBuilder.length(); | |
strBuilder.setSpan(new ForegroundColorSpan(color), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | |
return this; | |
} | |
public Builder replace(CharSequence text, int color, String... replaces) {strBuilder.append(text); | |
for (int i = 0; i < replaces.length; i++) {String replace = replaces[i]; | |
int start = text.toString().indexOf(replace); | |
if (start >= 0) {int end = start + replace.length(); | |
strBuilder.setSpan(new ForegroundColorSpan(color), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | |
} | |
} | |
return this; | |
} | |
public Builder click(CharSequence text, final int color, final OnClickListener onClickListener,String... clickTexts) {strBuilder.append(text); | |
for (int i = 0; i < clickTexts.length; i++) {String clickText = clickTexts[i]; | |
final int postion=i; | |
int start = text.toString().indexOf(clickText); | |
if (start >= 0) {int end = start + clickText.length(); | |
strBuilder.setSpan(new ClickableSpan() { | |
@Override | |
public void onClick(View view) {if (onClickListener != null) {onClickListener.onClick(postion); | |
} | |
} | |
@Override | |
public void updateDrawState(TextPaint ds) {super.updateDrawState(ds); | |
ds.setColor(color); | |
ds.setUnderlineText(false); | |
} | |
}, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | |
} | |
} | |
return this; | |
} | |
private boolean isChecked = false; | |
// 设置复选框 因为该办法没有调 strBuilder.append(),故请务必在调用该办法前保障 strBuilder 不为空,即调用了后面的办法 | |
public Builder checkBox(Context context, TextView tv, OnImageClickListener listener){setImageSpan(context, strBuilder, R.drawable.xzhhr_icon_circle2x); | |
strBuilder.setSpan(new ClickableSpan() { | |
@Override | |
public void onClick(@NonNull View view) { | |
isChecked = !isChecked; | |
if (isChecked){setImageSpan(context, strBuilder, R.drawable.xzhhr_icon_tick2x); | |
tv.setText(strBuilder);// 刷新显示 | |
listener.onChecked();} else {setImageSpan(context, strBuilder, R.drawable.xzhhr_icon_circle2x); | |
tv.setText(strBuilder); | |
listener.onUnChecked();} | |
} | |
@Override | |
public void updateDrawState(TextPaint ds) {super.updateDrawState(ds); | |
ds.setColor(Color.WHITE); | |
ds.setUnderlineText(false); | |
} | |
}, 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | |
return this; | |
} | |
public Builder clickInto(TextView tv) {tv.setMovementMethod(LinkMovementMethod.getInstance());// 设置可点击状态 | |
tv.setHighlightColor(Color.TRANSPARENT); // 设置点击后的色彩为通明 | |
tv.setText(strBuilder); | |
return this; | |
} | |
public Builder into(TextView tv) {tv.setText(strBuilder); | |
return this; | |
} | |
} | |
public interface OnClickListener {void onClick(int position); | |
} | |
public interface OnImageClickListener{void onChecked(); | |
void onUnChecked();} | |
private static void setImageSpan(Context context, SpannableStringBuilder builder, int resourceId){MyImageSpan imageSpan = new MyImageSpan(context, resourceId, 2);// 居中对齐 | |
builder.setSpan(imageSpan, 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | |
} | |
public static class MyImageSpan extends ImageSpan{ | |
// 因为这里文字存在换行, 零碎的 ImageSpan 图标无奈进行居中,所以咱们自定义一个 ImageSpan, 重写 draw 办法, 解决了该问题 | |
public MyImageSpan(@NonNull Context context, int resourceId, int verticalAlignment) {super(context, resourceId, verticalAlignment); | |
} | |
@Override | |
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {Drawable drawable = getDrawable(); | |
canvas.save(); | |
// 获取画笔的文字绘制时的具体测量数据 | |
Paint.FontMetricsInt fm = paint.getFontMetricsInt(); | |
int transY = bottom - drawable.getBounds().bottom; | |
if (mVerticalAlignment == ALIGN_BASELINE) {transY -= fm.descent;} else if (mVerticalAlignment == ALIGN_CENTER) {// 自定义居中对齐 | |
// 与文字的中间线对齐(这种形式不管是否设置行间距都能保障文字的中间线和图片的中间线是对齐的)// y+ascent 失去文字内容的顶部坐标,y+descent 失去文字的底部坐标,(顶部坐标 + 底部坐标)/2= 文字内容中间线坐标 | |
transY = ((y + fm.descent) + (y + fm.ascent)) / 2 - drawable.getBounds().bottom / 2;} | |
canvas.translate(x, transY); | |
drawable.draw(canvas); | |
canvas.restore();} | |
} | |
} |
而后,在须要应用的中央引入即可,如下所示。
//\u3000 实现占位缩进 | |
<string name="company_partner_protocol">\u3000\u3000 我已认真浏览《委托付款协定》的全部内容,批准并承受《隐衷政策》全副条款。嘉联账户和单干账户余额提现时,将扣除 x%% 的服务费;</string> | |
TextUtils.getBuilder().click(getResources().getString(R.string.company_partner_protocol), getResources().getColor(R.color.blue), new TextUtils.OnClickListener() { | |
@Override | |
public void onClick(int position) {switch (position){ | |
case 0: | |
// 跳转链接 | |
WebviewActivity.newInstance(CompanyPartner2Activity.this, Config.WITHDRAW_AGREEMENT, ""); | |
break; | |
case 1: | |
WebviewActivity.newInstance(CompanyPartner2Activity.this, Config.PRIVACY, "隐衷政策"); | |
break; | |
} | |
} | |
}, "《委托付款协定》", "《隐衷政策》").checkBox(this, tv_protocol, new TextUtils.OnImageClickListener() { | |
@Override | |
public void onChecked() {btn_commit.setEnabled(true); | |
// ToastUtils.showToast(CompanyPartner2Activity.this, "checked"); | |
} | |
@Override | |
public void onUnChecked() {btn_commit.setEnabled(false); | |
// ToastUtils.showToast(CompanyPartner2Activity.this, "unChecked"); | |
} | |
}).clickInto(tv_protocol); |
其中,tv_protocol 就是咱们的 TextView 组件。
正文完