Android TextView实现查看全部和收起功能

在工作遇到上图所示的一个小需求,将“查看全部”的提示连在原文的后面,使用一个textview显示。实现该功能大致步骤:

  1. 判断处理的文字是否超过最大的限制行数;
  2. 如果超过行数限制,截取掉超过的部分,并加上“...查看全部”;
  3. 然后用SpannableString将“查看全部”设置为蓝色,并且给整个textview设置点击事件即可。

实现上述步骤的难点在于:

  1. 如何在setText()之前判断处理文字是否超过了最大的限制行数
  2. 如何获取超过限制行数最后一个文字的下标

解决以上两个问题需要用到一个处理TextView文本排版,拆行处理的工具类SaticLayoutSaticLayout构造函数很多,但最终回调用这个构造函数

    public StaticLayout(CharSequence source, int bufstart, int bufend,
                        TextPaint paint, int outerwidth,
                        Alignment align, TextDirectionHeuristic textDir,
                        float spacingmult, float spacingadd,
                        boolean includepad,
                        TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines)

参数说明:

  • CharSequence source 文本内容
  • int bufstart, int bufend, 开始位置和结束位置
  • TextPaint paint 文本画笔对象
  • int outerwidth 布局宽度,超出宽度换行显示
  • Alignment align 对齐方式
  • TextDirectionHeuristic textDir 文本显示方向
  • float spacingmult 行间距倍数,默认是1
  • float spacingadd 行距增加值,默认是0
  • boolean includepad 文本顶部和底部是否留白
  • TextUtils.TruncateAt ellipsize 文本省略方式,有 START、MIDDLE、 END、MARQUEE 四种省略方式
  • int ellipsizedWidth 省略宽度
  • int maxLines 最大行数

在构造函数中最后会相继调用generate()out()方法,对文本进行拆行处理。如果需要详细了解StaticLayout的工作原理,可参考StaticLayout 源码分析

然后我们可以通过调用getLineCount()方法获取到布局该文本的行数,调用getLineStart(int line)方法可以获取line下一行第一个文字的下标。

下面是具体实现的相关代码:

    private int maxLine = 3;
    private SpannableString elipseString;//收起的文字
    private SpannableString notElipseString;//展开的文字
    private void getLastIndexForLimit(TextView tv, int maxLine, String content) {
        //获取TextView的画笔对象
        TextPaint paint = tv.getPaint();
        //每行文本的布局宽度
        int width =getResources().getDisplayMetrics().widthPixels - dip2px(this,40);
        //实例化StaticLayout 传入相应参数
        StaticLayout staticLayout = new StaticLayout(content,paint,width, Layout.Alignment.ALIGN_NORMAL, 1, 0, false);
        //判断content是行数是否超过最大限制行数3行
        if (staticLayout.getLineCount()>maxLine) {
            //定义展开后的文本内容
            String string1 = content + "    收起";
            notElipseString = new SpannableString(string1);
            //给收起两个字设成蓝色
            notElipseString.setSpan(new ForegroundColorSpan(Color.parseColor("#0079e2")), string1.length() - 2, string1.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

            //获取到第三行最后一个文字的下标
            int index = staticLayout.getLineStart(maxLine) - 1;
            //定义收起后的文本内容
            String substring = content.substring(0, index - 4) + "..." + "查看全部";
            elipseString = new SpannableString(substring);
            //给查看全部设成蓝色
            elipseString.setSpan(new ForegroundColorSpan(Color.parseColor("#0079e2")), substring.length() - 4, substring.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            //设置收起后的文本内容
            tv.setText(elipseString);
            tv.setOnClickListener(this);
            //将textview设成选中状态 true用来表示文本未展示完全的状态,false表示完全展示状态,用于点击时的判断
            tv.setSelected(true);
        } else {
            //没有超过 直接设置文本
            tv.setText(content);
            tv.setOnClickListener(null);
        }
    }

   /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    public static int dip2px(Context mContext, float dpValue) {
        final float scale = mContext.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

点击事件相关代码:

  @Override
    public void onClick(View v) {
        if (v.getId() ==R.id.tv) {
            if (v.isSelected()) {
                //如果是收起的状态
                tv.setText(notElipseString);
                tv.setSelected(false);
            } else {
                 //如果是展开的状态
                tv.setText(elipseString);
                tv.setSelected(true);
            }
        }
    }
}
2017.6.19补充---展开收起动画

关于展开和收起动画应该如何添加,首先我们需要在textview外面包一层布局, 然后在自定义一个Animation,最后在点击事件处开始动画即可。

  • 简单布局xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <RelativeLayout
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:id="@+id/tv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text=""
            android:textSize="16sp"
            android:padding="20dp"
            />
    </RelativeLayout>

</LinearLayout>
  • 自定义Animation
public class ExpandCollapseAnimation extends Animation{
    private final View mTargetView;//动画执行view
    private final int mStartHeight;//动画执行的开始高度
    private final int mEndHeight;//动画结束后的高度

    public ExpandCollapseAnimation(View contentview , int startHeight, int endHeight) {
        mTargetView = contentview;
        mStartHeight = startHeight;
        mEndHeight = endHeight;
        setDuration(2000);
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        //applyTransformation()方法就是动画具体的实现,每隔一段时间会调用此方法
        //计算出每次应该显示的高度
        final int newHeight = (int)((mEndHeight - mStartHeight) * interpolatedTime + mStartHeight);
        //改变执行view的高度,实现动画
        mTargetView.getLayoutParams().height = newHeight;
        mTargetView.requestLayout();
    }
}
  • 动画的调用执行
  private View contentView;
  private int expandHeight;//view展开的高度
  private int elipseHeight;//view收起的高度
  private Animation animation;//动画
  private void getLastIndexForLimit(TextView tv, int maxLine, String content) {
     ......
    //以上代码省略
    //计算得出contentview最后展开的高度
     expandHeight= staticLayout.getHeight() + tv.getPaddingTop() + tv.getPaddingBottom();
    }

    @Override
    public void onClick(View v) {
        if (v.getId() ==R.id.tv) {
            if (v.isSelected()) {
                //收起的状态
                //因为现在是收起的状态,所以可以得到contentview开始执行动画的高度
                elipseHeight = tv.getHeight();
                animation = new ExpandCollapseAnimation(contentView,elipseHeight,expandHeight);
                animation.setFillAfter(true);
                animation.setAnimationListener(new Animation.AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) {
                        //将contentview高度设置为textview的高度,以此让textview是一行一行的展示
                        contentView.getLayoutParams().height = elipseHeight;
                        contentView.requestLayout();
                        tv.setText(notElipseString);
                        tv.setSelected(false);
                    }

                    @Override
                    public void onAnimationEnd(Animation animation) {

                    }

                    @Override
                    public void onAnimationRepeat(Animation animation) {

                    }
                });

            } else {
                //展开
                animation = new ExpandCollapseAnimation(contentView,expandHeight,elipseHeight);
                animation.setFillAfter(true);
                animation.setAnimationListener(new Animation.AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) {

                    }

                    @Override
                    public void onAnimationEnd(Animation animation) {
                        //  动画结束后textview设置展开的状态
                        tv.setText(elipseString);
                        tv.setSelected(true);
                    }

                    @Override
                    public void onAnimationRepeat(Animation animation) {

                    }
                });
            }
            contentView.clearAnimation();
            //  执行动画
            contentView.startAnimation(animation);
        }
    }

以上就是实现展开收起的相关动画的代码,有不正确的地方,请大家指出。谢谢!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏androidBlog

Android 圆形头像的两种实现方式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/de...

1300
来自专栏lzj_learn_note

Android红包雨动画

上面就红包实体类的源码,重点就是在创建红包实体的时候,初始化红包相关的值,如生成红包图片,图片的宽高,红包初始位置,下落速度等。比较简单。

5374
来自专栏Android先生

Android自定义View——从零开始实现雪花飘落效果

前言:转眼已是十一月下旬了,天气慢慢转冷,不知道北方是不是已经开始下雪了呢?本期教程我们就顺应季节主题,一起来实现 雪花飘落的效果吧。本篇效果思路参考自国外大神...

1502
来自专栏Jack的Android之旅

淘宝开源库VLayout实践

最近淘宝出了vlayout,刚开始看淘宝的文档的时候还是有点懵,后来自己也总结规划了一下,写了一个比较好看的demo,顺便在这里总结一下。

1812
来自专栏developerHaoz 的安卓之旅

Android 带你撸一个好玩的 DoodleView(涂鸦)

可以看到这个这个自定义 View 的功能还是很丰富的,无论是设置画笔的形状、颜色、粗细,还是进行重置和保存,该有的 API,基本都已经实现了。有需要的读者直接 ...

1013
来自专栏向治洪

Android Material Design之Toolbar与Palette实践

前言 我们都知道Marterial Design是Google推出的全新UI设计规范,如果对其不太了解的可以看下:Material design非官方中文指导手...

2248
来自专栏Android点滴积累

Android XML shape 标签使用详解(apk瘦身,减少内存好帮手)

  一个android开发者肯定懂得使用 xml 定义一个 Drawable,比如定义一个 rect 或者 circle 作为一个 View 的背景。但是,也肯...

1150
来自专栏向治洪

android Material Design详解

前言 我们都知道Marterial Design是Google推出的全新UI设计规范,如果对其不太了解的可以看下:Material design非官方中文指...

3539
来自专栏Android点滴积累

Android XML shape 标签使用详解(apk瘦身,减少内存好帮手)

Android XML shape 标签使用详解   一个android开发者肯定懂得使用 xml 定义一个 Drawable,比如定义一个 rect 或者 c...

3397
来自专栏开发之途

Android 仿360悬浮球与加速球

3538

扫码关注云+社区

领取腾讯云代金券