转载

Android自定义控件之全文收起TextView(继承TextView法)

前言

因为公司项目需要全文收起的功能,一共有2种UI,所以需要写2个全文收起的控件,关于第一个控件已经在第一篇文章讲述 嵌套法实现全文收起TextView ,本篇文章主要讲述直接继承至TextView的实现方法

效果图

Android自定义控件之全文收起TextView(继承TextView法)

实现原理

通过另外一个方法设置文本,同时在GlobalLayoutListener中计算每行出需要显示的总行数,判断是否需要全文收起功能,如果需要,则计算出每行需要显示多少文本,在设定的最大行计算时,把...+全文加进去计算,得到实际上应该显示的文本,同时把全文设置为可点击的文本,在点击事件中根据状态设置当前TextView显示的文本,如果当前状态是收起状态,点击后就设置显示所有文字+收起,全文状态则设置显示文本为最开始计算出来的文本

代码

package wang.raye.library.widge;  import android.annotation.TargetApi;   import android.content.Context;   import android.content.res.TypedArray;   import android.os.Build;   import android.text.SpannableStringBuilder;   import android.text.Spanned;   import android.text.TextPaint;   import android.text.TextUtils;   import android.text.method.LinkMovementMethod;   import android.text.style.ClickableSpan;   import android.util.AttributeSet;   import android.view.View;   import android.view.ViewTreeObserver;   import android.widget.TextView;  import wang.raye.library.R;   /**  * 有全文和收起的TextView ExpandableTextView  * Created by Raye on 2016/6/24.  */ public class CollapsedTextView extends TextView {       private static final String TAG = CollapsedTextView.class.getName();     /** 收起状态下的最大行数*/     private int maxLine = 2;     /** 截取后,文本末尾的字符串*/     private static final String ELLIPSE = "...";     /** 默认全文的Text*/     private static final String EXPANDEDTEXT = "全文";     /** 默认收起的text*/     private static final String COLLAPSEDTEXT = "收起";     /** 全文的text*/     private String expandedText = EXPANDEDTEXT;     /** 收起的text*/     private String collapsedText = COLLAPSEDTEXT;     /** 所有行数*/     private int allLines;     /** 是否是收起状态,默认收起*/     private boolean collapsed = true;     /** 真实的text*/     private String text;     /** 收起时实际显示的text*/     private CharSequence collapsedCs;     /** 全文和收起的点击事件处理*/     private ReadMoreClickableSpan viewMoreSpan = new ReadMoreClickableSpan();      public CollapsedTextView(Context context) {         super(context);         init(context,null);     }      public CollapsedTextView(Context context, AttributeSet attrs) {         super(context, attrs);         init(context,attrs);     }      public CollapsedTextView(Context context, AttributeSet attrs, int defStyleAttr) {         super(context, attrs, defStyleAttr);         init(context,attrs);     }      @TargetApi(Build.VERSION_CODES.LOLLIPOP)     public CollapsedTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {         super(context, attrs, defStyleAttr, defStyleRes);         init(context,attrs);     }      @Override     public TextPaint getPaint() {         return super.getPaint();     }      private void init(Context context,AttributeSet attrs){         if(attrs != null){             TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CollapsedTextView);             allLines = ta.getInt(R.styleable.CollapsedTextView_trimLines,0);             expandedText = ta.getString(R.styleable.CollapsedTextView_expandedText);             if(TextUtils.isEmpty(expandedText)){                 expandedText = EXPANDEDTEXT;             }             collapsedText = ta.getString(R.styleable.CollapsedTextView_collapsedText);             if(TextUtils.isEmpty(collapsedText)){                 collapsedText = COLLAPSEDTEXT;             }         }      }     public void setShowText(final String text){         this.text = text;         if(allLines > 0) {             getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {                 @Override                 public void onGlobalLayout() {                     ViewTreeObserver obs = getViewTreeObserver();                     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {                         obs.removeOnGlobalLayoutListener(this);                     } else {                         obs.removeGlobalOnLayoutListener(this);                     }                     TextPaint tp = getPaint();                     float width = tp.measureText(text);                 /* 计算行数 */                     //获取显示宽度                     int showWidth = getWidth() - getPaddingRight() - getPaddingLeft();                     int lines = (int) (width / showWidth);                     if (width % showWidth != 0) {                         lines++;                     }                     allLines = (int) (tp.measureText(text + collapsedText) / showWidth);                     if (lines > maxLine) {                         int expect = text.length() / lines;                         int end = 0;                         int lastLineEnd = 0;                         //...+expandedText的宽度,需要在最后一行加入计算                         int expandedTextWidth = (int) tp.measureText(ELLIPSE + expandedText);                         //计算每行显示文本数                         for (int i = 1; i <= maxLine; i++) {                             int tempWidth = 0;                             if (i == maxLine) {                                  tempWidth = expandedTextWidth;                             }                             end += expect;                             if (end > text.length()) {                                 end = text.length();                             }                             if (tp.measureText(text, lastLineEnd, end) > showWidth - tempWidth) {                                 //预期的第一行超过了实际显示的宽度                                 end--;                                 while (tp.measureText(text, lastLineEnd, end) > showWidth - tempWidth) {                                     end--;                                 }                             } else {                                 end++;                                 while (tp.measureText(text, lastLineEnd, end) < showWidth - tempWidth) {                                     end++;                                 }                                 end--;                             }                             lastLineEnd = end;                         }                         SpannableStringBuilder s = new SpannableStringBuilder(text, 0, end)                                 .append(ELLIPSE)                                 .append(expandedText);                         collapsedCs = addClickableSpan(s, expandedText);                         setText(collapsedCs);                          setMovementMethod(LinkMovementMethod.getInstance());                     } else {                         setText(text);                     }                 }             });             setText("");         }else{             setText(text);         }     }       private CharSequence addClickableSpan(SpannableStringBuilder s, CharSequence trimText) {         s.setSpan(viewMoreSpan, s.length() - trimText.length(), s.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);         return s;     }      private class ReadMoreClickableSpan extends ClickableSpan {         @Override         public void onClick(final View widget) {             if(collapsed){                 SpannableStringBuilder s = new SpannableStringBuilder(text)                         .append(collapsedText);                 setText(addClickableSpan(s,collapsedText));             }else{                 setText(collapsedCs);             }             collapsed = !collapsed;         }     } }

详细说明

核心代码

TextPaint tp = getPaint();                       float width = tp.measureText(text);                 /* 计算行数 */                     //获取显示宽度                     int showWidth = getWidth() - getPaddingRight() - getPaddingLeft();                     int lines = (int) (width / showWidth);                     if (width % showWidth != 0) {                         lines++;                     }                     allLines = (int) (tp.measureText(text + collapsedText) / showWidth);                     if (lines > maxLine) {                         int expect = text.length() / lines;                         int end = 0;                         int lastLineEnd = 0;                         //...+expandedText的宽度,需要在最后一行加入计算                         int expandedTextWidth = (int) tp.measureText(ELLIPSE + expandedText);                         //计算每行显示文本数                         for (int i = 1; i <= maxLine; i++) {                             int tempWidth = 0;                             if (i == maxLine) {                                  tempWidth = expandedTextWidth;                             }                             end += expect;                             if (end > text.length()) {                                 end = text.length();                             }                             if (tp.measureText(text, lastLineEnd, end) > showWidth - tempWidth) {                                 //预期的第一行超过了实际显示的宽度                                 end--;                                 while (tp.measureText(text, lastLineEnd, end) > showWidth - tempWidth) {                                     end--;                                 }                             } else {                                 end++;                                 while (tp.measureText(text, lastLineEnd, end) < showWidth - tempWidth) {                                     end++;                                 }                                 end--;                             }                             lastLineEnd = end;                         }                         SpannableStringBuilder s = new SpannableStringBuilder(text, 0, end)                                 .append(ELLIPSE)                                 .append(expandedText);                         collapsedCs = addClickableSpan(s, expandedText);                         setText(collapsedCs);                          setMovementMethod(LinkMovementMethod.getInstance());                     } else {                         setText(text);                     }

通过TextPaint计算出文本的总宽度,粗略计算出一共需要多少行来显示,判断是否需要收起和全文功能,如果需要,则计算出每行实际展示的文本的宽度(因为通过Layout获取到的只有完全绘制成功后,才能正确获取到),同时在计算的最后一行(也就是超过多少行需要收起的最后一行),需要把"...全文"的宽度加入计算,这样才能计算出正确值,把计算出来的字符数截取出来,加入"...全文",同时针对"全文"本身,添加点击的ClickableSpan,使"全文"具有点击事件,最后设置控件展示的文本为截取的文本+"...全文",如果行数没有超过最大行数,则设置正常显示就ok了,同时保存计算出来的文本,避免再次计算。

点击事件

private CharSequence addClickableSpan(SpannableStringBuilder s, CharSequence trimText) {           s.setSpan(viewMoreSpan, s.length() - trimText.length(), s.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);         return s;     }      private class ReadMoreClickableSpan extends ClickableSpan {         @Override         public void onClick(final View widget) {             if(collapsed){                 SpannableStringBuilder s = new SpannableStringBuilder(text)                         .append(collapsedText);                 setText(addClickableSpan(s,collapsedText));             }else{                 setText(collapsedCs);             }             collapsed = !collapsed;         }     }

通过setSpan设置"全文"的点击事件,同时通过继承ClickableSpan 来实现点击事件,事件中根据当前的状态,判断需要设置什么文本,如果是收起状态,则设置文本显示内容为实际内容+"收起",同时给收起添加点击事件,如果是全文状态,则设置显示的文本为之前计算出来的文本。

自定义属性

private void init(Context context,AttributeSet attrs){           if(attrs != null){             TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CollapsedTextView);             allLines = ta.getInt(R.styleable.CollapsedTextView_trimLines,0);             expandedText = ta.getString(R.styleable.CollapsedTextView_expandedText);             if(TextUtils.isEmpty(expandedText)){                 expandedText = EXPANDEDTEXT;             }             collapsedText = ta.getString(R.styleable.CollapsedTextView_collapsedText);             if(TextUtils.isEmpty(collapsedText)){                 collapsedText = COLLAPSEDTEXT;             }         }      }

这里就很简单了,就是自义定最大多少行,"全文"的文本和"收起"的文本,相信不用多少

最后说两句

最近因为太忙,所以文章也写的有点水,而且总是感觉累,是身体加心累,每天躺床上就不想起床,也不喜欢敲代码,效率自热底下。同时也建议各位同行注意身体,身体才是革命的本钱,同时也要注意放松,不然心一旦累了,就很难调整过来了(对于我来说是这样),敲会代码就起身走动走动,前几天因为一直坐着敲代码,脖子痛的要命,所以适当的休息是必要的,好了,就说这么多吧,你们懂的。同时附上本控件 源码和demo github链接 ,另外同时也欢迎大家吐槽交流(QQ群:123965382)

原文  http://www.raye.wang/2016/07/14/androidzi-ding-yi-kong-jian-zhi-quan-wen-shou-qi-textview-ji-cheng-textviewfa/
正文到此结束
Loading...