概述
Android开发过程中,经常遇到 Textview 展示不完全的情况。
遇到此情况,通常的处理是:
方案一 Textview 添加 android:ellipsize 属性,让展示不完的部分使用省略号代替。 方案二 Textview 采用走马灯效果,使其滚动展示全部文本内容。 对于方案一,如果想查看被省略后的内容,如何实现?通常情况下是在 TextView 文本后面或下边添加一个可点击的图标,来实现 TextView 的展开与收缩。如下图:
收缩状态
展开状态
实现原理
对于以上效果,大致的实现思路是:
- 对 TextView 添加视图高度监听 (addOnGlobalLayoutListener),监控 TextView 的状态。
- 利用 SpannableString 在 TextView 文本的后面添加一个图标。
- 实现图标的点击效果(收缩或展开 TextView)。
下面用代码来详细描述实现的过程:
给TextView添加视图高度监听
代码语言:javascript复制 /**
* 添加监听
* @param tv 要实现伸缩效果的 TextView
* @param desc TextView 要展示的文字
*/
public static void toggleEllipsize(final TextView tv,final String desc){
if(desc == null){
return;
}
//去除点击图片后的背景色( SpannableString 在点击时会使背景变色 ,填上这句则可不变色 )
tv.setHighlightColor(Color.TRANSPARENT);
//添加 TextView 的高度监听
tv.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@SuppressWarnings("deprecation")
@SuppressLint("NewApi")
@Override
public void onGlobalLayout() {
int paddingLeft = tv.getPaddingLeft();
int paddingRight = tv.getPaddingRight();
TextPaint paint = tv.getPaint();
float moreText = tv.getTextSize() * 3;
float availableTextWidth = (tv.getWidth() - paddingLeft - paddingRight) * 2 - moreText;
CharSequence ellipsizeStr = TextUtils.ellipsize(desc,paint,availableTextWidth,TextUtils.TruncateAt.END);
// TextView 实际显示的文本长度 < 应该显示文本的长度(收缩状态)
if(ellipsizeStr.length() < desc.length()){
openFun(tv, ellipsizeStr, desc);//显示收缩状态的文本和图标
}
// TextView 实际显示的文本长度 == 应该显示文本的长度(正常状态)
else if(ellipsizeStr.length() == desc.length()){
tv.setText(desc);//正常显示Textview
}
// TextView 实际显示的文本长度 应该显示文本的长度(展开状态)
else{
closeFun(tv, ellipsizeStr, desc);//显示展开状态的文本和图标
}
if(Build.VERSION.SDK_INT =16){
tv.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}else{
tv.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
});
}
使用 SpannableString
在 SpannableString 中,我们可以通过设置 ImageSpan 来给 TextView 添加图标,但是普通的 ImageSpan 是不能响应点击事件的而且也不能设置图片的位置,那么我们要如何实现一个可以响应点击事件并且可以设置图片位置的 ImageSpan 呢?
Step 1:
新建一个 ClickableImageSpan 类,使之具有 ImageSpan 所有属性的,并且可以点击,图片垂直居中 。
代码语言:javascript复制/**
* ClickableImageSpan 继承自 ImageSpan,使其能响应点击事件,并图片垂直居中显示
* @author lee
*
*/
public abstract class ClickableImageSpan extends ImageSpan {
public ClickableImageSpan(Drawable b) {
super(b);
}
/** 图片垂直居中显示 */
@Override
public int getSize(Paint paint, CharSequence text, int start, int end,
Paint.FontMetricsInt fontMetricsInt) {
Drawable drawable = getDrawable();
Rect rect = drawable.getBounds();
if (fontMetricsInt != null) {
Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt();
int fontHeight = fmPaint.bottom - fmPaint.top;
int drHeight = rect.bottom - rect.top;
int top = drHeight / 2 - fontHeight / 4;
int bottom = drHeight / 2 fontHeight / 4;
fontMetricsInt.ascent = -bottom;
fontMetricsInt.top = -bottom;
fontMetricsInt.bottom = top;
fontMetricsInt.descent = top;
}
return rect.right;
}
/** 图片垂直居中显示 */
@Override
public void draw(Canvas canvas, CharSequence text, int start, int end,
float x, int top, int y, int bottom, Paint paint) {
Drawable drawable = getDrawable();
canvas.save();
int transY = 0;
transY = ((bottom - top) - drawable.getBounds().bottom) / 2 top;
canvas.translate(x, transY);
drawable.draw(canvas);
canvas.restore();
}
/** 添加点击事件 */
public abstract void onClick(View view);
}
Step 2:
新建一个 ClickableMovementMethod (修改 LinkMovementMethod 的 onTouchEvent 方法), 使其支持 ClickableImageSpan 。
代码语言:javascript复制/**
* ClickableMovementMethod 继承自 LinkMovementMethod,使其能响应 ClickableImageSpan
* @author lee
*
*/
public class ClickableMovementMethod extends LinkMovementMethod {
private static ClickableMovementMethod sInstance;
public static ClickableMovementMethod getInstance() {
if (sInstance == null) {
sInstance = new ClickableMovementMethod();
}
return sInstance;
}
public boolean onTouchEvent(TextView widget, Spannable buffer,
MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_UP ||
action == MotionEvent.ACTION_DOWN) {
int x = (int) event.getX();
int y = (int) event.getY();
x -= widget.getTotalPaddingLeft();
y -= widget.getTotalPaddingTop();
x = widget.getScrollX();
y = widget.getScrollY();
Layout layout = widget.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
/** 修改位置【1】 START **/
ClickableImageSpan[] imageSpans = buffer.getSpans(off, off, ClickableImageSpan.class);
/****** END ******/
if (link.length != 0) {
if (action == MotionEvent.ACTION_UP) {
link[0].onClick(widget);
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
}
return true;
}
/** 修改位置【2】START **/
else if (imageSpans.length != 0) {
if (action == MotionEvent.ACTION_UP) {
imageSpans[0].onClick(widget);
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(imageSpans[0]),
buffer.getSpanEnd(imageSpans[0]));
}
return true;
}
/****** END ******/
else {
Selection.removeSelection(buffer);
}
}
return false;
}
}
将改好的 SpannableString 设置到 TextView 中
代码语言:javascript复制 // 显示收缩状态的文本,设置点击图标,并添加点击事件
private static void openFun(final TextView tv,final CharSequence ellipsizeStr,final String desc){
CharSequence temp = ellipsizeStr ".";
SpannableStringBuilder ssb = new SpannableStringBuilder(temp);
Drawable dd = tv.getResources().getDrawable(R.drawable.ic_expand);
dd.setBounds(0, 0, dd.getIntrinsicWidth(), dd.getIntrinsicHeight());
ClickableImageSpan is = new ClickableImageSpan(dd) {
@Override
public void onClick(View view) {
closeFun(tv,ellipsizeStr,desc);
}
};
ssb.setSpan(is, temp.length()-1, temp.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
tv.setText(ssb);
tv.setMovementMethod(ClickableMovementMethod.getInstance());
}
// 显示展开状态的文本,设置点击图标,并添加点击事件
private static void closeFun(final TextView tv,final CharSequence ellipsizeStr,final String desc) {
SpannableStringBuilder ssb = new SpannableStringBuilder(desc);
Drawable dd = tv.getResources().getDrawable(R.drawable.ic_normal);
dd.setBounds(0, 0, dd.getIntrinsicWidth(), dd.getIntrinsicHeight());
ClickableImageSpan is = new ClickableImageSpan(dd) {
@Override
public void onClick(View view) {
openFun(tv,ellipsizeStr,desc);
}
};
ssb.setSpan(is, desc.length()-1, desc.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
tv.setText(ssb);
tv.setMovementMethod(ClickableMovementMethod.getInstance());
}
在Activity 中调用
代码语言:javascript复制public class MainActivity extends Activity {
private TextView mTv;
private String str = "我有一只小毛驴,我从来也不骑~ "
"有一天我心血来潮骑它去赶集,我手里拿着小皮鞭,我心里正得意~ "
"不知怎么哗啦啦啦啦,我摔了一身泥~";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTv = (TextView) findViewById(R.id.tv_test);
//调用 toggleEllipsize 方法来设置 mTv
Utils.toggleEllipsize(mTv,str);
}
}
完整Demo链接:ExpandableTextView
还有一些使用其他方法实现可伸缩的 TextView(使用 setMaxLines 方法),传送门:
如何写一个可以展开的TextView android Textview 使用之一:伸缩效果
参考文章:
用SpannableString和ImageSpan在textview中插入图片 自定义可点击的ImageSpan并在TextView中内置“View“
以上就是本文的全部内容,希望对大家的学习有所帮助。