专栏首页lzj_learn_noteAndroid TextView实现查看全部和收起功能

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 条评论
登录 后参与评论

相关文章

  • Android ImageSpan与TextView同一行图片居中

    在开发中常常会遇到标签(图片)+文字的需求,实现方式一般采用SpannableString的方式来实现。 这时候会遇到图片ImageSpan没有办法居中的问题。...

    用户3106371
  • 自定义无限循环ViewPager(三)――ViewPager方法改造实现无限循环

    在前面两篇文章中,已经对ViewPager的实现原理有了分析,相信大家对于ViewPager的页面切换也有了一定的了解,接下来就是在ViewPager的基础上对...

    用户3106371
  • 5-遍历、公共方法、引用

    通过for ... in ...:的语法结构,可以遍历字符串、列表、元组、字典,集合等数据结构。

    用户3106371
  • 替换空格

    题目:请实现一个函数,把字符串中的每个空格替换成“%20”。例如输入“We are happy.”,则输出“We%20are%20happy.”。       ...

    猿人谷
  • C++实现int与string之间的相互转换

    c++ 利用stringstream实现int与string类型的相互转换,记录在此,以备后用 #include<iostream> #include<ss...

    一灰灰blog
  • KMP算法 C#实现 字符串查找简单实现

    FreeTimeWorker
  • UNPv2第六章:System V 消息队列

    返回值是一个整数标识符,其他三个msg函数就用它来指代该队列。它是基于指定的key产生的,而key既可以是ftok返回值,也可以是IPC_PRIVATE。 ...

    提莫队长
  • 蓝桥杯-基础练习 查找整数

    TrueDei
  • Go 从入门到精通(三)字符串,时间,流程控制,函数

    一、strings和strconv的使用 strings strings.HasPrefix(s string,preffix string) bool: 判断...

    coders
  • [算法题] 字节流解析

    字节流解析 题目标题: 根据数值占用BIT数,按顺序从输入字节流中解析出对应数值,解析顺序按输入数组astElement索引升序。 详细描述: 接口说明...

    静默虚空

扫码关注云+社区

领取腾讯云代金券