TextView局部字体超链接处理方案

还是之前的老项目问题,应法务部门的要求要将之前客户端的相关条款调整一下,每条内容中还有多个条款可方便用户进行点击查看详细内容,本来没啥问题,当去寻找内容所在的时候却发现都是在本地配置的,并且是xml写死的,而且对于TextView中的条款却是采用多个TextView进行组合而成的。擦类,对于产品的要求要多种情况进行变换,还要调整大小屏,在我的思想里这……必须要换了。

问题及解决方案

针对文本TextVieww中含有多个不同的样式子串或者文本中含有多个超链接的问题其实解决方案还是挺多的。这里就列举几个:

  • 采用html标签对文本多样化设计。
  • 采用在xml文件中配置autoLink属性。
  • 采用SpannableStringBuilder进行多样化设置。

1.采用html标签对文本多样化设计

在代码中可直接采用Html.fromHtml():

TextView test_tv = findViewById(R.id.test_tv);
test_tv.setText(Html.fromHtml("<a href=\"http://www.baidu.com/\">地瓜地瓜这里是百度</a>"));

还可以直接在xml布局中设置,不过html要写在资源文件String里面。

    <TextView
        android:id="@+id/test_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/linktext"
        />
<resources>
    <string name="app_name">My Application</string>
    <string name="linktext"><a href="http://www.baidu.com/">地瓜地瓜这里是百度</a></string>
</resources> 

此时点击超链接其实并不起作用,我们还要加上一句话才能跳转到系统浏览器里面:

textView.setMovementMethod(LinkMovementMethod.getInstance());

但是有些时候我们想跳转到自己应用中的webview中怎么办呢?
所以我们要进行超链接拦截:

        test_tv = findViewById(R.id.test_tv);
        CharSequence charSequence = Html.fromHtml("地瓜地瓜这里是<a href=\"http://www.baidu.com/\">百度</a>");
        SpannableStringBuilder builder = new SpannableStringBuilder(charSequence);
        URLSpan[] urlSpans = builder.getSpans(0, charSequence.length(), URLSpan.class);
        for (URLSpan span : urlSpans) {
            int start = builder.getSpanStart(span);
            int end = builder.getSpanEnd(span);
            int flag = builder.getSpanFlags(span);
            final String link = span.getURL();
            builder.removeSpan(span); //移除默认的urlspan,去除优先跳到系统默认浏览器而使得拦截失效。
            builder.setSpan(new ClickableSpan() {
                @Override
                public void onClick(View widget) {
                    // 捕获<a>标签点击事件,及对应超链接link,可在这里做跳转。
                    Toast.makeText(MainActivity.this, "你点击的URL是" + link, Toast.LENGTH_SHORT).show();
                }
            }, start, end, flag);
        }
        test_tv.setLinksClickable(true);
        test_tv.setMovementMethod(LinkMovementMethod.getInstance());
        test_tv.setText(builder);

以下是收录的一些TextView可以使用的Html标签:

标签      说明  
<br>        插入一个换行符。
<br>        标签是空标签(意味着它没有结束标签,因此这是错误的:<br></br><p>         定义段落。
<p>         标签会自动在其前后各添加一个空行    
<h1>        定义最大的标题
<h2><h3><h4><h5><h6>        定义最小的标题
<div>       文档分节    
<strong>    把文本定义为语气更强的强调的内容。TextView中表现为文本加粗   
<b>         文本加粗    
<em>        把文本定义为强调的内容。TextView中表现为斜体文本效果。 
<cite>      定义引用。TextView中表现为斜体文本效果。    
<dfn>       标记那些对特殊术语或短语的定义。TextView中表现为斜体文本效果。 
<i>         显示斜体文本效果。   
<big>       呈现大号字体效果    
<small>     呈现小号字体效果    
<strike>    定义删除线样式的文字
<font size="..." color="..." face="...">    规定文本的字体、字体尺寸、字体颜色   color:文本颜色;size:文本大小;face:文本字体
<blockquote><blockquote></blockquote> 之间的文本从常规文本中分离出来。
通常在左、右两边进行缩进,有时使用斜体。    
<tt>        呈现类似打字机或者等宽的文本效果    
<a>         定义超链接。最重要的属性是 href 属性,它指示链接的目标。 href:指示链接的目标
<u>         为文本添加下划线    
<sup>       定义上标文本  
<sub>       定义下标文本  
<img src="..."> 向网页中嵌入一幅图像。<img>标签并不会在网页中插入图像,而是从网页上链接图像。<img>  标签创建的是被引用图像的占位空间。   src:图像的url;alt:图像的替代文本

2.采用在xml文件中配置autoLink属性

TextView有一个叫做autoLink的属性可以将符合指定格式的文本转换为可单击的超链接形式,在帮助文档中也可以发现Android给我们提供了如下几种格式:

  1. none:表示不进行任何匹配,默认;
  2. web:表示匹配web网址,例如http://www.baidu.com
  3. email:表示匹配邮件地址;
  4. phone:表示匹配电话号码;
  5. map:表示匹配地图地址,一般针对google地图;
  6. all:表示将会匹配web、email、phone、map;
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="百度的网址是:http://www.baidu.com"
        android:autoLink="web"
        />

3.采用SpannableString进行多样化设置

以上是文本中含有超链接地址,开发过程中更多的是后台单独给我传递局部字体的超链接地址,移动端除了单独设置超链接还要调节这些局部字体的大小、颜色等样式,因为原文本中不含超链接地址,所以我们也不用移除原有的span了。这里我们采用SpannableStringBuilder来进行多样化设置。

    /**
     * 设置超链接
     *
     * @param tv
     */
    private void settingLink(TextView tv) {
        String content = tv.getText().toString();
        String link = "百度";
        String linkUrl = "http://www.baidu.com";
        if (content.indexOf(link) == -1) {
            return;
        }
        final int start = content.indexOf(link);
        final int end = start + link.length();
        final int flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;

        //构建SpannableStringBuilder
        SpannableStringBuilder builder = new SpannableStringBuilder(content);
        //设置文字的单击事件
        builder.setSpan(new CustomUrlSpan(this, linkUrl), start, end, flag);
        //设置文字的前景色
        builder.setSpan(new ForegroundColorSpan(Color.RED), start, end, flag);

        tv.setLinksClickable(true);
        tv.setMovementMethod(LinkMovementMethod.getInstance());
        tv.setText(builder);
    }

默认情况下超链接部分都有一条下划线,为了美观性必要情况下我们要将下划线去掉,所以可以设置匿名内部类ClickableSpan扩充updateDrawState方法处理,这里为了程序功能发展性和模块化单独自定义个内部类继承ClickableSpan做处理:

    /**
     * 自定义ClickableSpan内部类,单独对点击事件处理
     */
    class CustomUrlSpan extends ClickableSpan {

        private Context context;
        private String url;

        public CustomUrlSpan(Context context, String url) {
            this.context = context;
            this.url = url;
        }

        @Override
        public void onClick(View widget) {
            Intent intent = new Intent(context, WebViewActivity.class);
            intent.putExtra(WebViewActivity.WEB_URL, url);
            context.startActivity(intent);
        }

        @Override
        public void updateDrawState(TextPaint ds) {
            super.updateDrawState(ds);
            //去除超链接下划线
            ds.setUnderlineText(false);
        }
    }

SpannableStringBuilder有个亲兄弟——SpannableString。是不是觉得有点熟悉?相信大部分童鞋之前也用过这个。SpannableStringBuilder和SpannableString的区别类似与StringBuilder、String,就是SpannableStringBuilder可以拼接,而SpannableString不可拼接。

SpannableStringBuilder和SpannableString主要通过使用setSpan(Object what, int start, int end, int flags)改变文本样式,上边的实例主要用的就是这个方法。
对应的参数:

  • start: 指定Span的开始位置。
  • end: 指定Span的结束位置,并不包括这个位置。
  • flags:取值有如下四个:

    • Spannable.SPAN_EXCLUSIVE_INCLUSIVE:开始坐标不包括,结束坐标包括。
    • Spannable.SPAN_INCLUSIVE_EXCLUSIVE:开始坐标包括,结束坐标不包括。
    • Spannable.SPAN_INCUJSIVE_INCLUSIVE:开始和结束坐标都包括。
    • Spannable.SPAN_EXCLUSIVE_EXCLUSIVE:开始和结束坐标都不包括。
  • what: 对应的各种Span,不同的Span对应不同的样式。已知的可用类有:

    • BackgroundColorSpan : 文本背景色
    • ForegroundColorSpan : 文本颜色
    • MaskFilterSpan : 修饰效果,如模糊(BlurMaskFilter)浮雕
    • RasterizerSpan : 光栅效果
    • StrikethroughSpan : 删除线
    • SuggestionSpan : 相当于占位符
    • UnderlineSpan : 下划线
    • AbsoluteSizeSpan : 文本字体(绝对大小)
    • DynamicDrawableSpan : 设置图片,基于文本基线或底部对齐
    • ImageSpan : 图片
    • RelativeSizeSpan : 相对大小(文本字体)
    • ScaleXSpan : 基于x轴缩放
    • StyleSpan : 字体样式:粗体、斜体等
    • SubscriptSpan : 下标(数学公式会用到)
    • SuperscriptSpan : 上标(数学公式会用到)
    • TextAppearanceSpan : 文本外貌(包括字体、大小、样式和颜色)
    • TypefaceSpan : 文本字体
    • URLSpan : 文本超链接
    • ClickableSpan : 点击事件

感谢Blankj童鞋将SpannableStringBuilder完美封装成一个工具类,大家可以根据需求进行参考:

public class SpannableStringUtils {
    private SpannableStringUtils() {
        throw new UnsupportedOperationException("u can't instantiate me...");
    }

    /**
     * 获取建造者 * * @return {@link Builder}
     */
    public static Builder getBuilder(@NonNull CharSequence text) {
        return new Builder(text);
    }

    public static class Builder {
        private int defaultValue = 0x12000000;
        private CharSequence text;
        private int flag;
        @ColorInt
        private int foregroundColor;
        @ColorInt
        private int backgroundColor;
        @ColorInt
        private int quoteColor;
        private boolean isLeadingMargin;
        private int first;
        private int rest;
        private boolean isBullet;
        private int gapWidth;
        private int bulletColor;
        private float proportion;
        private float xProportion;
        private boolean isStrikethrough;
        private boolean isUnderline;
        private boolean isSuperscript;
        private boolean isSubscript;
        private boolean isBold;
        private boolean isItalic;
        private boolean isBoldItalic;
        private String fontFamily;
        private Alignment align;
        private boolean imageIsBitmap;
        private Bitmap bitmap;
        private boolean imageIsDrawable;
        private Drawable drawable;
        private boolean imageIsUri;
        private Uri uri;
        private boolean imageIsResourceId;
        @DrawableRes
        private int resourceId;
        private ClickableSpan clickSpan;
        private String url;
        private boolean isBlur;
        private float radius;
        private Blur style;
        private SpannableStringBuilder mBuilder;

        private Builder(@NonNull CharSequence text) {
            this.text = text;
            flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
            foregroundColor = defaultValue;
            backgroundColor = defaultValue;
            quoteColor = defaultValue;
            proportion = -1;
            xProportion = -1;
            mBuilder = new SpannableStringBuilder();
        }

        /**
         * 设置标识 * * @param flag <ul> * <li>{@link Spanned#SPAN_INCLUSIVE_EXCLUSIVE}</li> * <li>{@link Spanned#SPAN_INCLUSIVE_INCLUSIVE}</li> * <li>{@link Spanned#SPAN_EXCLUSIVE_EXCLUSIVE}</li> * <li>{@link Spanned#SPAN_EXCLUSIVE_INCLUSIVE}</li> * </ul> * @return {@link Builder}
         */
        public Builder setFlag(int flag) {
            this.flag = flag;
            return this;
        }

        /**
         * 设置前景色 * * @param color 前景色 * @return {@link Builder}
         */
        public Builder setForegroundColor(@ColorInt int color) {
            this.foregroundColor = color;
            return this;
        }

        /**
         * 设置背景色 * * @param color 背景色 * @return {@link Builder}
         */
        public Builder setBackgroundColor(@ColorInt int color) {
            this.backgroundColor = color;
            return this;
        }

        /**
         * 设置引用线的颜色 * * @param color 引用线的颜色 * @return {@link Builder}
         */
        public Builder setQuoteColor(@ColorInt int color) {
            this.quoteColor = color;
            return this;
        }

        /**
         * 设置缩进 * * @param first 首行缩进 * @param rest 剩余行缩进 * @return {@link Builder}
         */
        public Builder setLeadingMargin(int first, int rest) {
            this.first = first;
            this.rest = rest;
            isLeadingMargin = true;
            return this;
        }

        /**
         * 设置列表标记 * * @param gapWidth 列表标记和文字间距离 * @param color 列表标记的颜色 * @return {@link Builder}
         */
        public Builder setBullet(int gapWidth, int color) {
            this.gapWidth = gapWidth;
            bulletColor = color;
            isBullet = true;
            return this;
        }

        /**
         * 设置字体比例 * * @param proportion 比例 * @return {@link Builder}
         */
        public Builder setProportion(float proportion) {
            this.proportion = proportion;
            return this;
        }

        /**
         * 设置字体横向比例 * * @param proportion 比例 * @return {@link Builder}
         */
        public Builder setXProportion(float proportion) {
            this.xProportion = proportion;
            return this;
        }

        /**
         * 设置删除线 * * @return {@link Builder}
         */
        public Builder setStrikethrough() {
            this.isStrikethrough = true;
            return this;
        }

        /**
         * 设置下划线 * * @return {@link Builder}
         */
        public Builder setUnderline() {
            this.isUnderline = true;
            return this;
        }

        /**
         * 设置上标 * * @return {@link Builder}
         */
        public Builder setSuperscript() {
            this.isSuperscript = true;
            return this;
        }

        /**
         * 设置下标 * * @return {@link Builder}
         */
        public Builder setSubscript() {
            this.isSubscript = true;
            return this;
        }

        /**
         * 设置粗体 * * @return {@link Builder}
         */
        public Builder setBold() {
            isBold = true;
            return this;
        }

        /**
         * 设置斜体 * * @return {@link Builder}
         */
        public Builder setItalic() {
            isItalic = true;
            return this;
        }

        /**
         * 设置粗斜体 * * @return {@link Builder}
         */
        public Builder setBoldItalic() {
            isBoldItalic = true;
            return this;
        }

        /**
         * 设置字体 * * @param fontFamily 字体 * <ul> * <li>monospace</li> * <li>serif</li> * <li>sans-serif</li> * </ul> * @return {@link Builder}
         */
        public Builder setFontFamily(@Nullable String fontFamily) {
            this.fontFamily = fontFamily;
            return this;
        }

        /**
         * 设置对齐 * <ul> * <li>{@link Alignment#ALIGN_NORMAL}正常</li> * <li>{@link Alignment#ALIGN_OPPOSITE}相反</li> * <li>{@link Alignment#ALIGN_CENTER}居中</li> * </ul> * * @return {@link Builder}
         */
        public Builder setAlign(@Nullable Alignment align) {
            this.align = align;
            return this;
        }

        /**
         * 设置图片 * * @param bitmap 图片位图 * @return {@link Builder}
         */
        public Builder setBitmap(@NonNull Bitmap bitmap) {
            this.bitmap = bitmap;
            imageIsBitmap = true;
            return this;
        }

        /**
         * 设置图片 * * @param drawable 图片资源 * @return {@link Builder}
         */
        public Builder setDrawable(@NonNull Drawable drawable) {
            this.drawable = drawable;
            imageIsDrawable = true;
            return this;
        }

        /**
         * 设置图片 * * @param uri 图片uri * @return {@link Builder}
         */
        public Builder setUri(@NonNull Uri uri) {
            this.uri = uri;
            imageIsUri = true;
            return this;
        }

        /**
         * 设置图片 * * @param resourceId 图片资源id * @return {@link Builder}
         */
        public Builder setResourceId(@DrawableRes int resourceId) {
            this.resourceId = resourceId;
            imageIsResourceId = true;
            return this;
        }

        /**
         * 设置点击事件 * <p>需添加view.setMovementMethod(LinkMovementMethod.getInstance())</p> * @param clickSpan 点击事件 * @return {@link Builder}
         */
        public Builder setClickSpan(@NonNull ClickableSpan clickSpan) {
            this.clickSpan = clickSpan;
            return this;
        }

        /**
         * 设置超链接 * <p>需添加view.setMovementMethod(LinkMovementMethod.getInstance())</p> * * @param url 超链接 * @return {@link Builder}
         */
        public Builder setUrl(@NonNull String url) {
            this.url = url;
            return this;
        }

        /**
         * 设置模糊 * <p>尚存bug,其他地方存在相同的字体的话,相同字体出现在之前的话那么就不会模糊,出现在之后的话那会一起模糊</p> * <p>推荐还是把所有字体都模糊这样使用</p> * * @param radius 模糊半径(需大于0) * @param style 模糊样式<ul> * <li>{@link Blur#NORMAL}</li> * <li>{@link Blur#SOLID}</li> * <li>{@link Blur#OUTER}</li> * <li>{@link Blur#INNER}</li> * </ul> * @return {@link Builder}
         */
        public Builder setBlur(float radius, Blur style) {
            this.radius = radius;
            this.style = style;
            this.isBlur = true;
            return this;
        }

        /**
         * 追加样式字符串 * * @param text 样式字符串文本 * @return {@link Builder}
         */
        public Builder append(@NonNull CharSequence text) {
            setSpan();
            this.text = text;
            return this;
        }

        /**
         * 创建样式字符串 * * @return 样式字符串
         */
        public SpannableStringBuilder create() {
            setSpan();
            return mBuilder;
        }

        /**
         * 设置样式
         */
        private void setSpan() {
            int start = mBuilder.length();
            mBuilder.append(this.text);
            int end = mBuilder.length();
            if (foregroundColor != defaultValue) {
                mBuilder.setSpan(new ForegroundColorSpan(foregroundColor), start, end, flag);
                foregroundColor = defaultValue;
            }
            if (backgroundColor != defaultValue) {
                mBuilder.setSpan(new BackgroundColorSpan(backgroundColor), start, end, flag);
                backgroundColor = defaultValue;
            }
            if (isLeadingMargin) {
                mBuilder.setSpan(new LeadingMarginSpan.Standard(first, rest), start, end, flag);
                isLeadingMargin = false;
            }
            if (quoteColor != defaultValue) {
                mBuilder.setSpan(new QuoteSpan(quoteColor), start, end, 0);
                quoteColor = defaultValue;
            }
            if (isBullet) {
                mBuilder.setSpan(new BulletSpan(gapWidth, bulletColor), start, end, 0);
                isBullet = false;
            }
            if (proportion != -1) {
                mBuilder.setSpan(new RelativeSizeSpan(proportion), start, end, flag);
                proportion = -1;
            }
            if (xProportion != -1) {
                mBuilder.setSpan(new ScaleXSpan(xProportion), start, end, flag);
                xProportion = -1;
            }
            if (isStrikethrough) {
                mBuilder.setSpan(new StrikethroughSpan(), start, end, flag);
                isStrikethrough = false;
            }
            if (isUnderline) {
                mBuilder.setSpan(new UnderlineSpan(), start, end, flag);
                isUnderline = false;
            }
            if (isSuperscript) {
                mBuilder.setSpan(new SuperscriptSpan(), start, end, flag);
                isSuperscript = false;
            }
            if (isSubscript) {
                mBuilder.setSpan(new SubscriptSpan(), start, end, flag);
                isSubscript = false;
            }
            if (isBold) {
                mBuilder.setSpan(new StyleSpan(Typeface.BOLD), start, end, flag);
                isBold = false;
            }
            if (isItalic) {
                mBuilder.setSpan(new StyleSpan(Typeface.ITALIC), start, end, flag);
                isItalic = false;
            }
            if (isBoldItalic) {
                mBuilder.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), start, end, flag);
                isBoldItalic = false;
            }
            if (fontFamily != null) {
                mBuilder.setSpan(new TypefaceSpan(fontFamily), start, end, flag);
                fontFamily = null;
            }
            if (align != null) {
                mBuilder.setSpan(new AlignmentSpan.Standard(align), start, end, flag);
                align = null;
            }
            if (imageIsBitmap || imageIsDrawable || imageIsUri || imageIsResourceId) {
                if (imageIsBitmap) {
                    mBuilder.setSpan(new ImageSpan(Utils.context, bitmap), start, end, flag);
                    bitmap = null;
                    imageIsBitmap = false;
                } else if (imageIsDrawable) {
                    mBuilder.setSpan(new ImageSpan(drawable), start, end, flag);
                    drawable = null;
                    imageIsDrawable = false;
                } else if (imageIsUri) {
                    mBuilder.setSpan(new ImageSpan(Utils.context, uri), start, end, flag);
                    uri = null;
                    imageIsUri = false;
                } else {
                    mBuilder.setSpan(new ImageSpan(Utils.context, resourceId), start, end, flag);
                    resourceId = 0;
                    imageIsResourceId = false;
                }
            }
            if (clickSpan != null) {
                mBuilder.setSpan(clickSpan, start, end, flag);
                clickSpan = null;
            }
            if (url != null) {
                mBuilder.setSpan(new URLSpan(url), start, end, flag);
                url = null;
            }
            if (isBlur) {
                mBuilder.setSpan(new MaskFilterSpan(new BlurMaskFilter(radius, style)), start, end, flag);
                isBlur = false;
            }
            flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值