Android 多协议显示工具类
该工具类针对协议数量不固定的场景使用
/**
* 多协议显示工具类
*/
public class MultiProtocolUtils {
public static Builder getBuilder() {
return new Builder();
}
/**
* 创建同意协议全文
*
* @param prefix 同意协议前缀
* @param protocols 协议列表
* @return 同意协议全文
*/
public static String createProtocolStr(String prefix, List<String> protocols) {
StringBuilder sb = new StringBuilder();
//添加空格,为单选框预留位置
sb.append("\t");
sb.append("\t");
sb.append(prefix);
if (protocols != null && !protocols.isEmpty()) {
for (int i = 0; i < protocols.size(); i++) {
//最后一个协议前加“和”
if (protocols.size() > 1 && i == protocols.size() - 1) {
sb.append("和");
}
sb.append(protocols.get(i));
//协议后加、
if (i < protocols.size() - 2) {
sb.append("、");
}
}
}
Log.e("createProtocolStr", sb.toString());
return sb.toString();
}
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 (String replace : replaces) {
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;
}
/**
* @param text 协议全文
* @param color 协议颜色
* @param onClickListener 协议点击事件
* @param clickTexts 协议标题集合
* @return
*/
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 position = 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(position);
}
}
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setColor(color);
ds.setUnderlineText(false);
}
}, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
return this;
}
/**
* @param text 协议全文
* @param color 协议颜色
* @param onClickListener 协议点击事件
* @param clickTexts 协议标题集合
* @return
*/
public Builder click(CharSequence text, final int color, final OnClickListener onClickListener, List<String> clickTexts) {
strBuilder.append(text);
for (int i = 0; i < clickTexts.size(); i++) {
String clickText = clickTexts.get(i);
final int position = 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(position);
}
}
@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不为空,即调用了前面的方法
/**
* @param context 上下文
* @param tv 文本控件
* @param checkImg 单选框选中图片
* @param unCheckImg 单选框未选中图片
* @param listener 单选框点击事件监听
* @return
*/
public Builder checkBox(Context context, TextView tv, int unCheckImg, int checkImg, OnImageClickListener listener) {
setImageSpan(context, strBuilder, unCheckImg);
strBuilder.setSpan(new ClickableSpan() {
@Override
public void onClick(@NonNull View view) {
isChecked = !isChecked;
if (isChecked) {
setImageSpan(context, strBuilder, checkImg);
tv.setText(strBuilder);//刷新显示
listener.onChecked();
} else {
setImageSpan(context, strBuilder, unCheckImg);
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;
}
/**
* @param tv 点击事件绑定控件
* @return
*/
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();
}
}
}
使用方式
MultiProtocolUtils
.getBuilder()
.click(
MultiProtocolUtils.createProtocolStr("本人已阅读并同意签署", protocolList),
Color.parseColor("#3063E7"),
{ position ->
run {
//协议点击跳转
)
}
}, protocolList
).checkBox(
mContext,
tvProtocol,
R.mipmap.ic_check_blue20,
R.mipmap.ic_check_blue21,
object : MultiProtocolUtils.OnImageClickListener {
override fun onChecked() {
//单选框选中
}
override fun onUnChecked() {
//单选框未选中
}
}).clickInto(tvProtocol)