Android 点击按钮隐藏/展开 TextView 实现文本折叠效果

未经本人授权,不得转载!否则必将维权到底

这次版本迭代产品提出了一个很常见的需求:列表中的一个 TextView 条目默认展示两行文本,超过两行则展示一个 Button,可点击展开阅读。再次点击将文本折叠起来。可折叠的 TextView 网上教程很多,但找不到这种类似的。做这个需求又遇到一些坑,故记录一下,供后人参考,喜欢就直接 Ctrl + c/v。

效果展示

图例一.jpg

图例二.jpg

图例三.png

一、需求拆分

  1. 文本不满两行时,底部的展开按钮隐藏
  2. 文本超过两行,底部显示展开按钮,点击按钮文本展开/隐藏
  3. 记录展开/隐藏的状态,当该条目滑出屏幕可见范围或点击其他条目再返回时,展开/隐藏状态保持不变

前面两点很容易实现,重点在第三条。它很容易忽视,所以在第三步踩了点坑

二、具体实现

由于该条目出现在列表中,所以我们将其抽取成一个自定义 View ,这样逻辑比较清晰,更加解耦。

  • 布局(标题、正文、底部按钮)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:background="@color/white"
              android:orientation="vertical">

    <!-- 标题-->
    <cn.keithxiaoy.TitleView
        android:id="@+id/titleView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </cn.keithxiaoy.TitleView>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/white"
        android:orientation="vertical"
        android:paddingLeft="12dp"
        android:paddingRight="12dp"
        android:paddingTop="12dp">

        <!--重点属性: lineSpacingExtra = 3dp  行间距 3 dp-->
        <TextView
            android:id="@+id/apply_school_desc_notice"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:lineSpacingExtra="3dp"
            android:padding="@dimen/default_padding"
            android:textColor="@color/font_dark"
            android:textSize="@dimen/font_size_normal2"/>

    </LinearLayout>

    <!-- 点击展开/隐藏-->
    <cn.keithxiaoy.LookAllView
        android:id="@+id/lookAllView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </cn.keithxiaoy.LookAllView>

</LinearLayout>

  • 重点业务逻辑
    1.测量一行字体的高度
    2.计算出默认显示两行字体所需要的高度(一行字体*2 + 行间距 + 误差值)
    3.这里默认显示两行,我们可以设置 maxLine 属性为 2 ,如果服务端返回的数据超过两行,则可以将该 TextView 的 MaxLine 属性 设置为 20(或者更多,但笔者觉得通知一般显示不了 20 行,最优做法是动态计算返回文本的高度,但不想把需求复杂化,所以偷懒了)
    4.测量 TextView 时,需要延迟一会儿,否则无法测量出真实的控件高度
    5.做一个标志来记录 TextView 的展开/收缩状态,保证控件重绘时能够保持刚才的控件展开状态。(坑就在这里,刚开始实现的时候,遇到问题就是当滑动列表控件不可见再滑动回来,控件的状态没有保存。并且动态获取 TextView 为两行字体的高度,所以底部 Button 也被隐藏了)

  • 核心代码

    /**
     * notice:服务器返回的文本,字数未知
     */
    public void bindData(String notice) {
        if (!TextUtils.isEmpty(notice)) {
            setVisibility(View.VISIBLE);
            viewHolder.mTitleView.bindData(TitleView.TITLE_SIGNUPNOTICE);
            viewHolder.mTitleView.setVisibility(View.VISIBLE);
             // 这里估算出一行字体的高度
            int h = StringUtils.getFontHeigh("报名须知", viewHolder.mApplySchoolDescNotice);
            // DipUtils.dip2px(getContext(), 3) 行间距是3dp ------ DipUtils.dip2px(getContext(), 5) 是误差值
            final int h2 = 2 * h + DipUtils.dip2px(getContext(), 3) + DipUtils.dip2px(getContext(), 5);
            // 不要先设置 TextView 的最大高度为 2 ,否则测量出来的 TextView 控件高度都是展示两行文本的高度
            viewHolder.mApplySchoolDescNotice.setMaxLines(20);
            viewHolder.mApplySchoolDescNotice.setText(notice);
            // 一定要有延迟,给系统测量的时间
            viewHolder.mApplySchoolDescNotice.post(new Runnable() {
                @Override
                public void run() {
                    if (viewHolder.mApplySchoolDescNotice != null && viewHolder.mLookAllView != null) {
                       // 这里得到控件的高度
                       int h4 = viewHolder.mApplySchoolDescNotice.getHeight();
                       // 控件的高度 - 上下的 padding 值
                       int h5 = h4 - DipUtils.dip2px(getContext(), 24);
                        if (h5 > h2) {
                            //大于两行
                            if (!viewHolder.mLookAllView.getIsExpanded()){
                                //如果不是展开的情况
                                viewHolder.mApplySchoolDescNotice.setMaxLines(2);
                            }
                            viewHolder.mLookAllView.bindText(LookAllView.LOOKMORE_NOTICE, viewHolder.mApplySchoolDescNotice);
                            viewHolder.mLookAllView.setMVisible();
                        } else {
                            //小于两行,底部「 更多 」不显示
                            viewHolder.mLookAllView.setMGone();
                            viewHolder.mLookAllView.setViewDividerGone();
                        }
                    }

                }
            });


        } else {
            setVisibility(View.GONE);
        }
    }
  • LookAllView 里面是点击按钮,TextView 展开/收缩的逻辑‘’
    /**
     * 绑定数据
     *
     * @param type
     * @param NoticeTextView
     */
    public void bindText(int type, TextView NoticeTextView) {
        mType = type;
        mTextView = NoticeTextView;
        if (!mIsExpanded) {
            lineUp.setVisibility(View.VISIBLE);
            mViewDivider.setVisibility(View.GONE);
            tvMore.setText("更多");
            // 更多/收起 旁边的小箭头
            tvMore.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.arrow_gray_down, 0);
        }else {
            lineUp.setVisibility(View.VISIBLE);
            mTextView.setMaxLines(20);
            tvMore.setText("收起");
            // 更多/收起 旁边的小箭头
            tvMore.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.arrow_gray_up, 0);
        }
    }


    /**
     * 点击底部 Button 的逻辑
     *
     * @param type
     * @param NoticeTextView
     */
        //点击查看全部xx
        llMore.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                //更多报名须知
                else if (mType == LookAllView.LOOKMORE_NOTICE) {
                    if (null != mTextView && null != tvMore) {
                        if (tvMore.getText().toString().equalsIgnoreCase("收起")) {
                            lineUp.setVisibility(View.VISIBLE);
                            tvMore.setText("更多");
                            tvMore.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.arrow_gray_down, 0);
                            mTextView.setMaxLines(2);
                            mIsExpanded = false;
                        } else {
                            lineUp.setVisibility(View.VISIBLE);
                            mTextView.setMaxLines(20);
                            tvMore.setText("收起");
                            tvMore.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.arrow_gray_up, 0);
                            mIsExpanded = true;
                        }

                    }
                }
            }
        });

三、 可伸缩 TextView 完整代码

/**
 * Created by KeithXiaoY on 17/07/03.
 */
public class SignUpNoticeItemView extends LinearLayout {

    private ViewHolder viewHolder;
    private boolean isExpanded;

    public SignUpNoticeItemView(Context context) {
        super(context);
        init(context);
    }

    public SignUpNoticeItemView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public SignUpNoticeItemView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public SignUpNoticeItemView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context);
    }

    private void init(Context context) {
        // 这里的布局在上面已经给出了
        LayoutInflater.from(context).inflate(R.layout.layout_signupnotice_itemview, this);
        viewHolder = new ViewHolder(this);
    }

    static class ViewHolder {

        @Bind(R.id.titleView)
        TitleView mTitleView;
        @Bind(R.id.apply_school_desc_notice)
        TextView mApplySchoolDescNotice;
        @Bind(R.id.lookAllView)
        LookAllView mLookAllView;

        public ViewHolder(View view) {
            ButterKnife.bind(this, view);
        }
    }

    /**
     * 详细的分析已经写在本文的第二部分了,真的尽力写的很详细了
     */
    public void bindData(String notice) {
        if (!TextUtils.isEmpty(notice)) {
            setVisibility(View.VISIBLE);
            viewHolder.mTitleView.bindData(TitleView.TITLE_SIGNUPNOTICE);
            viewHolder.mTitleView.setVisibility(View.VISIBLE);

            int h = StringUtils.getFontHeigh("报名须知", viewHolder.mApplySchoolDescNotice);
            // DipUtils.dip2px(getContext(), 3) 行间距是3dp ------ DipUtils.dip2px(getContext(), 5) 是误差值
            final int h2 = 2 * h + DipUtils.dip2px(getContext(), 3) + DipUtils.dip2px(getContext(), 5);
            viewHolder.mApplySchoolDescNotice.setMaxLines(20);
            viewHolder.mApplySchoolDescNotice.setText(notice);
            viewHolder.mApplySchoolDescNotice.post(new Runnable() {
                @Override
                public void run() {
                    if (viewHolder.mApplySchoolDescNotice != null && viewHolder.mLookAllView != null) {
                        int h4 = viewHolder.mApplySchoolDescNotice.getHeight();
                       int h5 = h4 - DipUtils.dip2px(getContext(), 24);
                        if (h5 > h2) {
                            //大于两行
                            if (!viewHolder.mLookAllView.getIsExpanded()){
                                //如果不是展开的情况
                                viewHolder.mApplySchoolDescNotice.setMaxLines(2);
                            }
                            viewHolder.mLookAllView.bindText(LookAllView.LOOKMORE_NOTICE, viewHolder.mApplySchoolDescNotice);
                            viewHolder.mLookAllView.setMVisible();
                        } else {
                            //小于两行
                            viewHolder.mLookAllView.setMGone();
                            viewHolder.mLookAllView.setViewDividerGone();
                        }
                    }

                }
            });
        } else {
            setVisibility(View.GONE);
        }
    }

}

四、 结语(如果有更好的实现方法,欢迎留言)

具体实现思路已经写的很清楚了,注释写的也特别详细了。由于顶部「 标题 」 和 底部「 更多 」复用的地方很多,所以也单独写了一个自定义 View 进行解耦,代码里有大段和这个需求无关的东西,所以也没办法贴出来。写代码主要是思路,剩下的大家就结合自身的需求来写吧~


本文原创发布于微信公众号「keithxiaoy」,编程、思维、成长、正能量,关注并回复「编程」、「阅读」、「Java」、「Python」等关键字获取免费学习资料

不要给自己的人生设限

猜你喜欢

转载自blog.csdn.net/xiaoy_yan/article/details/80985897