热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

实现可点击展开的TextView

概述Android开发过程中,经常遇到Textview展示不完全的情况。遇到此情况,通常的处理是:方案一Textview添加android:ellipsize属性,让展示不

概述

Android开发过程中,经常遇到 Textview 展示不完全的情况。

遇到此情况,通常的处理是:

  • 方案一
    Textview 添加 android:ellipsize 属性,让展示不完的部分使用省略号代替。
  • 方案二
    Textview 采用走马灯效果,使其滚动展示全部文本内容。

对于方案一,如果想查看被省略后的内容,如何实现?通常情况下是在 TextView 文本后面或下边添加一个可点击的图标,来实现 TextView 的展开与收缩。如下图:

收缩状态

这里写图片描述

展开状态

这里写图片描述

实现原理

对于以上效果,大致的实现思路是:

  1. 对 TextView 添加视图高度监听 (addOnGlobalLayoutListener),监控 TextView 的状态。
  2. 利用 SpannableString 在 TextView 文本的后面添加一个图标。
  3. 实现图标的点击效果(收缩或展开 TextView)。

下面用代码来详细描述实现的过程:

给TextView添加视图高度监听

    /**
* 添加监听
* @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() 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 所有属性的,并且可以点击,图片垂直居中 。

/**
* 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

/**
* 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 中

    // 显示收缩状态的文本,设置点击图标,并添加点击事件
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 中调用

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链接:http://download.csdn.net/detail/l_lhc/9460197

还有一些使用其他方法实现可伸缩的 TextView(使用 setMaxLines 方法),传送门:

  • 如何写一个可以展开的TextView
  • android Textview 使用之一:伸缩效果

参考文章:

  • 用SpannableString和ImageSpan在textview中插入图片
  • 自定义可点击的ImageSpan并在TextView中内置“View“

推荐阅读
  • 本文详细介绍了 `org.apache.tinkerpop.gremlin.structure.VertexProperty` 类中的 `key()` 方法,并提供了多个实际应用的代码示例。通过这些示例,读者可以更好地理解该方法在图数据库操作中的具体用途。 ... [详细]
  • 本文基于Java官方文档进行了适当修改,旨在介绍如何实现一个能够同时处理多个客户端请求的服务端程序。在前文中,我们探讨了单客户端访问的服务端实现,而本篇将深入讲解多客户端环境下的服务端设计与实现。 ... [详细]
  • 尽管在WPF中工作了一段时间,但在菜单控件的样式设置上遇到了一些基础问题,特别是关于如何正确配置前景色和背景色。 ... [详细]
  • Logging all MySQL queries into the Slow Log
    MySQLoptionallylogsslowqueriesintotheSlowQueryLog–orjustSlowLog,asfriendscallit.However,Thereareseveralreasonstologallqueries.Thislistisnotexhaustive:Belowyoucanfindthevariablestochange,astheyshouldbewritteninth ... [详细]
  • 在Android中实现黑客帝国风格的数字雨效果
    本文将详细介绍如何在Android平台上利用自定义View实现类似《黑客帝国》中的数字雨效果。通过实例代码,我们将探讨如何设置文字颜色、大小,以及如何控制数字下落的速度和间隔。 ... [详细]
  • 本文详细介绍了如何在Spring框架中设置事件发布器、定义事件监听器及响应事件的具体步骤。通过实现ApplicationEventPublisherAware接口来创建事件发布器,利用ApplicationEvent类定义自定义事件,并通过ApplicationListener接口来处理这些事件。 ... [详细]
  • 本文介绍了SIP(Session Initiation Protocol,会话发起协议)的基本概念、功能、消息格式及其实现机制。SIP是一种在IP网络上用于建立、管理和终止多媒体通信会话的应用层协议。 ... [详细]
  • Beetl是一款先进的Java模板引擎,以其丰富的功能、直观的语法、卓越的性能和易于维护的特点著称。它不仅适用于高响应需求的大型网站,也适合功能复杂的CMS管理系统,提供了一种全新的模板开发体验。 ... [详细]
  • OBS Studio自动化实践:利用脚本批量生成录制场景
    本文探讨了如何利用OBS Studio进行高效录屏,并通过脚本实现场景的自动生成。适合对自动化办公感兴趣的读者。 ... [详细]
  • 问题场景用Java进行web开发过程当中,当遇到很多很多个字段的实体时,最苦恼的莫过于编辑字段的查看和修改界面,发现2个页面存在很多重复信息,能不能写一遍?有没有轮子用都不如自己造。解决方式笔者根据自 ... [详细]
  • spring boot使用jetty无法启动 ... [详细]
  • 本文探讨了如何通过Service Locator模式来简化和优化在B/S架构中的服务命名访问,特别是对于需要频繁访问的服务,如JNDI和XMLNS。该模式通过缓存机制减少了重复查找的成本,并提供了对多种服务的统一访问接口。 ... [详细]
  • 本文将从基础概念入手,详细探讨SpringMVC框架中DispatcherServlet如何通过HandlerMapping进行请求分发,以及其背后的源码实现细节。 ... [详细]
  • 本文详细介绍了如何利用 Bootstrap Table 实现数据展示与操作,包括数据加载、表格配置及前后端交互等关键步骤。 ... [详细]
  • 如何高效解决Android应用ANR问题?
    本文介绍了ANR(应用程序无响应)的基本概念、常见原因及其解决方案,并提供了实用的工具和技巧帮助开发者快速定位和解决ANR问题,提高应用的用户体验。 ... [详细]
author-avatar
亲亲羊to
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有