Android实现可点击展开的TextView

作者:码卡农 时间:2022-04-02 04:58:01 

概述

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

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

方案一
Textview 添加 android:ellipsize 属性,让展示不完的部分使用省略号代替。
方案二
Textview 采用走马灯效果,使其滚动展示全部文本内容。
对于方案一,如果想查看被省略后的内容,如何实现?通常情况下是在 TextView 文本后面或下边添加一个可点击的图标,来实现 TextView 的展开与收缩。如下图:

收缩状态

Android实现可点击展开的TextView

展开状态

Android实现可点击展开的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

标签:Android,点击展开,TextView
0
投稿

猜你喜欢

  • C#新手常犯的错误汇总

    2021-10-29 05:05:53
  • android 添加按(power键)电源键结束通话(挂断电话)

    2023-08-14 20:38:24
  • Java基础之容器Vector详解

    2023-11-25 13:10:07
  • Java面试题之HashMap 的 hash 方法原理是什么

    2022-09-11 20:20:54
  • 对指定的网页进行截图的效果 C#版

    2022-07-04 03:14:18
  • C# TabControl控件中TabPage选项卡切换时的触发事件问题

    2022-10-30 08:11:13
  • C# File类中的文件读写方法详解

    2023-04-14 09:52:08
  • 详解Mybatis的二级缓存配置

    2023-03-20 10:48:37
  • Android实现在一个activity中添加多个listview的方法

    2023-10-13 14:41:22
  • java返回json请求中文变成问号的问题及解决

    2023-11-01 02:56:34
  • Android编程实现应用自动更新、下载、安装的方法

    2021-11-15 11:21:39
  • vscode编写latex的方法

    2022-05-21 13:30:52
  • C#中Equals和GetHashCode使用及区别

    2023-12-10 14:47:27
  • C#使用HtmlAgilityPack抓取糗事百科内容实例

    2022-01-16 10:45:16
  • 每天学Java!一分钟了解JRE与JDK

    2021-06-13 11:52:41
  • Android对话框使用方法详解

    2023-11-09 03:37:51
  • Java 实现常见的非对称加密算法

    2023-11-27 18:51:03
  • Android SharedPreferences实现记住密码和自动登录界面

    2023-06-15 20:07:00
  • C#实现读取注册表监控当前操作系统已安装软件变化的方法

    2022-01-04 08:07:04
  • java理论基础Stream管道流状态与并行操作

    2021-08-31 14:24:07
  • asp之家 软件编程 m.aspxhome.com