展开--收起 功能实现(kotlin+java)

平时关于展开和收起的使用效果是不少的。也有一些各路子的实现方式。关于这个实现,这里我用字符串裁剪+富文本的方式去实现。

效果图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191015103401918.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpeGlueGlhb3M=,size_16,color_FFFFFF,t_70

首先。我们要算出手机屏幕的宽度,如果在设计上两边有边距的话记得减去,如此,得到的便是TextView的宽度:

val dm = host.resources.displayMetrics
val width = (dm.widthPixels) - 两侧边距

下一步,拿到textView单个文本的大小,再用上面算出的TextView的宽度除上单个字体的大小:

val textSize = tvBrief.textSize
val linesNum = (width / textSize) * 保留显示的行数//得到两行总字数

如上,得到的就是要保留的行数的字体总和。后面可根据业务来做具体实现。如:

//如果后台返给的字数大于要保留的行数字数的总和就追加“展开”,否则全部展示
if (talker.summary.length > linesNum) {
	expandText(tvBrief, talker, linesNum, "展开")
} else {
	tvBrief.text = talker.summary
}

这里是expandText 和 collapseText 方法的具体实现,为裁剪追加字符串和展开收起的方法:

//收起状态,点击展开
    fun expandText(tvBrief: ExpandTextView, talker: Talker, linesNum: Float, expand: String) {
        val strSummary = "${talker.summary.substring(0, (linesNum - 9).toInt())}...    $expand"
        onChange(tvBrief, talker, linesNum, expand, strSummary)
    }


///


//展开状态,点击收起
    fun collapseText(tvBrief: ExpandTextView, talker: Talker, linesNum: Float, expand: String) {
        val strSummary = "${talker.summary}    $expand"
        onChange(tvBrief, talker, linesNum, expand, strSummary)
    }

下面为onChange方法,具体的样式和效果逻辑(注意:这里用自定义的TextView,会在下面把源码贴出):

/**

tvBrief:TextView控件
talker:后台返给的bean类,可以去到要截取的数据
linesNum:为保留行数的字数总和
expand:要追加的文字,如(展开,收起)
strSummary:截取并且拼接好的字符串
**/
fun onChange(tvBrief: ExpandTextView, talker: Talker, linesNum: Float, expand: String, strSummary: String) {

        val spannableString = SpannableString(strSummary)
        val span = RoundBackgroundColorSpan(Color.parseColor("#faa623"), Color.parseColor("#ffffff"), 12)
        spannableString.setSpan(span, strSummary.length - 3, strSummary.length, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
        spannableString.setSpan(object : ClickableSpan() {
            override fun onClick(widget: View) {
                if (expand == "展开") {
                    collapseText(tvBrief, talker, linesNum, "收起")
                } else {
                    expandText(tvBrief, talker, linesNum, "展开")
                }
            }
        }, strSummary.length - 3, strSummary.length, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)

        tvBrief.text = spannableString
        tvBrief.movementMethod = LinkMovementMethod.getInstance()
    }

自定义view :ExpandTextView.java

/**
 * 自定义控件,长文本展开收起TextView
 */
public class ExpandTextView extends AppCompatTextView {
    /**
     * 原始内容文本
     */
    private String originText;
    /**
     * TextView最大行数
     */
    private int mMaxLines = 4;
    /**
     * 收起的文案(颜色处理)
     */
    private SpannableString SPAN_CLOSE = null;
    /**
     * 展开的文案(颜色处理)
     */
    private SpannableString SPAN_EXPAND = null;
    private final String TEXT_EXPAND = "    查看更多 v";
    private final String TEXT_CLOSE = "  <收起";

    public ExpandTextView(Context context) {
        super(context);
        initCloseEnd();
    }

    public ExpandTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initCloseEnd();
    }

    public ExpandTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initCloseEnd();
    }

    /**
     * 设置TextView可显示的最大行数
     *
     * @param maxLines 最大行数
     */
    @Override
    public void setMaxLines(int maxLines) {
        this.mMaxLines = maxLines;
        super.setMaxLines(maxLines);
    }

    /**
     * 收起的文案(颜色处理)初始化
     */
    private void initCloseEnd() {
        String content = TEXT_EXPAND;
        SPAN_CLOSE = new SpannableString(content);
        ButtonSpan span = new ButtonSpan(getContext(), v -> {
            ExpandTextView.super.setMaxLines(Integer.MAX_VALUE);
            setExpandText(originText);
        }, R.color.themeAccent);
        SPAN_CLOSE.setSpan(span, 0, content.length() - 2, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);

        Drawable drawable = getResources().getDrawable(R.mipmap.ic_live_speech_summary_more);
        Rect rect = new Rect(0, -8, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        drawable.setBounds(rect);
        // drawable.setBounds(0, -8, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        SPAN_CLOSE.setSpan(new ImageSpan(drawable, ImageSpan.ALIGN_BASELINE), content.length() - 1, content.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    }

    /**
     * 展开的文案(颜色处理)初始化
     */
    private void initExpandEnd() {
        String content = TEXT_CLOSE;
        SPAN_EXPAND = new SpannableString(content);
        ButtonSpan span = new ButtonSpan(getContext(), v -> {
            ExpandTextView.super.setMaxLines(mMaxLines);
            setCloseText(originText);
        }, R.color.themeAccent);
        SPAN_EXPAND.setSpan(span, 0, content.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
    }

    public void setCloseText(CharSequence text) {

        if (SPAN_CLOSE == null) {
            initCloseEnd();
        }
        // true 不需要展开收起功能, false 需要展开收起功能
        boolean appendShowAll = false;
        originText = text.toString();

        // SDK >= 16 可以直接从xml属性获取最大行数
        int maxLines;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
            maxLines = getMaxLines();
        } else {
            maxLines = mMaxLines;
        }
        String workingText = originText;
        if (maxLines != -1) {
            Layout layout = createWorkingLayout(workingText);
            if (layout.getLineCount() > maxLines) {
                //获取一行显示字符个数,然后截取字符串数
                // 收起状态原始文本截取展示的部分
                workingText = originText.substring(0, layout.getLineEnd(maxLines - 1)).trim();
                String showText = originText.substring(0, layout.getLineEnd(maxLines - 1)).trim() + "..." + SPAN_CLOSE;
                Layout layout2 = createWorkingLayout(showText);
                // 对workingText进行-1截取,直到展示行数==最大行数,并且添加 SPAN_CLOSE 后刚好占满最后一行
                while (layout2.getLineCount() > maxLines) {
                    int lastSpace = workingText.length() - 1;
                    if (lastSpace == -1) {
                        break;
                    }
                    workingText = workingText.substring(0, lastSpace);
                    layout2 = createWorkingLayout(workingText + "..." + SPAN_CLOSE);
                }
                appendShowAll = true;
                workingText = workingText + "...";
            }
        }

        setText(workingText);
        if (appendShowAll) {
            // 必须使用append,不能在上面使用+连接,否则spannable会无效
            append(SPAN_CLOSE);
            setMovementMethod(LinkMovementMethod.getInstance());
        }
    }

    public void setExpandText(String text) {
        if (SPAN_EXPAND == null) {
            initExpandEnd();
        }
        Layout layout1 = createWorkingLayout(text);
        Layout layout2 = createWorkingLayout(text + TEXT_CLOSE);
        // 展示全部原始内容时 如果 TEXT_CLOSE 需要换行才能显示完整,则直接将TEXT_CLOSE展示在下一行
        if (layout2.getLineCount() > layout1.getLineCount()) {
            setText(originText + "\n");
        } else {
            setText(originText);
        }
        append(SPAN_EXPAND);
        setMovementMethod(LinkMovementMethod.getInstance());
    }

    /**
     * 返回textview的显示区域的layout,该textview的layout并不会显示出来,只是用其宽度来比较要显示的文字是否过长
     */
    private Layout createWorkingLayout(String workingText) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            return new StaticLayout(workingText, getPaint(), getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                    Layout.Alignment.ALIGN_NORMAL, getLineSpacingMultiplier(), getLineSpacingExtra(), false);
        } else {
            return new StaticLayout(workingText, getPaint(), getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                    Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
        }
    }
}

下面,为带圆角背景的富文本工具类实现(RoundBackgroundColorSpan.java),具体的样式可自定义富文本去实现:

public class RoundBackgroundColorSpan extends ReplacementSpan {
    private int mRadius;
    private int bgColor;
    private int textColor;
    private int mSize;

    public RoundBackgroundColorSpan(int bgColor,
                                    int textColor,
                                    int radius) {
        super();
        this.bgColor = bgColor;
        this.textColor = textColor;
        this.mRadius = radius;
    }

    /**
     * @param start 第一个字符的下标
     * @param end   最后一个字符的下标
     * @return span的宽度
     */
    @Override
    public int getSize(@NonNull Paint paint,
                       CharSequence text,
                       int start,
                       int end,
                       Paint.FontMetricsInt fm) {
        mSize = (int) (paint.measureText(text, start, end) + 2 * mRadius);
        return mSize + 5;//5:距离其他文字的空白
    }

    /**
     * @param y baseline
     */
    @Override
    public void draw(@NonNull Canvas canvas,
                     CharSequence text,
                     int start,
                     int end,
                     float x,
                     int top,
                     int y, int bottom,
                     @NonNull Paint paint) {
        int defaultColor = paint.getColor();//保存文字颜色
        float defaultStrokeWidth = paint.getStrokeWidth();

        //绘制圆角矩形
        paint.setColor(bgColor);
        paint.setStyle(Paint.Style.FILL);
        paint.setStrokeWidth(5);
        paint.setAntiAlias(true);
        RectF rectF = new RectF(x + 2.5f, y + 2.5f + paint.ascent(), x + mSize, y + paint.descent());
        //设置文字背景矩形,x为span其实左上角相对整个TextView的x值,y为span左上角相对整个View的y值。
        // paint.ascent()获得文字上边缘,paint.descent()获得文字下边缘
        //x+2.5f解决线条粗细不一致问题
        canvas.drawRoundRect(rectF, mRadius, mRadius, paint);

        //绘制文字
        paint.setColor(textColor);
        paint.setStyle(Paint.Style.FILL);
        paint.setStrokeWidth(defaultStrokeWidth);
        canvas.drawText(text, start, end, x + mRadius, y, paint);//此处mRadius为文字右移距离

        paint.setColor(defaultColor);//恢复画笔的文字颜色
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值