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

Android实现可点击展开的TextView

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

概述

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()  应该显示文本的长度(展开状态)
        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“

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程笔记。


推荐阅读
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • Python瓦片图下载、合并、绘图、标记的代码示例
    本文提供了Python瓦片图下载、合并、绘图、标记的代码示例,包括下载代码、多线程下载、图像处理等功能。通过参考geoserver,使用PIL、cv2、numpy、gdal、osr等库实现了瓦片图的下载、合并、绘图和标记功能。代码示例详细介绍了各个功能的实现方法,供读者参考使用。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • Week04面向对象设计与继承学习总结及作业要求
    本文总结了Week04面向对象设计与继承的重要知识点,包括对象、类、封装性、静态属性、静态方法、重载、继承和多态等。同时,还介绍了私有构造函数在类外部无法被调用、static不能访问非静态属性以及该类实例可以共享类里的static属性等内容。此外,还提到了作业要求,包括讲述一个在网上商城购物或在班级博客进行学习的故事,并使用Markdown的加粗标记和语句块标记标注关键名词和动词。最后,还提到了参考资料中关于UML类图如何绘制的范例。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了在rhel5.5操作系统下搭建网关+LAMP+postfix+dhcp的步骤和配置方法。通过配置dhcp自动分配ip、实现外网访问公司网站、内网收发邮件、内网上网以及SNAT转换等功能。详细介绍了安装dhcp和配置相关文件的步骤,并提供了相关的命令和配置示例。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
author-avatar
詹旭萌鸡蛋_544
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有