Android SpannableString 图文混排使用并封装(含完整Demo)

2,013 阅读4分钟

在Android经常看到这种效果:

image.png

你还在使用2个TextView来写吗?

那这种情况怎么使用TextView呢?

image.png

那就太out了,今天带大家重新认识一下TextView,并封装一下子!

先来看看今天的效果:

image.png

来看看gif图效果:

1487E9C946EE2CA1D9A1194020C6E311.gif

Spannable介绍

Spannable有2个实现类,分别是SpannableString和SpannableStringBuilder,关系如下图

image.png

图片来源

SpannableString 和 SpannableStringBuilder 的区别:

image.png

他们的区别类似于String和StringBuilder

SpannableString 和 SpannableStringBuilder使用类似,本篇使用SpannableString

SpannableString的方法:

SpannableString参数返回值说明
charAt(int)char返回指定索引处的char值。
getSpanEnd(Object )int返回结尾数据,如果无数据则返回-1。
getSpanFlags(Object )int返回在使用Spannable#setSpan附加指定标记对象时指定的标志,如果尚未附加指定对象,则返回0。
getSpanStart(Object )int返回文本开头,如果无数据,则返回-1。
removeSpan(Object )void删除指定位置
setSpan(Object,int,int,int)void将指定的标记对象附加到文本的开始…结束区域,或者如果对象已附加到其他位置,则将其移动到该区域。

这里主要使用的是setSpan()方法 参考官方文档

说了这么多理论,现在开始来实践一下吧~

SpannableString简单实用

文字和加载本地图片:

先来看看代码:

image.png

这里主要采用的是ImageSpan(),然后通过SpannableString.setSpan()方法设置给TextView

这里在解释一下setSpan()的参数

  • 参数一:Object
  • 参数二:SpannableString的占位开始位置(这里占位的值<image>的位置)
  • 参数三:SpannableString的占位结束位置
  • 参数四:图片的位置 (我没看出有什么效果)

更多样式:

image.png

更多样式参考其他博主博文

文字和加载网络图片:

加载网络图片我采用的是Glide

思路:首先加载一个本地图片,然后等Glide加载好图片之后,通过Glide获取到Drawable然后通过removeSpan()方法将本地图片删除掉,最后通过setSpan()方法将Glide获取到的Drawable设置给TextView即可

来看看代码:

private fun buildImage(url: String, width: Int, height: Int) {
        val ss = SpannableStringBuilder(url)
        val drawable = getDrawableData()

        //设置Drawable图片宽高
        drawable.setBounds(5, 5, width, height)
        //占位符
        val placeholderSpan = ImageSpan(drawable)
        ss.setSpan(placeholderSpan, 0, url.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)

        //添加数据(先占好位置,然后等图片加载出来在删除掉,最后在设置网络图片)
        textView.append(ss)

        //获取到Text的Spannable
        val spannable = textView.text as Spannable

        //Glide加载网络图片,获取到Drawable
        Glide.with(this)
            .asDrawable()
            .load(url)
            .into(object : SimpleTarget<Drawable>() {
                override fun onResourceReady(
                    resource: Drawable,
                    transition: Transition<in Drawable>?,
                ) {
                    //获取到开始位置
                    val start = spannable.getSpanStart(placeholderSpan)
                    //获取到结束位置
                    val end = spannable.getSpanEnd(placeholderSpan)

                    //当前图片宽高不为-1
                    if (start != -1 && end != -1) {
                        //设置图片宽高
                        resource.setBounds(0, 0, width, height)

                        //删除占位符,然后重新设置网络图片
                        spannable.removeSpan(placeholderSpan)
                        
                        //设置网络图片
                        spannable.setSpan(ImageSpan(resource),
                            start,
                            end,
                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
                        )
                    }
                }
            })
    }

注释很清晰,不懂得记得在评论区留言哦!

通过写这么多代码,发现很多代码都是重复的,而且属性需要一个一个设置,特别麻烦,用起来也不方便,那么接下来咋们就给他封装一下.

通过观察发现,建造者模式非常适合,那就采用建造者模式吧~

建造者模式封装

拿最常用的来举例,剩下的自己下载Demo看哦~

定义建造者模式规范:

public interface RichBuilder {
    RichBuilder addText(String msg);//添加文字
    
    RichBuilder setOnClick(RichTextView.onItemClick onClick);//点击事件

    RichBuilder build();//确定
}

实现RichBuilder规范:

public class RichTextView extends androidx.appcompat.widget.AppCompatTextView
        implements RichBuilder {
        
        //必要构造器
      public RichTextView(@NonNull Context context) {
        super(context, null);
      }

    public RichTextView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs, 0);
    }

    public RichTextView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }  
    
    
    private final ArrayList<Object> objList = new ArrayList<>();

    String text = "text";
      
    @Override
    public RichBuilder addText(String msg) {
        text = msg;
        return this;
    }
    
    //点击事件
    @Override
    public RichBuilder setOnClick(onItemClick onClick) {
        objList.add(new ClickableSpan() {
            @Override
            public void onClick(View widget) {
                //接口回调
                onClick.click();
            }
        });

        //设置为可点击状态
        setMovementMethod(LinkMovementMethod.getInstance());
        return this;
    }
    
    @Override
    public RichBuilder build() {
        SpannableString ss = new SpannableString(text);
        for (Object o : objList) {
            ss.setSpan(o, 0, text.length(),
                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
        append(ss);
        
        //一定要清空objList哦
        objList.clear();
        return this;
    }
       
    //点击事件回调
    public interface onItemClick {
        void click();
    }   
}

使用:

            richText
                .addText("100")
                .setOnClick {
                    toast("点击了100哦")
                }
                .build()
                .addText(" m\n\n")
                .build()
                .addText("我是构建者模式的数据")
                .setOnClick {
                    toast("我是构建者模式的数据")
                }
                .build()

效果图:

E4349844F635040FA3E45288AF05364D.gif

数学公式

在来看看数学公式是怎么使用的吧:

        tvFormula
            .addText("(X")
            .build()
            .addText("1")
            .setSubscript()     //设置下标
            .setSubscriptSize(30)   //角标大小
            .build()
            .addText("+ ")
            .build()
            .addText("2")
            .setSuperscript()   //设置上标
            .setSubscriptSize(30)
            .build()
            .addText(")")
            .build()

最终结果为:

image.png

剩下的下载Demo看吧~

完整代码

猜你喜欢:

原创不易,您的点赞就是对我最大的支持!