Android实现TextView内容可展开收缩功能

        最近在开发中需要实现一个需求,某项东西的介绍文字超过三行默认要收起,点击末端下拉箭头可以展开查看更多,大致效果图如下:

        咋一看需求还挺简单,GitHub上也有现成的ExpandableTextView控件可用,实现效果:

        好像看起来差不多,不过UI验收的时候就过不去了,要求下拉箭头需要跟在文字后面,且要有省略号表示更多内容,那现成的控件是不能使用了,只好自己想办法。因为下拉箭头要跟随文字,所以我们也不能使用drawableRight方法,想了想,那就用SpannableString加ImageSpan来实现试试,大致思路:先判断TextView在页面上可以展示几行,超过三行,我们就取前三行文字内容,末端截取、拼接“... ∨”上去;不超过三行,那就不做改动,直接展示。现在思路有了,那就可以开动写代码了。

        第一步,获取TextView内容在页面上可以展示几行。通过TextView的getLayout()方法得到TextView的布局Layout对象,调用getLineCount()方法就可以知道TextView内容在页面上展示几行了,代码:

// 得到TextView的布局对象
Layout layout = textView.getLayout();
// 得到TextView显示有多少行
int lines = layout.getLineCount();

        第二步,判断行数是否超过UI要求展示的行数,超过(我们这里定3行),我们取前三行的文字内容,这里又要用到第一步中的Layout对象,我们调用getLineStart(int)和getLineEnd(int)来获取前三行文字内容,大致代码如下:

StringBuffer threeLinesContent = new StringBuffer();
for (int i = 0; i < 3; i ++) {
    threeLinesContent.append(textContent.substring(layout.getLineStart(i), layout.getLineEnd(i));
}

        第三步,拼接前三行末尾的“... ∨”展示,代码:

threeLinesContent = threeLinesContent .substring(0, threeLinesContent .length() - 3) + "...\u3000 ";
setContentText(threeLinesContent , false);
​​private void setContentText(String textContent, boolean expand) {
    ​SpannableString ss = new SpannableString(textContent);
    Drawable drawable;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        drawable = getResources().getDrawable(expand ? R.drawable.common_up_arrow : R.drawable.common_down_arrow, null);
    } else {
        drawable = getResources().getDrawable(expand ? R.drawable.common_up_arrow : R.drawable.common_down_arrow);
    }
    drawable.setBounds(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight());
    ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BASELINE);
    ss.setSpan(imageSpan, textContent.length() - 1, textContent.length(), 
    Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
    textView.setText(ss);
}

        第四步,给TextView添加点击事件,处理展开收缩功能

textView.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View view) {
        if (isExpand) {
            isExpand = false;
            setContentText(threeLineContent, false);
        } else {
            isExpand = true;
            setContentText(textContent, true);
        }
    }
});

       

经过上面四步,基本上已经实现了我们需要的效果了,为什么说是基本上呢,因为,如果第一步的代码写在Activity的onCreate()方法里的话,会报空指针,TextView获取Layout对象需要在页面渲染完成才能获取到,所以我们改造下上面的代码,完整代码如下:

    private TextView introduceTV;

    // 介绍是否展开
    private boolean isExpand;
    // 介绍动作runnable
    private Runnable resumeRunnable;

    protected void onCreate(Bundle savedInstanceState) {
        ......
        introduceTV.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                // 擅长
                introduceTV.setText(describtionStr);
                resumeRunnable = new LineContent(introduceTV, describtionStr);
                introduceTV.post(resumeRunnable);
                introduceTV.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });
    }

    private class LineContent implements Runnable {
        private TextView mTarget;
        private String mContent;

        public LineContent(TextView mTarget, String mContent) {
            this.mTarget = mTarget;
            this.mContent = mContent;
        }

        public void run() {
            if (null != mTarget && !TextUtils.isEmpty(mContent)) {
                GetLineContent();
            }
        }

        private void GetLineContent() {
            // 得到TextView的布局
            Layout layout = mTarget.getLayout();
            // 得到TextView显示有多少行
            int lines = mTarget.getLineCount();
            try {
                if (isExpand) {
                    setGoodAtText(mContent + "\u3000 ", isExpand);
                } else {
                    if (lines > 3) {
                        StringBuffer threeLinesContent = new StringBuffer();
                        for (int i = 0; i < 3; i++) {
                            threeLinesContent.append(layout.getLineStart(i), layout.getLineEnd(i));
                        threeLinesContent = threeLinesContent.substring(0, threeLinesContent.length() - 3) + "...\u3000 ";
                        setGoodAtText(threeLinesContent, false);
                        mTarget.setOnClickListener(new OnClickEffectiveListener() {
                            @Override
                            public void onClickEffective(View view) {
                                if (isExpand) {
                                    isExpand = false;
                                } else {
                                    isExpand = true;
                                }
                                mTarget.post(resumeRunnable);
                            }
                        });
                    }
                }
            } catch (Exception e) {
                // Do nothing
            }
        }

        private void setGoodAtText(String textContent, boolean expand) {
            SpannableString ss = new SpannableString(textContent);
            Drawable drawable;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                drawable = getResources().getDrawable(expand ? R.drawable.common_up_arrow : R.drawable.common_down_arrow, null);
            } else {
                drawable = getResources().getDrawable(expand ? R.drawable.common_up_arrow : R.drawable.common_down_arrow);
            }
            drawable.setBounds(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight());
            ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BASELINE);
            ss.setSpan(imageSpan, textContent.length() - 1, textContent.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
            mTarget.setText(ss);
        }
    }

    @Override
    protected void onDestroy() {
        if (null != resumeRunnable) {
            resumeRunnable = null;
        }
        super.onDestroy();
    }

 

       以上就是全部的实现过程,平时比较懒于记载东西,偶尔记录一次就花点时间认真写下,便于后续学习共享。不足之处请指正。

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值