Android自定义用户协议的解决方案

Android自定义用户协议的解决方案

在开发Android App时,经常会遇到各种协议,并且有些文字是灰色的,有些蓝色的,可以点击跳转,对于这种情况,其实我们是可以对它进行一些封装的,因为这些功能都是通用的,效果如下。

在这里插入图片描述

可以看到,协议内容除了各种协议外,还包含很多的描述文案。对于这种需求,我们可以通过SpannableStringBuilder来实现。首先,新建一个TextUtils工具类,它基于SpannableStringBuilder实现,代码如下。

scss 复制代码
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();
        }
    }
}

复制

然后,在需要使用的地方引入即可,如下所示。

csharp 复制代码
//\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组件。

相关推荐
阿巴斯甜16 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker17 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952718 小时前
Andorid Google 登录接入文档
android
黄林晴19 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android