最近在开发中需要实现一个需求,某项东西的介绍文字超过三行默认要收起,点击末端下拉箭头可以展开查看更多,大致效果图如下:
咋一看需求还挺简单,GitHub上也有现成的ExpandableTextView控件可用,实现效果:
好像看起来差不多,不过UI验收的时候就过不去了,要求下拉箭头需要跟在文字后面,且要有省略号表示更多内容,那现成的控件是不能使用了,只好自己想办法。因为下拉箭头要跟随文字,所以我们也不能使用drawableRight方法,想了想,那就用SpannableString加ImageSpan来实现试试,大致思路:先判断TextView在页面上可以展示几行,超过三行,我们就取前三行文字内容,末端截取、拼接“... ∨”上去;不超过三行,那就不做改动,直接展示。现在思路有了,那就可以开动写代码了。
第一步,获取TextView内容在页面上可以展示几行。通过TextView的getLayout()方法得到TextView的布局Layout对象,调用getLineCount()方法就可以知道TextView内容在页面上展示几行了,代码:
// 得到TextView的布局对象
Layout layout = textView.getLayout();
// 得到TextView显示有多少行
int lines = layout.getLineCount();
第二步,判断行数是否超过UI要求展示的行数,超过(我们这里定3行),我们取前三行的文字内容,这里又要用到第一步中的Layout对象,我们调用getLineStart(int)和getLineEnd(int)来获取前三行文字内容,大致代码如下:
StringBuffer threeLinesContent = new StringBuffer();
for (int i = 0; i < 3; i ++) {
threeLinesContent.append(textContent.substring(layout.getLineStart(i), layout.getLineEnd(i));
}
第三步,拼接前三行末尾的“... ∨”展示,代码:
threeLinesContent = threeLinesContent .substring(0, threeLinesContent .length() - 3) + "...\u3000 ";
setContentText(threeLinesContent , false);
private void setContentText(String textContent, boolean expand) {
SpannableString ss = new SpannableString(textContent);
Drawable drawable;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
drawable = getResources().getDrawable(expand ? R.drawable.common_up_arrow : R.drawable.common_down_arrow, null);
} else {
drawable = getResources().getDrawable(expand ? R.drawable.common_up_arrow : R.drawable.common_down_arrow);
}
drawable.setBounds(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight());
ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BASELINE);
ss.setSpan(imageSpan, textContent.length() - 1, textContent.length(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
textView.setText(ss);
}
第四步,给TextView添加点击事件,处理展开收缩功能
textView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (isExpand) {
isExpand = false;
setContentText(threeLineContent, false);
} else {
isExpand = true;
setContentText(textContent, true);
}
}
});
经过上面四步,基本上已经实现了我们需要的效果了,为什么说是基本上呢,因为,如果第一步的代码写在Activity的onCreate()方法里的话,会报空指针,TextView获取Layout对象需要在页面渲染完成才能获取到,所以我们改造下上面的代码,完整代码如下:
private TextView introduceTV;
// 介绍是否展开
private boolean isExpand;
// 介绍动作runnable
private Runnable resumeRunnable;
protected void onCreate(Bundle savedInstanceState) {
......
introduceTV.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// 擅长
introduceTV.setText(describtionStr);
resumeRunnable = new LineContent(introduceTV, describtionStr);
introduceTV.post(resumeRunnable);
introduceTV.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
private class LineContent implements Runnable {
private TextView mTarget;
private String mContent;
public LineContent(TextView mTarget, String mContent) {
this.mTarget = mTarget;
this.mContent = mContent;
}
public void run() {
if (null != mTarget && !TextUtils.isEmpty(mContent)) {
GetLineContent();
}
}
private void GetLineContent() {
// 得到TextView的布局
Layout layout = mTarget.getLayout();
// 得到TextView显示有多少行
int lines = mTarget.getLineCount();
try {
if (isExpand) {
setGoodAtText(mContent + "\u3000 ", isExpand);
} else {
if (lines > 3) {
StringBuffer threeLinesContent = new StringBuffer();
for (int i = 0; i < 3; i++) {
threeLinesContent.append(layout.getLineStart(i), layout.getLineEnd(i));
threeLinesContent = threeLinesContent.substring(0, threeLinesContent.length() - 3) + "...\u3000 ";
setGoodAtText(threeLinesContent, false);
mTarget.setOnClickListener(new OnClickEffectiveListener() {
@Override
public void onClickEffective(View view) {
if (isExpand) {
isExpand = false;
} else {
isExpand = true;
}
mTarget.post(resumeRunnable);
}
});
}
}
} catch (Exception e) {
// Do nothing
}
}
private void setGoodAtText(String textContent, boolean expand) {
SpannableString ss = new SpannableString(textContent);
Drawable drawable;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
drawable = getResources().getDrawable(expand ? R.drawable.common_up_arrow : R.drawable.common_down_arrow, null);
} else {
drawable = getResources().getDrawable(expand ? R.drawable.common_up_arrow : R.drawable.common_down_arrow);
}
drawable.setBounds(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight());
ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BASELINE);
ss.setSpan(imageSpan, textContent.length() - 1, textContent.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
mTarget.setText(ss);
}
}
@Override
protected void onDestroy() {
if (null != resumeRunnable) {
resumeRunnable = null;
}
super.onDestroy();
}
以上就是全部的实现过程,平时比较懒于记载东西,偶尔记录一次就花点时间认真写下,便于后续学习共享。不足之处请指正。