3

When developing Android App, we often encounter various protocols, and some of the text is gray, some blue, you can click to jump. In this case, we can actually encapsulate it because of these functions All are universal, and the effect is as follows.
在这里插入图片描述
It can be seen that in addition to various agreements, the agreement also contains a lot of description text. For this demand, we can achieve it through SpannableStringBuilder. First, create a new TextUtils tool class, which is implemented based on SpannableStringBuilder, and the code is as follows.

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();
        }
    }
}

Then, just import it where you need to use it, as shown below.

 
//\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);

Among them, tv_protocol is our TextView component.


xiangzhihong
5.9k 声望15.3k 粉丝

著有《React Native移动开发实战》1,2,3、《Kotlin入门与实战》《Weex跨平台开发实战》、《Flutter跨平台开发与实战》1,2、《Android应用开发实战》和《鸿蒙HarmonyOS应用开发实践》