Android实现可点击展开的TextView
作者:码卡农 发布时间:2022-04-02 04:58:01
概述
Android开发过程中,经常遇到 Textview 展示不完全的情况。
遇到此情况,通常的处理是:
方案一
Textview 添加 android:ellipsize 属性,让展示不完的部分使用省略号代替。
方案二
Textview 采用走马灯效果,使其滚动展示全部文本内容。
对于方案一,如果想查看被省略后的内容,如何实现?通常情况下是在 TextView 文本后面或下边添加一个可点击的图标,来实现 TextView 的展开与收缩。如下图:
收缩状态
展开状态
实现原理
对于以上效果,大致的实现思路是:
对 TextView 添加视图高度监听 (addOnGlobalLayoutListener),监控 TextView 的状态。
利用 SpannableString 在 TextView 文本的后面添加一个图标。
实现图标的点击效果(收缩或展开 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() < 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 所有属性的,并且可以点击,图片垂直居中 。
/**
* 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链接:ExpandableTextView
还有一些使用其他方法实现可伸缩的 TextView(使用 setMaxLines 方法),传送门:
如何写一个可以展开的TextView
android Textview 使用之一:伸缩效果
参考文章:
用SpannableString和ImageSpan在textview中插入图片
自定义可点击的ImageSpan并在TextView中内置“View“
来源:http://blog.csdn.net/l_lhc/article/details/50879287


猜你喜欢
- C#接口的学习,在编程中,我们经常会用到接口,那什么是接口呢?接口描述的是可属于任何类或结构的一组相关功能,所以实现接口的类或结构必须实现接
- 1:在很多APP里点击返回键,都可以看到返回键由亮变为暗2:实现方法也是很简单的(1)新建一个页面<RelativeLayout &n
- 前言上文讲的MyBatis部署运行且根据官网运行了一个demo:一步到位部署运行MyBatis3源码<保姆级>jdbc再贴一个J
- Android 版本更替,新的版本带来新的特性,新的方法。新的方法带来许多便利,但无法在低版本系统上运行,如果兼容性处理不恰当,APP在低版
- 题目描述已知鸡的数量为n只,兔的数量为m只,鸡兔的总头数为H个鸡兔的总脚数为Y只for循环语法for(表达式1;表达式2;表达式3 ){&n
- 1、首先创建一个测试实体类Person,并携带如上注解,其注解的作用描述在messagepackage com.clickpaas.pojo
- 引言在进行Winform程序开发需要进行大量的数据的读写操作的时候,往往会需要一定的时间,然在这个时间段里面,界面ui得不到更新,导致在用户
- 今天遇到一个需求,需要处理通过接口传过来的一个参数,参数内容为一个拼接好的Url地址,且该地址还会携带了一些额外的参数,包括但不限于数字,字
- 本文实例为大家分享了Java实现二分查找的变种,供大家参考,具体内容如下普通二分查找:先回顾一下普通的二分查找注意:二分查找有这样一个问题:
- 一Map特性:1 Map提供一种映射关系,其中的元素是以键值对(key-value)的形式存储的,能够实现根据key快速查找value;2
- Springboot 实体类生成数据库表JPA:springboot -jpa:数据库的一系列的定义数据持久化的标准的体系学习的目的是:利用
- 什么是构建生命周期构建生命周期是一组阶段的序列(sequence of phases),这些构建生命周期中的每一个由构建阶段的不同列表定义,
- 前言本文主要给大家介绍了关于Java读取二进制文件的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。读Hex写CS
- 本文实例讲述了C#实现在Form里面内嵌dos窗体的方法。分享给大家供大家参考。具体如下:using System;using System
- 这是调用相机 public static File getImageFromCamer(Context context, File
- android开发中通过View的getDrawingCache方法可以达到截屏的目的,只是缺少状态栏!原始界面截屏得到的图片代码实现1.
- 在Springboot+Mybatis-plus不使用SQL语句进行多表添加操作我所遇到的问题准备工作在测试环境下模拟思维分解一下:创建出一
- 前言大家看标题,可能会有点儿懵,什么是ViewPagers,因为在很久之前,我们使用的都是ViewPager,但是现在更多的是在用ViewP
- 一、本地仓库初始化与远程仓库推送操作Idea 基本环境配置Github 配置Git 执行文件目录指定创建工程git02创建本地仓库并提交项目
- 大家好,我是狸小华,萌汉子一枚。今天给大家带来的是仿微信/支付宝的密码输入框。这个效果也出来有一段时间了,所以搜索一下还是有不少的网友实现,