一個 TextView 完成顯示全文與隱藏功能

zhou0715 7年前發布 | 18K 次閱讀 TextView Android開發 移動開發

經常遇到大段文本需要部分展示的場景,通常的做法是在隱藏的狀態下文本末尾加上「顯示全文」,在展開的狀態下文本末尾加上「隱藏」來控制文本的展示狀態。這個交互可能有很多種實現方法,本文則以一個簡單的 TextView 來實現這些交互,封裝后的 CollapsiableTextView 僅增加了不到 70 個額外的方法數。

參數定義

如上圖效果,我們需要使用到幾個可配置的參數:

<declare-styleablename="CollapsibleTextView">
  <attrname="suffixColor"format="color"/>
  <attrname="collapsedLines"format="integer"/>
  <attrname="collapsedText"format="string"/>
  <attrname="expandedText"format="string"/>
  <attrname="suffixTrigger"format="boolean"/>
</declare-styleable>

這幾個參數分別表示

  • 后綴顏色,也就是「顯示全文」,「隱藏」這幾個字的顏色
  • 折疊后顯示幾行文字
  • 折疊后的后綴文字,也就是「顯示全文」
  • 展開后的后綴文字,也就是「隱藏」
  • 隱藏與展示的觸發事件是點擊后綴還是整個 TextView

主要的構造函數如:

publicCollapsibleTextView(Context context, AttributeSet attrs,intdefStyleAttr){
  super(context, attrs, defStyleAttr);
  TypedArray attributes = context.getTheme()
      .obtainStyledAttributes(attrs, R.styleable.CollapsibleTextView, defStyleAttr, 0);

  mSuffixColor = attributes.getColor(R.styleable.CollapsibleTextView_suffixColor, 0xff0000ff);
  mCollapsedLines = attributes.getInt(R.styleable.CollapsibleTextView_collapsedLines, 1);
  mCollapsedText = attributes.getString(R.styleable.CollapsibleTextView_collapsedText);
  if (TextUtils.isEmpty(mCollapsedText)) mCollapsedText = " Show All";
  mExpandedText = attributes.getString(R.styleable.CollapsibleTextView_expandedText);
  if (TextUtils.isEmpty(mExpandedText)) mExpandedText = " Hide";
  mSuffixTrigger = attributes.getBoolean(R.styleable.CollapsibleTextView_suffixTrigger, false);

  this.mText = getText() == null ? null : getText().toString();
  setMovementMethod(LinkMovementMethod.getInstance());
  super.setOnClickListener(mClickListener);
}

代理 onClick 事件

為了配置是否由后綴觸發顯示與隱藏操作,我們要在 CollapsibleTextView 中處理點擊事件。所以在構造函數中設置 clickListener 為 mClickListener。同時在 mClickListener 中處理點擊事件:

private OnClickListener mClickListener = new OnClickListener() {
  @Override
  publicvoidonClick(View v){
    if (!mSuffixTrigger) {
      mExpanded = !mExpanded;
      applyState(mExpanded);
    }

    if (mCustomClickListener != null) {
      mCustomClickListener.onClick(v);
    }
  }
};

為了用戶仍可以設置 clickListener 我們重寫 setOnClickListener 方法,并保留 clickListener 為 mCustomClickListener:

@Override
publicvoidsetOnClickListener(OnClickListener l){
  mCustomClickListener = l;
}

這樣就將 click 事件代理到了 CollapsibleTextView 內部。

ClickableSpan 處理部分文本點擊

為了能夠監聽后綴的點擊事件,需要使用 ClickableSpan

str.setSpan(mClickSpanListener,
  note.length(),
  note.length() + suffix.length(),
  SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);

// ClickableSpan
private ClickableSpan mClickSpanListener
    = new ClickableSpan() {
  @Override
  publicvoidonClick(View widget){
    if (mSuffixTrigger) {
      mExpanded = !mExpanded;
      applyState(mExpanded);
    }
  }

  @Override
  publicvoidupdateDrawState(TextPaint ds){
    super.updateDrawState(ds);
    ds.setUnderlineText(false);
  }
};

根據狀態計算出 SpannableString

privatevoidapplyState(booleanexpanded){
  if (TextUtils.isEmpty(mText)) return;

  String note = mText, suffix;
  if (expanded) {
    suffix = mExpandedText;
  } else {
    if (mCollapsedLines - 1 < 0) {
      throw new RuntimeException("CollapsedLines must equal or greater than 1");
    }
    int lineEnd = getLayout().getLineEnd(mCollapsedLines - 1);
    suffix = mCollapsedText;
    int newEnd = lineEnd - suffix.length() - 1;
    int end = newEnd > 0 ? newEnd : lineEnd;

    TextPaint paint = getPaint();
    int maxWidth = mCollapsedLines * (getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
    while (paint.measureText(note.substring(0, end) + suffix) > maxWidth)
      end--;
    note = note.substring(0, end);
  }

  final SpannableString str = new SpannableString(note + suffix);
  if (mSuffixTrigger) {
    str.setSpan(mClickSpanListener,
        note.length(),
        note.length() + suffix.length(),
        SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
  }
  str.setSpan(new ForegroundColorSpan(mSuffixColor),
      note.length(),
      note.length() + suffix.length(),
      SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
  post(new Runnable() {
    @Override
    publicvoidrun(){
      setText(str);
    }
  });
}

其中 paint.measureText 可以測量出文本布局的寬度從而得只文本行數并與 mCollapsedLines 比較裁剪出合適的字符長度并添加上后綴與 span 賦予 TextView 即可

由于 getLineEnd 等函數只有在 layout 過程之后值才有意義,所以要合理的選擇 applyState 的時機:

@Override
protectedvoidonLayout(booleanchanged,intleft,inttop,intright,intbottom){
  super.onLayout(changed, left, top, right, bottom);
  if (mShouldInitLayout && getLineCount() > mCollapsedLines) {
    mShouldInitLayout = false;
    applyState(mExpanded);
  }
}

至此 CollapsibleTextView 的要點已經完成,添加上 getter,setter 函數與一些邏輯組織即可

 

 

 

 本文由用戶 zhou0715 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!