参考自他人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();
}
}