转载

Android自定义控件之全文收起TextView(控件嵌套法)

前言

因为公司项目需要全文收起的功能,一共有2种UI,所以需要写2个全文收起的控件,之前也是用过一个全文收起的TextView控件,但是因为设计原因,在ListView刷新的时候会闪烁,我估计原因是因为控件本身的设计是需要先让TextView绘制完成,然后获取TextView一共有多少行,再判断是否需要全文收起按钮,如果需要,则吧TextView压缩回最大行数,添加全文按钮,这样就会造成ListView的Item先高后低,所以会发生闪烁,后面我也在网上找了几个,发现和之前的设计都差不多,虽然肯定是有解决了这个问题的控件,但是还是决定自己写了,毕竟找到控件后还需要测试,而现在的项目时间不充分啊(另外欢迎指教如何快速的找到自己需要的控件,有时候在Github上面搜索,都不知道具体该用什么关键字),而且自己写,也是一种锻炼。这里讲述的是布局式的实现,还有一个就直接继承TextView来实现那个会在下一篇文章讲述。

效果图

Android自定义控件之全文收起TextView(控件嵌套法)

实现原理

其实很多全文收起的实现原理应该都差不多,首先外部是一个布局,里面放一个显示正文的TextView控件,设置文本后,判断正文TextView的控件到底有多少行,如果达到了全文收起的行数,则将TextView的高度修改为指定的行数高度,把状态设置为收起状态,并在布局中添加全文收起按钮,点击全文时,则把高度还原为控件本身的高度,把状态位置为全文状态,点击收起时,则把控件高度设置为指定行数的高度,状态设置为收起状态。

代码

package wang.raye.library.widge;  import android.annotation.TargetApi;   import android.content.Context;   import android.content.res.TypedArray;   import android.graphics.Color;   import android.os.Build;   import android.text.TextPaint;   import android.text.TextUtils;   import android.util.AttributeSet;   import android.view.Gravity;   import android.view.View;   import android.view.ViewTreeObserver;   import android.view.animation.Animation;   import android.view.animation.Transformation;   import android.widget.LinearLayout;   import android.widget.TextView;  import wang.raye.library.R;  /**  * 有全文和收起的TextView  * Created by Raye on 2016/6/24.  */ public class MoreTextView extends LinearLayout {       /** TextView的实际高度*/     private int textViewHeight;     /** 默认全文的Text*/     private static final String EXPANDEDTEXT = "全文";     /** 默认收起的text*/     private static final String COLLAPSEDTEXT = "收起";     /** 全文的text*/     private String expandedText ;     /** 收起的text*/     private String collapsedText ;     /** 字体大小*/     private int textSize;     /** 字体颜色*/     private int textColor;     /** 超过多少行出现全文、收起按钮*/     private int trimLines;     /** 显示文本的TextView */     private TextView showTextView;     /** 全文和收起的TextView*/     private TextView collapseTextView;     /** 是否是收起状态,默认收起*/     private boolean collapsed = true;       public MoreTextView(Context context, AttributeSet attrs) {         super(context, attrs);         initView(context,attrs);     }      public MoreTextView(Context context, AttributeSet attrs, int defStyleAttr) {         super(context, attrs, defStyleAttr);         initView(context,attrs);     }      @TargetApi(Build.VERSION_CODES.LOLLIPOP)     public MoreTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {         super(context, attrs, defStyleAttr, defStyleRes);         initView(context,attrs);     }      private void initView(Context context,AttributeSet attrs){         showTextView = new TextView(context);         setOrientation(VERTICAL);         TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MoreTextView);         textColor = typedArray.getColor(R.styleable.MoreTextView_textColor, Color.GRAY);         textSize = typedArray.getDimensionPixelSize(R.styleable.MoreTextView_textSize,14);         expandedText = typedArray.getString(R.styleable.MoreTextView_expandedText);         if(TextUtils.isEmpty(expandedText)){             expandedText = EXPANDEDTEXT;         }         collapsedText = typedArray.getString(R.styleable.MoreTextView_collapsedText);         if(TextUtils.isEmpty(collapsedText)){             collapsedText = COLLAPSEDTEXT;         }         trimLines = typedArray.getInt(R.styleable.MoreTextView_trimLines,0);         typedArray.recycle();         showTextView.setTextSize(textSize);         showTextView.setTextColor(textColor);         addView(showTextView);       }      public void setText(CharSequence text){         globalLayout();         showTextView.setText(text);     }     /**      * 获取控件实际高度,并设置最大行数      */     private void globalLayout() {         showTextView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {             @Override             public void onGlobalLayout() {                 textViewHeight = showTextView.getHeight();                 ViewTreeObserver obs = showTextView.getViewTreeObserver();                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {                     obs.removeOnGlobalLayoutListener(this);                 } else {                     obs.removeGlobalOnLayoutListener(this);                 }                  TextPaint tp = showTextView.getPaint();                 int allWidth = (int) tp.measureText(showTextView.getText().toString());                 //计算总行数                 int allLine = allWidth / showTextView.getWidth();                  if(allWidth % showTextView.getWidth() == 0){                     textViewHeight = showTextView.getLineHeight() * allLine;                 }else{                     allLine ++;                     textViewHeight = showTextView.getLineHeight() * allLine;                 }                  if(trimLines > 0 && trimLines < allLine){                     //需要全文和收起                     if(collapsed) {                         showTextView.setHeight(showTextView.getLineHeight() * trimLines);                     }                      if(collapseTextView == null) {                         //全文和收起的textView                         collapseTextView = new TextView(getContext());                         collapseTextView.setTextSize(textSize);                         collapseTextView.setTextColor(Color.BLUE);                         collapseTextView.setText(expandedText);                         LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,                                 LayoutParams.WRAP_CONTENT, Gravity.RIGHT | Gravity.BOTTOM);                         collapseTextView.setLayoutParams(lp);                         collapseTextView.setOnClickListener(collapseListener);                         addView(collapseTextView);                      }                  }             }         });     }      private OnClickListener collapseListener = new OnClickListener() {         @Override         public void onClick(final View v) {             v.setEnabled(false);             final int startValue = showTextView.getHeight();             final int deltaValue ;              if(collapsed){                 //是放大                 deltaValue = textViewHeight - startValue;              }else{                 deltaValue = showTextView.getLineHeight() * trimLines - startValue;             }             Animation animation = new Animation() {                 protected void applyTransformation(float interpolatedTime, Transformation t) {                     showTextView.setHeight((int) (startValue + deltaValue * interpolatedTime));                 }             };             animation.setDuration(500);             animation.setAnimationListener(new Animation.AnimationListener() {                 @Override                 public void onAnimationStart(Animation animation) {                  }                  @Override                 public void onAnimationEnd(Animation animation) {                     v.setEnabled(true);                     collapsed = !collapsed;                     collapseTextView.setText(collapsed?expandedText:collapsedText);                 }                  @Override                 public void onAnimationRepeat(Animation animation) {                  }             });             showTextView.startAnimation(animation);         }     }; } 

具体分析

初始化控件

private void initView(Context context,AttributeSet attrs){           showTextView = new TextView(context);         setOrientation(VERTICAL);         TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MoreTextView);         textColor = typedArray.getColor(R.styleable.MoreTextView_textColor, Color.GRAY);         textSize = typedArray.getDimensionPixelSize(R.styleable.MoreTextView_textSize,14);         expandedText = typedArray.getString(R.styleable.MoreTextView_expandedText);         if(TextUtils.isEmpty(expandedText)){             expandedText = EXPANDEDTEXT;         }         collapsedText = typedArray.getString(R.styleable.MoreTextView_collapsedText);         if(TextUtils.isEmpty(collapsedText)){             collapsedText = COLLAPSEDTEXT;         }         trimLines = typedArray.getInt(R.styleable.MoreTextView_trimLines,0);         typedArray.recycle();         showTextView.setTextSize(textSize);         showTextView.setTextColor(textColor);         addView(showTextView);       } 

这里主要是获取自定义参数的属性,并且在布局中添加一个显示正文的TextView控件,以及设置控件相关属性

核心代码

/**      * 获取控件实际高度,并设置最大行数      */     private void globalLayout() {         showTextView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {             @Override             public void onGlobalLayout() {                 textViewHeight = showTextView.getHeight();                 ViewTreeObserver obs = showTextView.getViewTreeObserver();                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {                     obs.removeOnGlobalLayoutListener(this);                 } else {                     obs.removeGlobalOnLayoutListener(this);                 }                  TextPaint tp = showTextView.getPaint();                 int allWidth = (int) tp.measureText(showTextView.getText().toString());                 //计算总行数                 int allLine = allWidth / showTextView.getWidth();                  if(allWidth % showTextView.getWidth() == 0){                     textViewHeight = showTextView.getLineHeight() * allLine;                 }else{                     allLine ++;                     textViewHeight = showTextView.getLineHeight() * allLine;                 }                  if(trimLines > 0 && trimLines < allLine){                     //需要全文和收起                     if(collapsed) {                         showTextView.setHeight(showTextView.getLineHeight() * trimLines);                     }                      if(collapseTextView == null) {                         //全文和收起的textView                         collapseTextView = new TextView(getContext());                         collapseTextView.setTextSize(textSize);                         collapseTextView.setTextColor(Color.BLUE);                         collapseTextView.setText(expandedText);                         LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,                                 LayoutParams.WRAP_CONTENT, Gravity.RIGHT | Gravity.BOTTOM);                         collapseTextView.setLayoutParams(lp);                         collapseTextView.setOnClickListener(collapseListener);                         addView(collapseTextView);                      }                  }             }         });     } 

这里主要是在GlobalLayoutListener监听中,获取控件的实际高度,因为第一次GlobalLayoutListener会在onDraw方法前面调用,所以不会造成闪烁,另外由于我之前试过获取一行中有多少个字符,发现TextView只有完全绘制成功后,获取到的每行字符才是正确的,所以我担心没有完成绘制完成后获取的行数也有误差,所以通过TextPaint来计算出文本总宽度,然后根据TextView宽度来计算出行数,最后判断总行数是否达到了需要收起的行数,如果达到了收起的行数,则设置textView的高度为行高*指定行数,因为没有padding等属性,所以不需要考虑,同时判断全文收起的按钮是否为空,为空就初始化控件,并添加到布局

点击事件

private OnClickListener collapseListener = new OnClickListener() {           @Override         public void onClick(final View v) {             v.setEnabled(false);             final int startValue = showTextView.getHeight();             final int deltaValue ;              if(collapsed){                 //是放大                 deltaValue = textViewHeight - startValue;              }else{                 deltaValue = showTextView.getLineHeight() * trimLines - startValue;             }             Animation animation = new Animation() {                 protected void applyTransformation(float interpolatedTime, Transformation t) {                     showTextView.setHeight((int) (startValue + deltaValue * interpolatedTime));                 }             };             animation.setDuration(500);             animation.setAnimationListener(new Animation.AnimationListener() {                 @Override                 public void onAnimationStart(Animation animation) {                  }                  @Override                 public void onAnimationEnd(Animation animation) {                     v.setEnabled(true);                     collapsed = !collapsed;                     collapseTextView.setText(collapsed?expandedText:collapsedText);                 }                  @Override                 public void onAnimationRepeat(Animation animation) {                  }             });             showTextView.startAnimation(animation);         }     }; 

这里是全文收起按钮的点击事件,获取控件目前的高度,同时判断目前的状态,根据状态判断是收起还是展开,获取应该添加的高度(收起的,高度是负数),同时设置动画,并启动动画, 动画过程中设置正文的高度。这样一个全文收起的TextView就实现了。

结语

当然这个控件是非常简陋的,而且还有一两个bug,大家可以猜一下到底是啥问题。另外,我想知道就是到底TextView绘制的时候能不能获取到正确的行数,以及为啥获取每行字数的时候会有误差,希望知道的解答一下,当然我自己也会查询资料了解,同时附上本控件源码和 demo github链接

原文  http://www.raye.wang/2016/06/29/androidzi-ding-yi-kong-jian-zhi-quan-wen-shou-qi-textview-kong-jian-qian-tao-fa/
正文到此结束
Loading...