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();
}
项目地址
点这里