在工作遇到上图所示的一个小需求,将“查看全部”的提示连在原文的后面,使用一个textview显示。实现该功能大致步骤:
- 判断处理的文字是否超过最大的限制行数;
- 如果超过行数限制,截取掉超过的部分,并加上“...查看全部”;
- 然后用
SpannableString
将“查看全部”设置为蓝色,并且给整个textview设置点击事件即可。
实现上述步骤的难点在于:
- 如何在
setText()
之前判断处理文字是否超过了最大的限制行数 - 如何获取超过限制行数最后一个文字的下标
解决以上两个问题需要用到一个处理TextView文本排版,拆行处理的工具类SaticLayout
,SaticLayout
构造函数很多,但最终回调用这个构造函数
public StaticLayout(CharSequence source, int bufstart, int bufend,
TextPaint paint, int outerwidth,
Alignment align, TextDirectionHeuristic textDir,
float spacingmult, float spacingadd,
boolean includepad,
TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines)
参数说明:
- CharSequence source 文本内容
- int bufstart, int bufend, 开始位置和结束位置
- TextPaint paint 文本画笔对象
- int outerwidth 布局宽度,超出宽度换行显示
- Alignment align 对齐方式
- TextDirectionHeuristic textDir 文本显示方向
- float spacingmult 行间距倍数,默认是1
- float spacingadd 行距增加值,默认是0
- boolean includepad 文本顶部和底部是否留白
- TextUtils.TruncateAt ellipsize 文本省略方式,有 START、MIDDLE、 END、MARQUEE 四种省略方式
- int ellipsizedWidth 省略宽度
- int maxLines 最大行数
在构造函数中最后会相继调用generate()
和out()
方法,对文本进行拆行处理。如果需要详细了解StaticLayout
的工作原理,可参考StaticLayout 源码分析
然后我们可以通过调用getLineCount()
方法获取到布局该文本的行数,调用getLineStart(int line)
方法可以获取line下一行第一个文字的下标。
下面是具体实现的相关代码:
private int maxLine = 3;
private SpannableString elipseString;//收起的文字
private SpannableString notElipseString;//展开的文字
private void getLastIndexForLimit(TextView tv, int maxLine, String content) {
//获取TextView的画笔对象
TextPaint paint = tv.getPaint();
//每行文本的布局宽度
int width =getResources().getDisplayMetrics().widthPixels - dip2px(this,40);
//实例化StaticLayout 传入相应参数
StaticLayout staticLayout = new StaticLayout(content,paint,width, Layout.Alignment.ALIGN_NORMAL, 1, 0, false);
//判断content是行数是否超过最大限制行数3行
if (staticLayout.getLineCount()>maxLine) {
//定义展开后的文本内容
String string1 = content + " 收起";
notElipseString = new SpannableString(string1);
//给收起两个字设成蓝色
notElipseString.setSpan(new ForegroundColorSpan(Color.parseColor("#0079e2")), string1.length() - 2, string1.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//获取到第三行最后一个文字的下标
int index = staticLayout.getLineStart(maxLine) - 1;
//定义收起后的文本内容
String substring = content.substring(0, index - 4) + "..." + "查看全部";
elipseString = new SpannableString(substring);
//给查看全部设成蓝色
elipseString.setSpan(new ForegroundColorSpan(Color.parseColor("#0079e2")), substring.length() - 4, substring.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//设置收起后的文本内容
tv.setText(elipseString);
tv.setOnClickListener(this);
//将textview设成选中状态 true用来表示文本未展示完全的状态,false表示完全展示状态,用于点击时的判断
tv.setSelected(true);
} else {
//没有超过 直接设置文本
tv.setText(content);
tv.setOnClickListener(null);
}
}
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public static int dip2px(Context mContext, float dpValue) {
final float scale = mContext.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
点击事件相关代码:
@Override
public void onClick(View v) {
if (v.getId() ==R.id.tv) {
if (v.isSelected()) {
//如果是收起的状态
tv.setText(notElipseString);
tv.setSelected(false);
} else {
//如果是展开的状态
tv.setText(elipseString);
tv.setSelected(true);
}
}
}
}
2017.6.19补充---展开收起动画
关于展开和收起动画应该如何添加,首先我们需要在textview外面包一层布局, 然后在自定义一个Animation
,最后在点击事件处开始动画即可。
- 简单布局xml
- 自定义
Animation
类
public class ExpandCollapseAnimation extends Animation{
private final View mTargetView;//动画执行view
private final int mStartHeight;//动画执行的开始高度
private final int mEndHeight;//动画结束后的高度
public ExpandCollapseAnimation(View contentview , int startHeight, int endHeight) {
mTargetView = contentview;
mStartHeight = startHeight;
mEndHeight = endHeight;
setDuration(2000);
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
//applyTransformation()方法就是动画具体的实现,每隔一段时间会调用此方法
//计算出每次应该显示的高度
final int newHeight = (int)((mEndHeight - mStartHeight) * interpolatedTime + mStartHeight);
//改变执行view的高度,实现动画
mTargetView.getLayoutParams().height = newHeight;
mTargetView.requestLayout();
}
}
- 动画的调用执行
private View contentView;
private int expandHeight;//view展开的高度
private int elipseHeight;//view收起的高度
private Animation animation;//动画
private void getLastIndexForLimit(TextView tv, int maxLine, String content) {
......
//以上代码省略
//计算得出contentview最后展开的高度
expandHeight= staticLayout.getHeight() + tv.getPaddingTop() + tv.getPaddingBottom();
}
@Override
public void onClick(View v) {
if (v.getId() ==R.id.tv) {
if (v.isSelected()) {
//收起的状态
//因为现在是收起的状态,所以可以得到contentview开始执行动画的高度
elipseHeight = tv.getHeight();
animation = new ExpandCollapseAnimation(contentView,elipseHeight,expandHeight);
animation.setFillAfter(true);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
//将contentview高度设置为textview的高度,以此让textview是一行一行的展示
contentView.getLayoutParams().height = elipseHeight;
contentView.requestLayout();
tv.setText(notElipseString);
tv.setSelected(false);
}
@Override
public void onAnimationEnd(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
} else {
//展开
animation = new ExpandCollapseAnimation(contentView,expandHeight,elipseHeight);
animation.setFillAfter(true);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
// 动画结束后textview设置展开的状态
tv.setText(elipseString);
tv.setSelected(true);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
contentView.clearAnimation();
// 执行动画
contentView.startAnimation(animation);
}
}
以上就是实现展开收起的相关动画的代码,有不正确的地方,请大家指出。谢谢!