自定義可展開收起TextView,展開收起按鈕緊跟文字內容
阿新 • • 發佈:2019-01-23
自定義展開收起TextView的文章有很多,不過大多都是在文字後面一行新增一個按鈕,自定義按鈕監聽事件達到展開收起的目標,但是專案需求展開收起的文案要緊跟著文字內容。先上效果圖:
如果收起文案需要換行才能顯示完整,則直接將收起文案展示在下一行
直接上程式碼,詳情見註釋
ExpandTextView.class
ButtonSpan.classimport android.content.Context; 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.util.AttributeSet; import android.view.View; import android.widget.TextView; /** * 自定義控制元件,長文字展開收起TextView */ public class ExpandTextView extends TextView { private String originText;// 原始內容文字 private int initWidth = 0;// TextView可展示寬度 private int mMaxLines = 3;// TextView最大行數 private SpannableString SPAN_CLOSE = null;// 收起的文案(顏色處理) private SpannableString SPAN_EXPAND = null;// 展開的文案(顏色處理) private String TEXT_EXPAND = " 檢視更多>"; private 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); } /** * 初始化TextView的可展示寬度 * @param width */ public void initWidth(int width){ initWidth = width; } /** * 收起的文案(顏色處理)初始化 */ private void initCloseEnd(){ String content = TEXT_EXPAND; SPAN_CLOSE = new SpannableString(content); ButtonSpan span = new ButtonSpan(getContext(), new View.OnClickListener(){ @Override public void onClick(View v) { ExpandTextView.super.setMaxLines(Integer.MAX_VALUE); setExpandText(originText); } }, R.color.color_fe9901); SPAN_CLOSE.setSpan(span, 0, content.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); } /** * 展開的文案(顏色處理)初始化 */ private void initExpandEnd(){ String content = TEXT_CLOSE; SPAN_EXPAND = new SpannableString(content); ButtonSpan span = new ButtonSpan(getContext(), new View.OnClickListener(){ @Override public void onClick(View v) { ExpandTextView.super.setMaxLines(mMaxLines); setCloseText(originText); } }, R.color.color_fe9901); SPAN_EXPAND.setSpan(span, 0, content.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); } public void setCloseText(CharSequence text) { if(SPAN_CLOSE == null){ initCloseEnd(); } boolean appendShowAll = false;// true 不需要展開收起功能, false 需要展開收起功能 originText = text.toString(); // SDK >= 16 可以直接從xml屬性獲取最大行數 int maxLines = 0; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { maxLines = getMaxLines(); } else{ maxLines = mMaxLines; } String workingText = new StringBuilder(originText).toString(); 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(), initWidth - getPaddingLeft() - getPaddingRight(), Layout.Alignment.ALIGN_NORMAL, getLineSpacingMultiplier(), getLineSpacingExtra(), false); } else{ return new StaticLayout(workingText, getPaint(), initWidth - getPaddingLeft() - getPaddingRight(), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } } }
使用例子import android.content.Context; import android.text.TextPaint; import android.text.style.ClickableSpan; import android.view.View; public class ButtonSpan extends ClickableSpan { View.OnClickListener onClickListener; private Context context; private int colorId; public ButtonSpan(Context context, View.OnClickListener onClickListener) { this(context, onClickListener, R.color.color_693f3e); } public ButtonSpan(Context context, View.OnClickListener onClickListener, int colorId){ this.onClickListener = onClickListener; this.context = context; this.colorId = colorId; } @Override public void updateDrawState(TextPaint ds) { ds.setColor(context.getResources().getColor(colorId)); ds.setUnderlineText(false); } @Override public void onClick(View widget) { if (onClickListener != null) { onClickListener.onClick(widget); } } }
import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { private ExpandTextView mContentExpandTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mContentExpandTextView = (ExpandTextView) findViewById(R.id.txt_content); // 設定TextView可展示的寬度 ( 父控制元件寬度 - 左右margin - 左右padding) int whidth = ScreenUtils.getScreenWidth(this) - ScreenUtils.dip2px(this, 16 * 2); mContentExpandTextView.initWidth(whidth); // 設定最大行數(如果SDK >= 16 也可以直接在xml裡設定) mContentExpandTextView.setMaxLines(3); String content = "茫茫的長白大山,浩瀚的原始森林,大山腳下,原始森林環抱中散落著幾十戶人家的" + "一個小山村,茅草房,對面炕,煙筒立在屋後邊。在村東頭有一個獨立的房子,那就是青年點," + "窗前有一道小溪流過。學子在這裡吃飯,由這裡出發每天隨社員去地裡幹活。乾的活要麼上山伐" + "樹,擡樹,要麼砍柳樹毛子開荒種地。在山裡,可聽那吆呵聲:“順山倒了!”放樹謹防回頭棒!" + "樹上的枯枝打到別的樹上再蹦回來,這回頭棒打人最厲害。"; mContentExpandTextView.setCloseText(content); } }
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.lxw.expandtextview.MainActivity">
<com.lxw.expandtextview.ExpandTextView
android:id="@+id/txt_content"
android:textSize="20sp"
android:textColor="@color/color_693f3e"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
需要特別注意的是設定ExpandTextView可展示寬度,如果這個寬度值計算錯誤,則ExpandTextView會出現顯示異常的情況。參考資料: