Android系统Span的应用


资料来源
资料1: 探索 Android 中的 Span
资料2: Android花样Text设置神器之SpanableString

使用方法

// 设置span效果
 public void setSpan(Object what, int start, int end, int flags) {}
 //取消span效果
 public void removeSpan(Object what) {}	

参数

第一个参数 Object what:这个what就是上面分享的各种Span的类型,大家根据需要自己指定。
第二个第三个参数 int start int end:这两个参数是Span开始跟结束的位置。
第四个参数: int flag 参数用4中类型,分别代表的意思为span开始结束的位置包含或者不包含start 、end,
flags的取值如下:
Spannable. SPAN_INCLUSIVE_EXCLUSIVE:前面包括,后面不包括,即在文本前插入新的文本会应用该样式,而在文本后插入新文本不会应用该样式
Spannable. SPAN_INCLUSIVE_INCLUSIVE:前面包括,后面包括,即在文本前插入新的文本会应用该样式,而在文本后插入新文本也会应用该样式
Spannable. SPAN_EXCLUSIVE_EXCLUSIVE:前面不包括,后面不包括
Spannable. SPAN_EXCLUSIVE_INCLUSIVE:前面不包括,后面包括


作者:谢栋_
来源:CSDN
原文:https://blog.csdn.net/xieluoxixi/article/details/77149255
版权声明:本文为博主原创文章,转载请附上博文链接!

使用步骤

1.先是new出一个SpannedString, SpannableString 或 SpannableStringBuilder对象,并为其初始化一个字符串
2.构建一个自己想要的span效果,影响尺寸/外观/点击等功能
3.setSpan()通过方法来为第一步构建的String类加载我们的span和配置参数
4.为我们的控件设置我们的String类

 textView.setText(spannableString);
 textView.setMovementMethod(LinkMovementMethod.getInstance());  

作者:谢栋_
来源:CSDN
原文:https://blog.csdn.net/xieluoxixi/article/details/77149255
版权声明:本文为博主原创文章,转载请附上博文链接!

应用1:创建一个会折叠和展开的textView

知识储备

原理就是要展示3行以内的内容,超过3行,就截取前三行,组合一个展开全文的按钮
点击按钮,展开全文,末尾增加收起的按钮
点击收起,回到开始前三行显示的状态

前三行String的内容如何获取

主要用到文本布局staticlayout
通过staticlayout.getLineCount()获取布局中的行数来判断是否截取
通过staticlayout.getLineEnd(int lines)获取前lines行字符的个数
然后通过String.subString(0,offset)截取字符串

staticlayout的获取

new StaticLayout()方法已经被标注为过时,在28及P版本以后的系统中不推荐使用,AS推荐使用StaticLayout.Builder.obtain().build()方法,但是Builder方法只支持23版本或者更高,所以就需要做兼容

//如果版本号小于23
		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return new StaticLayout(workingText, getPaint(), width,Layout.Alignment.ALIGN_NORMAL, getLineSpacingMultiplier(), getLineSpacingExtra(), false);
        } else {
            StaticLayout.Builder builder=StaticLayout.Builder.obtain(workingText,0,workingText.length(),getPaint(),width);
            builder.build();
            return builder.build();
        }

配置参数的意义

workingText 需要分行的字符串
0 需要分行的字符串从第几的位置开始
workingText.length() 需要分行的字符串到哪里结束
getPaint() 画笔对象
width layout的宽度,字符串超出宽度时自动换行。
Alignment.ALIGN_NORMAL layout的对齐方式,有ALIGN_CENTER, ALIGN_NORMAL, ALIGN_OPPOSITE 三种。
getLineSpacingMultiplier() 相对行间距,相对字体大小,1.5f表示行间距为1.5倍的字体高度。
getLineSpacingExtra() 在基础行距上添加多少
实际行间距等于这两者的和。
false 参数未知

实现一个属于自己的可折叠TextView

资料来源:自定义ExpandTextView的思路
场景:一大段字符串,当行数超过maxLines时,自动折叠,并添加全文显示的按钮
点击全文显示按钮,显示全文,底部添加折叠按钮
思路

  • 显示前maxLines行
  • 添加全文按钮
  • 响应全文按钮点击事件,处理全文展开
  • 显示全文 ,添加折叠按钮
  • 折叠过程处理

下面是具体的过程

  • 显示前maxLines行
    控件选择继承TextView,由于android的处理,我们看到的TextView都被替换为v7包中的AppCompatTextView,因此直接继承该控件(不然会提示错误)
    在构造器中 获取原始文本origin
    显示前maxLines行的内容,实际上就是对原始文本做截取处理
    如何控制截取到的字符串就是需要显示的字符串呢
    上面介绍的staticlayout就能很方便的获取到指定光标(哪一行,开始位置,结束位置)的index
    然后重新设置截取到的字符串即可
origin = getText().toString();
...
Layout originLayout = createWorkingLayout(origin, width);
int expandTextLength = originLayout.getLineEnd(maxLines);
substring = origin.substring(0, expandTextLength - length);
...
setText(substring);

这里有个问题,originLayout 的初始化需要提供控件的宽度,而View的width必须在onMeasure()后才能获取到

  • 添加全文按钮
    我希望在maxLines的末尾添加一个全文按钮,因此需要重新截取字符串长度
private String openText="...全文";
int length = openExpand.length();
int expandTextLength = originLayout.getLineEnd(maxLines) - length; 
substring = origin.substring(0, expandTextLength - length);
setText(substring);
append(openExpand);
setMovementMethod(LinkMovementMethod.getInstance());

openExpand是一个全文展开的按钮,在下一步处理
要让openExpand生效,必须设置append()和setMovementMethod()方法

  • 响应全文按钮点击事件,处理全文展开

这里就是对上面span的复习
openExpandText()就是用来处理展开全文的方法
openTextColor是控制按钮字体颜色的属性

openExpand = new SpannableString(openText);
ButtonSpan openButtonSpan = new ButtonSpan(getContext(), new OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i(TAG, "onClick: ");
                openExpandText();
            }
        }, openTextColor);
openExpand.setSpan(openButtonSpan, 0, openExpand.length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

ButtonSpan 是自己实现的ClickableSpan类,ClickableSpan类是一个可以响应点击的抽象类
因此必须自己实现

public class ButtonSpan extends ClickableSpan {

    private View.OnClickListener onClickListener;
    private Context context;
    private int colorId;

    public ButtonSpan(Context context, View.OnClickListener onClickListener,  int colorId) {
        this.onClickListener = onClickListener;
        this.context = context;
        this.colorId = colorId;
    }

    @Override
    public void updateDrawState(@NonNull TextPaint ds) {
//        给buttonSpan设置颜色,取消下划线
        ds.setColor(context.getResources().getColor(colorId));
        ds.setUnderlineText(false);
    }

    @Override
    public void onClick( @NonNull View widget) {
        if (onClickListener!=null){
            onClickListener.onClick(widget);
        }
    }
}
  • 显示全文 ,添加折叠按钮
    因为显示全文时,需要在全文下方插入一行,全文按钮放在最后一行的最后
    space 是放在按钮前的空格字符串
    由于不知道最后一行需要添加多少个空格,才能让按钮放到最后,这里用循环添加空格,直到变成两行,就回到上一步,保存space
    然后就可以先设置全文,在插入换行符,在添加space和最后的closeSpan按钮
private void openExpandText() {

        if (space == null) {
            space = new StringBuilder(" ");
            Layout layout = createWorkingLayout(space.toString() + closeExpand, width);
//        偏移span按钮到下一行的最后
            while (layout.getLineCount() < 2) {
                space.append(" ");
                layout = createWorkingLayout(space.toString() + closeExpand, width);
            }
            space.delete(space.length() - 2, space.length() - 1);
        }
//        展开原文
        setText(origin);
        append("\n");
        append(space);
        append(closeExpand);
        setMovementMethod(LinkMovementMethod.getInstance());

    }
  • 折叠过程处理

在折叠按钮的初始化中,我们重新调用closeExpandText()来折叠全文即可

closeExpand = new SpannableString(closeText);
        ButtonSpan closeButtonSpan = new ButtonSpan(getContext(), new OnClickListener() {
            @Override
            public void onClick(View v) {
                closeExpandText();
            }
        }, closeTextColor);
        closeExpand.setSpan(closeButtonSpan, 0, closeExpand.length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

-动画效果
这里采用的是valueAnimation动画
maxHeight是控件展开的高度
minHeight是控件折叠的高度
300是动画时间
setHeight(height);是自己设置的控件高度方法

 ValueAnimator valueAnimator=ValueAnimator.ofInt( maxHeight,minHeight)
                        .setDuration(300);
                valueAnimator.setTarget(this);
                valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        int height=(int)animation.getAnimatedValue();
                        setHeight(height);
                    }
                });
                valueAnimator.start();
...
 @Override
    public void setHeight(int height) {
//        为属性动画增加设置器
        this.getLayoutParams().height=height;
        requestLayout();
    }

这里的难点在于 maxHeight,minHeight的获取
由于控件大小参数是在onMeasure()之后才能获取到,而且每次设置text都会导致控件重新测量
我需要的动画效果是最小高度和最大高度之间变化,最小高度是maxLines的高度,最大高度是原文高度+额外一行的高度
因此,在onDraw()方法中,通过比较,来保留高度变化的最小值和最大值来解决
最大值需要在初始化时,人为添加一个换行符
又由于必须在测量后,才能进行折叠操作(因为文本layout需要width初始化),我把closeExpandText()放在了onDraw()中,又因为onDraw会反复调用,这里我只需要保证初始化时,折叠操作调用一次即可(drawEnd标志位)

private boolean drawEnd;
 ...
append("\n");
...
@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (width == 0) {
            width = getMeasuredWidth();
            maxHeight = getMeasuredHeight();
        }
        if (minHeight == 0) {
            minHeight = getMeasuredHeight();
        }
        minHeight = Math.min(minHeight, getMeasuredHeight());
        maxHeight = Math.max(maxHeight, getMeasuredHeight());
        if (!drawEnd){
         drawEnd=true;
         closeExpandText();
        }
    }

属性的拓展

为了能自由控制按钮文本和颜色,这里增加了自定义属性的支持

//自定义属性文件attrs.xml
<resources>
    <declare-styleable name="ExpandingTextView">
        <attr name="openText" format="string"/>
        <attr name="closeText" format="string"/>
        <attr name="openTextColor" format="integer|reference"/>
        <attr name="closeTextColor" format="integer|reference"/>
    </declare-styleable>
</resources>

//构造器
public ExpandingTextView(Context context, @org.jetbrains.annotations.Nullable @Nullable AttributeSet attrs) {
        super(context, attrs);
        TypedArray ta=context.obtainStyledAttributes(attrs,R.styleable.ExpandingTextView);
        String result=ta.getString(R.styleable.ExpandingTextView_openText);
        if (result!=null){
            openText=result;
        }
        result=ta.getString(R.styleable.ExpandingTextView_closeText);
        if (result!=null){
            closeText=result;
        }
        int color=ta.getResourceId(R.styleable.ExpandingTextView_openTextColor,R.color.colorPrimary);
        openTextColor=color;
        color=ta.getResourceId(R.styleable.ExpandingTextView_closeTextColor,R.color.colorPrimary);
        closeTextColor=color;
        ta.recycle();
        initText();
    }

项目地址
点这里

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值