Android进阶——借助强大Span家族增添丰富的特效及格式化字符串

引言

Android中我们用到的最多的UI组件或许就是TextView了,也许很多初学者都会被它的中文翻译所误导,认为TextView只是像他的中文名称一样“文本框”只能用于显示普通字符串,也许还一度以为TextView是最简单的控件,其实不然TextView差不多是其他很多UI组件的基类,比如说我们熟悉的Button、CheckedTextView, DigitalClock, EditText 等等都是 直接或者间接继承了TextView。更重要的是TextView还能显示图片、超链接形式的文本、emoj表情混合字符串形式、图片混合字符串形式等等。

一 TextView及其子类中所支持的格式化字符串四种方式

1 String类的format常规的字符串格式化

1.1 不同数据类型到字符串的转换
    /*String format(String format, Object... args) 转换符就相当于是占位符*/
    public static void main(String[] args) {
        String str=null;
        str=String.format("Hello,%s", "Wolrd");//字符串类型转换符,此时的转换符有点类似占位符,用后面的参数替换掉转换符
        System.out.println(str);//Hello,Wolrd
        str=String.format("Hello,%s:%s.%s", "World","Java","android"); 
        System.out.println(str); //Hello,World:Java.android                                 
        System.out.println("字符类型转换符:%c", 'Z');
        System.out.println("布尔类型转换符%b", 1>2); //false
        System.out.println("整数类型转换符(10进制):%d ", 100);
        System.out.println("整数类型转换符(16进制):%x ", 100);
        System.out.println("整数类型转换符(8进制):%o ", 100);
        System.out.println("浮点类型转换符:%f元", 99*0.15);
        System.out.println("浮点类型转换符(16进制):%a %n", 65*0.85);
        System.out.println("指数类型的转换符表示:%e %n", 50*0.85);
        System.out.println("通用浮点类型(f和e类型中较短的):%g ", 555*0.85);
        System.out.println("百分比类型是%f%% %n", 85);//百分比转换符%%
        System.out.println("散列码转换符:%h", 'A');
    }

1.2 对原字符类型的格式转换

 String str=String.format("格式参数$的使用:%1$d,%2$s", 99,"abc");     //$使用          
        System.out.println(str); //格式参数$的使用:99,abc                 
        System.out.printf("显示正负数的符号:%+d与%d%n", 99,-99);    //+使用, 显示正负数的符号:+99与-99
        System.out.printf("补零操作,第二个数字是补足多少位:最牛的编号是:%04d%n", 99); //补O使用,补零操作,第二个数字是补足多少位:0099
        System.out.printf("Tab键的效果是:% 8d%n", 7);  //空格使用,Tab键的效果是:       7
        System.out.printf("用,分组会计的金额格式%,d%n", 9999999);   //.使用, 整数分组的效果是:9,999,998
        System.out.printf("一本书的价格是:% 50.5f元%n", 49.8);   //空格和小数点后面个数 一本书的价格是:    49.80000

1.3 时间和日期格式化

System.out.printf("c 的使用输出全部日期和时间信息:%tc%n",date);///c 的使用输出全部日期和时间信息:星期四 十月 8 22:23:36 CST 2015       
        System.out.printf("F 的使用年-月-日格式:%tF%n",date);//F 的使用年-月-日格式:2015-10-8  
        System.out.printf("d 的使用月/日/年格式:%tD%n",date);//d的使用月/日/年格式:10/08/15    
        System.out.printf("r 的使用HH:MM:SS PM格式(12时制):%tr%n",date);//r 的使用HH:MM:SS PM格式(12时制):10:23:36 上午
        System.out.printf("HH:MM格式(24时制):%tR%n",date); //R 的使用HH:MM格式(24时制):22:23
        System.out.printf("t 的使用HH:MM:SS格式(24时制):%tT%n",date);//t 的使用HH:MM:SS格式(24时制):22:23:36  

日期处理

public void main(){                                 
        String str=String.format(Locale.US,"使用b得到英文月份简称:%tb",date); 
        System.out.println(str);/*使用b得到英文月份简称:Oct                                                                            
        System.out.printf("使用b得到本地月份简称:%tb%n",date);//使用b得到本地月份简称:十月
        str=String.format(Locale.US,"使用B得到英文月份全称:%tB",date);
        System.out.println(str);//使用B得到英文月份全称:October
        System.out.printf("使用B得到本地月份全称:%tB%n%n",date);//使用B得到本地月份全称:十月
        str=String.format(Locale.US,"使用a得到英文星期的简称:%ta%n",date);
        System.out.println(str);//使用a得到英文星期的简称:Thu
        System.out.printf("使用a得到本地星期的简称:%ta%n",date);//使用a得到本地星期的简称:星期四
        str=String.format(Locale.US,"使用A得到英文星期的全称:%tA%n",date);
        System.out.println(str);//使用A得到英文星期的全称:Thursday
        System.out.printf("使用A得到本地星期的全称:%tA%n",date);//使用A得到本地星期的全称:星期四
        System.out.printf("年的前两位数字(不足两位前面补0):%tC%n",date);//年的前两位数字(不足两位前面补0):20
        System.out.printf("年的后两位数字(不足两位前面补0):%ty%n",date);//年的后两位数字(不足两位前面补0):15
        System.out.printf("一年中的天数(即年的第几天):%tj%n",date);//一年中的天数(即年的第几天):281
        System.out.printf("两位数字的月份(不足两位前面补0):%tm%n",date);//两位数字的月份(不足两位前面补0):10
        System.out.printf("两位数字的日(不足两位前面补0):%td%n",date);//两位数字的日(不足两位前面补0):08
        System.out.printf("月份的日(前面不补0):%te%n",date);//月份的日(前面不补0):8
        }

时间的处理

    public static void main(String[] args) {
        Date date = new Date();
        //H的使用 :2位数字24时制的小时(不足2位前面补0):23
        System.out.printf("2位数字24时制的小时(不足2位前面补0):%tH%n", date);/
        //k的使用 :2位数字24时制的小时(前面不补0):23
        System.out.printf("2位数字24时制的小时(前面不补0):%tk%n", date);
        //I的使用 :2位数字12时制的小时(不足2位前面补0):11
        System.out.printf("2位数字12时制的小时(不足2位前面补0):%tI%n", date);
        //l的使用 :2位数字12时制的小时(前面不补0):11
        System.out.printf("2位数字12时制的小时(前面不补0):%tl%n", date);
        //M的使用 :2位数字的分钟(不足2位前面补0):04
        System.out.printf("2位数字的分钟(不足2位前面补0):%tM%n", date);
        //S的使用 :2位数字的秒(不足2位前面补0):00
        System.out.printf("2位数字的秒(不足2位前面补0):%tS%n", date);
        //L的使用 :3位数字的毫秒(不足3位前面补0):833
        System.out.printf("3位数字的毫秒(不足3位前面补0):%tL%n", date);
        //N的使用 :9位数字的毫秒数(不足9位前面补0):833000000
        System.out.printf("9位数字的毫秒数(不足9位前面补0):%tN%n", date);
        //p的使用 :小写字母的上午或下午标记(英):pm
        String str = String.format(Locale.US, "小写字母的上午或下午标记(英):%tp", date);
        System.out.println(str); 
        System.out.printf("小写字母的上午或下午标记(中):%tp%n", date);//小写字母的上午或下午标记(中):下午
        //z的使用 :相对于GMT的RFC822时区的偏移量:+0800
        System.out.printf("相对于GMT的RFC822时区的偏移量:%tz%n", date);
        //Z的使用 :时区缩写字符串:CST
        System.out.printf("时区缩写字符串:%tZ%n", date);
        //s的使用 :1970-1-1 00:00:00 到现在所经过的秒数:1444316640
        System.out.printf("1970-1-1 00:00:00 到现在所经过的秒数:%ts%n", date);
        //Q的使用 :1970-1-1 00:00:00 到现在所经过的毫秒数:1444316640833
        System.out.printf("1970-1-1 00:00:00 到现在所经过的毫秒数:%tQ%n", date);
    }

2 直接在strings.xml文件里使用Html标签(仅支持i、b、u标签)

3 使用Html.fromHtml格式化

接受的参数为html格式的文本(即正常语法下的html格式的字符串),可以接受绝大部分html标签,也可以处理自定义的标签。
虽然返回的是Spanned对象,但这个Spanned接口继承了CharSequence所以直接传入setText方法中设置。

3.1 处理Html的标签(未包含img标签时) static Spanned fromHtml(String source)
String source = "<u>这是<font color='red'>添加下</font>划线的部分</u>这部分没有处理<font size='20px' color='green'>这部分添加了绿色</color>";
txt.setText(Html.fromHtml(source));

这里写图片描述

3.2 处理Html标签及自定义的标签 static Spanned fromHtml(String source, Html.ImageGetter imageGetter, Html.TagHandler tagHandler) ,其中Html.ImageGetter 、Html.TagHandler 是Html类的内部接口
 public class Html {
     public static interface ImageGetter {
        public Drawable getDrawable(String source);
     }
      public static interface TagHandler {
          public void handleTag(boolean opening, String tag,Editable output, XMLReader xmlReader);
      }
 }
  • Html.ImageGetter的初始化
ImageGetter imgeGetter=new Html.ImageGetter() {
        //当解析到HTML的img标签时自动触发
        @Override
        public Drawable getDrawable(String source) {
            //得到用于替换图片占位符的的真正图片,即真正要显示的图片
        }
    };
  • Html.TagHandler 的初始化,按照Android官方文档说明,说是处理自定义非Html的标签,其实是遍历所有的标签,而且解析html的工作Android也不是自己写代码完成的,所以解析标签的时候即使没有html、body标签也会自动先添加构造成完整的html格式的,首先会把标签的头都解析打印出来,再把尾逆序解析。
TagHandler tagHandler=new Html.TagHandler() {
        @Override
        public void handleTag(boolean opening, String tag, Editable output,XMLReader xmlReader) {
            //每一个标签都会遍历到
        }
    };
  • 包含Html的img标签的应用
String source="<b>这格式化后的字符串</b>插入的图像<img src='ic_launcher.png'/><font color='green'> ImageGetter就是用于解析img标签的</font>";
txt.setText(Html.fromHtml(source,imgeGetter,null)); 

这里写图片描述

3.3 Html.formHtml用法小结

核心思想就是通过fromHtml方法得到Spanned对象,如果需要解析img标签,则需要传递imgeGetter对象;反之则传递null;如果还包含自定义的标签,则需要传递tagHandler对象,反之传递null;最后再把Spanned对象赋值到SetText方法。这样的话实现表情文字混合效果就很easy了,但是不好实现点击事件。

4 使用SpannableString和SpannableStringBuilder格式化

SpannableStringSpannableStringBuilder的基类也是
CharSequence,也能直接把这两个对象赋值到SetText方法。构造了这两个对象还可以通过SetSpan方法来为存储的String添加各种样式和事件(不同的样式的实现在于设置不同的XxxSpan子类就实现了不同的效果)。还有一点值得注意的是这两者的区别和String与StringBuilder是一样的,前者是静态,后者可以动态添加。

4.1 XxxSpan体系,Android提供了很多种Span对象,每一种Span都对应一种特效

BackgroundColorSpan, ClickableSpan, ForegroundColorSpan, MaskFilterSpan, MetricAffectingSpan, RasterizerSpan, StrikethroughSpan, UnderlineSpanAbsoluteSizeSpan

4.2 使用XxxSpan的步骤
  • 利用SpannableString或SpannableStringBuilder构造字符串
  • 根据需求的特效构造对应的XxxSpan对象
  • 利用SetSpan()对圈定范围的String使用构造对应的Span
4.3 setSpan方法

给SpannableString或SpannableStringBuilder特定范围的字符串应用Span样式,如在原来String上加下划线、加背景色、改变字体颜色、用图片把指定的文字给替换掉等,可以同时设置多个(比如同时加上下划线和前景色等),Falgs参数标识了当在所标记范围前和标记范围后紧贴着插入新字符时的动作,即是否对新插入的字符应用同样的样式
void setSpan (XxxSpan what, int start, int end, int flags)

  • object what :对应的各种Span,后面会提到;
  • int start:开始应用指定Span的位置,索引从0开始 int
  • end:结束应用指定Span的位置,特效并不包括这个位置
  • int flags:(都是在Spannable里定义的常量,下面四个比较常用)
    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE:前后都不包括,即在指定范围的前面和后面插入新字符都不会应用新样式
    Spannable.SPAN_EXCLUSIVE_INCLUSIVE :前面不包括,后面包括。即仅在范围字符的后面插入新字符时会应用新样式
    Spannable.SPAN_INCLUSIVE_EXCLUSIVE :前面包括,后面不包括。
    Spannable.SPAN_INCLUSIVE_INCLUSIVE :前后都包括。
4.4 SpannableString和SpannableStringBuilder应用
  • ForegroundColorSpan设置字体绿色和BackgroundColorSpan字体背景红色
    这里写图片描述
 ///先构造SpannableString  
SpannableString spanString = new SpannableString("这是使用ForegroundColorSpan实现改变字体颜色");    
//再构造一个改变字体颜色的Span  
ForegroundColorSpan span = new ForegroundColorSpan(Color.GREEN);
BackgroundColorSpan spanbcg = new BackgroundColorSpan(Color.RED);    
//将这个Span应用于指定范围的字体  
spanString.setSpan(span, 4, 9, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
spanString.setSpan(spanbcg, 5, 12, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
txt.setText(spanString);
  • 插入图片
    这里写图片描述
SpannableString spanString = new SpannableString("这是使用ImageSpan实现改变");  
Drawable d = getResources().getDrawable(R.drawable.ic_launcher);  
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());  
ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BASELINE);  
spanString.setSpan(span, 6, 8, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);  
txt.setText(spanString);
  • ClickableSpan实现点击超链接,实现ClickableSpan里的onClick方法
///要想成功实现点击效果必须添加以下四句
//原因:当调用textview的setMovementMethod 或者 setKeyListener, 
//TextView 自动修改它的属性:setFocusable(true);
//这也就是说你手动设置的focusable被覆盖掉了,也就需要我们覆写//hasFocusable方法,使其始终返回false。
//2、覆写hasFocusable之后listview的OnItemClick已经可以响应,以有限的大脑容量我以为自定义的LinkMovementMethod可以不要了,可惜注释掉之后发现ClickableSpan不工作了。。。
txt.setMovementMethod(LinkMovementMethod.getInstance());  
txt.setFocusable(false);  
txt.setClickable(false);  
txt.setLongClickable(false);
        SpannableString spanString = new SpannableString("这是使用怒戳ImageSpan这里");  
        ClickableSpan clickSpan=new ClickableSpan(){

            @Override
            public void onClick(View widget) {
                Log.d("TagClick","cickSPan");
                Toast.makeText(getBaseContext(), "这就是用ClickSpan实现的点击!", Toast.LENGTH_LONG).show();
            }
            @Override
            public void updateDrawState(TextPaint ds) {
                ds.setColor(ds.linkColor);
                ds.setUnderlineText(true);
            }
        };
        spanString.setSpan(clickSpan, 6, 16, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        txt.setText(spanString);

        txt.setLinkTextColor(Color.RED);  
        //必须添加这一段
        txt.setMovementMethod(LinkMovementMethod.getInstance());  
        txt.setFocusable(false);  
        txt.setClickable(false);  
        txt.setLongClickable(false);  

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CrazyMo_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值