Android 可折叠自定义ExpandTextView
Posted Keepingrun
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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();
以上是关于Android 可折叠自定义ExpandTextView的主要内容,如果未能解决你的问题,请参考以下文章
可折叠部分和每个部分的多个自定义 UITableViewCell