Android 可折叠自定义ExpandTextView

参考自他人https://blog.csdn.net/Luckly452468460/article/details/103613471
通过自己实现CreateAppenderListener,可以自己定义 尾部追加的文字或者图片,或者文字加图片。
有注释,就不解释细节了。
使用:
折叠状态:
在这里插入图片描述
展开状态:
在这里插入图片描述

<!--这里的宽度不能设置wrap_content,可以具体值或者match_parent-->
<com.example.myapplication.ExpandTextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:maxLines="4" />
expandTextView.init(false, text, null);
expandTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                expandTextView.toggle();
            }
});

实现:

package com.example.myapplication;

import android.content.Context;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.Layout;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.StaticLayout;
import android.text.method.LinkMovementMethod;
import android.text.style.ImageSpan;
import android.util.AttributeSet;

import androidx.annotation.Nullable;


/**
 * 自定义控件,文本展开收起TextView
 */
public class ExpandTextView extends androidx.appcompat.widget.AppCompatTextView {
    /**
     * 原始内容文本
     */
    private String originText;
    /**
     * TextView可展示宽度
     */
    private int mWidth = Integer.MAX_VALUE;
    /**
     * TextView限制显示的最大行数
     */
    private int mMaxLines = 0;
    /**
     * 收起状态时的拼接文案
     */
    private SpannableString SPAN_TO_EXPAND = null;
    /**
     * 展开状态时的拼接文案
     */
    private SpannableString SPAN_TO_CLOSE = null;
    /**
     * 文本格式(true全角   false半角)
     */
    private boolean ToDBC = true;
    /**
     * 状态值 true:展开中  false:折叠状态   (该状态值只能在当前类内部修改)
     */
    private boolean mIsExpanding = false;
    private int mOriginTextLines;
    /**
     * 追加的图片的宽度
     */
    private int mToExpandImageWidth = 0;
    private int mToCloseImageWidth = 0;

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

    }

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

    }

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

    }

    /**
     * 使用前必须调用该方法
     */
    public void init(boolean toExpand, String text, @Nullable CreateAppenderListener createAppenderListener) {
        mMaxLines = getMaxLines();
        originText = ToDBC ? ToDBC(text) : toDBC(text);
        if (createAppenderListener != null) {
            // 1.优先使用listener的生成
            SPAN_TO_CLOSE = createAppenderListener.getDefaultToCloseSpannableString();
            SPAN_TO_EXPAND = createAppenderListener.getDefaultToExpandSpannableString();
        } else {
            // 2.使用默认的SpannableString
            SPAN_TO_CLOSE = getDefaultToCloseSpannableString();
            SPAN_TO_EXPAND = getDefaultToExpandSpannableString();
        }

        mOriginTextLines = createWorkingLayout(originText).getLineCount();
        // 设置初始显示的文本
        toggle(toExpand);
    }

    public void toggle(boolean toExpand) {
        // 由于获取不到textview的宽度,所以这里用post方法
        post(new Runnable() {
            @Override
            public void run() {
                mWidth = getWidth();
                if (toExpand) {
                    setExpandText();
                } else {
                    setCloseText();
                }
            }
        });
    }

    public void toggle() {
        if (mIsExpanding) {
            toggle(false);
        } else {
            toggle(true);
        }

    }

    /**
     * 设置TextView可显示的最大行数
     *
     * @param maxLines 最大行数
     */
    @Override
    public void setMaxLines(int maxLines) {
        if (mMaxLines == 0) {
            // 这里对mMaxLines记录一次就可以
            this.mMaxLines = maxLines;
        }
        super.setMaxLines(maxLines);
    }

    public void setToDBC(boolean toDBC) {
        ToDBC = toDBC;
    }

    public int getToExpandImageWidth() {
        return mToExpandImageWidth;
    }

    public int getToCloseImageWidth() {
        return mToCloseImageWidth;
    }

    public void setToExpandImageWidth(int mToExpandImageWidth) {
        this.mToExpandImageWidth = mToExpandImageWidth;
    }

    public void setToCloseImageWidth(int mToCloseImageWidth) {
        this.mToCloseImageWidth = mToCloseImageWidth;
    }

    public SpannableString getDefaultToExpandSpannableString() {
        SpannableString spannableString = new SpannableString("... ");
        // 测量文字的高度,用于设置图片大小
        Paint paint = getPaint();
        Paint.FontMetrics fontMetrics = paint.getFontMetrics();
        setToExpandImageWidth((int) (fontMetrics.descent - fontMetrics.ascent));
        // 对图片的宽高设置
        Drawable drawable = getResources().getDrawable(R.mipmap.ic_launcher);
        drawable.setBounds(0, 0 , getToExpandImageWidth(), getToExpandImageWidth());
        ImageSpan span = new ImageSpan(drawable);
        spannableString.setSpan(span, spannableString.length() - 1, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        // 对imageSpan设置点击事件,也可以这里不设置,在Activity对expandTextView整个事件设置点击事件
//        spannableString.setSpan(new ClickableSpan() {
//            @Override
//            public void onClick(@NonNull View widget) {
//                toggle();
//            }
//        }, spannableString.length() - 1, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        return spannableString;
    }


    public SpannableString getDefaultToCloseSpannableString() {
        // 因为末尾要插入图片,这里空格是占位符
        SpannableString spannableString = new SpannableString(" ");
        // 测量文字的高度,用于设置图片大小
        Paint paint = getPaint();
        Paint.FontMetrics fontMetrics = paint.getFontMetrics();
        setToCloseImageWidth((int) (fontMetrics.descent - fontMetrics.ascent));
        // 对图片的宽高设置
        Drawable drawable = getResources().getDrawable(R.mipmap.ic_launcher);
        drawable.setBounds(0, 0 , getToCloseImageWidth(), getToCloseImageWidth());
        ImageSpan span = new ImageSpan(drawable);
        spannableString.setSpan(span, spannableString.length() - 1, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
//        spannableString.setSpan(new ClickableSpan() {
//            @Override
//            public void onClick(@NonNull View widget) {
//                toggle();
//            }
//        }, spannableString.length() - 1, spannableString.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        return spannableString;
    }

    /**
     * 设置文本收起
     */
    public void setCloseText() {
        mIsExpanding = false;
        setMaxLines(mMaxLines);

        boolean needAppend = false;
        String workingText = originText;

        if (mMaxLines != 0) {
            Layout originLayout = createWorkingLayout(originText);
            mOriginTextLines = originLayout.getLineCount();
            // 原始文本的行数 大于 最大能显示行数
            if (mOriginTextLines > mMaxLines) {
                // 获取mMaxLines行的文本
                workingText = originText.substring(0, originLayout.getLineEnd(mMaxLines - 1)).trim();
                // 计算mMaxLines行的文本的宽度
                float allWidth = getPaint().measureText(workingText);
                // 当前显示需要的宽度
                float realWidth = getPaint().measureText(workingText + SPAN_TO_EXPAND) + mToExpandImageWidth;

                while (realWidth > allWidth) {
                    int lastSpace = workingText.length() - 1;
                    if (lastSpace == -1) {
                        break;
                    }
                    workingText = workingText.substring(0, lastSpace);
                    realWidth = getPaint().measureText(workingText + SPAN_TO_EXPAND) + mToExpandImageWidth;
                }

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

    /**
     * 设置文本展开
     */
    public void setExpandText() {
        if (mOriginTextLines <= mMaxLines) {
            return;
        }
        mIsExpanding = true;
        setMaxLines(Integer.MAX_VALUE);

        Layout originLayout = createWorkingLayout(originText);
        Layout compareLayout = createWorkingLayout(originText + SPAN_TO_CLOSE);
        if (compareLayout.getLineCount() > originLayout.getLineCount()) {
            setText(originText + "\n");
        } else {
            setText(originText);
        }
        append(SPAN_TO_CLOSE);
        setMovementMethod(LinkMovementMethod.getInstance());
    }

    /**
     * 返回textview的显示区域的layout
     */
    private Layout createWorkingLayout(String workingText) {
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
            return StaticLayout.Builder.obtain(workingText, 0, workingText.length(), getPaint(), mWidth).build();
        } else {
            return new StaticLayout(workingText, getPaint(), mWidth - getPaddingLeft() - getPaddingRight(),
                    Layout.Alignment.ALIGN_NORMAL, getLineSpacingMultiplier(), getLineSpacingExtra(), false);
        }
    }

    /**
     * 屏蔽长按事件,防止崩溃
     *
     * @param longClickable
     */
    @Override
    public void setLongClickable(boolean longClickable) {
        super.setLongClickable(false);
    }

    /**
     * 转全角
     *
     * @param input
     * @return
     */
    private static String toDBC(String input) {
        char[] c = input.toCharArray();
        for (int i = 0; i < c.length; i++) {
            if (c[i] == '\n') {

            } else if (c[i] == ' ') {
                c[i] = '\u3000';
            } else if (c[i] < '\177') {
                c[i] = (char) (c[i] + 65248);
            }
        }
        return new String(c);
    }

    /**
     * 转半角
     *
     * @param input
     * @return
     */
    public static String ToDBC(String input) {
        char[] c = input.toCharArray();
        for (int i = 0; i < c.length; i++) {
            if (c[i] == '\u3000') {
                c[i] = ' ';
            } else if (c[i] > '\uFF00' && c[i] < '\uFF5F') {
                c[i] = (char) (c[i] - 65248);

            }
        }
        return new String(c);
    }

    /**
     * 用于生成 文本末尾要追加的SpannableString
     */
    public interface CreateAppenderListener {
        SpannableString getDefaultToCloseSpannableString();
        SpannableString getDefaultToExpandSpannableString();
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值