[置顶] Android自定义View系列之可伸缩的TextView

在写博客之前,告诉大家一个消息,我开通了自己的微信公众账号,如果你喜欢我的文章,希望关注我的微信公众号,我会定期与大家分享最新的博客文章,以及移动互联网最新动态。
我的微信公众号:yuanzeyao_android
二维码:
[置顶] Android自定义View系列之可伸缩的TextView_第1张图片

经常访问我博客的同学应该注意到了我的博客大部分是关于分析Android系统原理的,很少涉及应用层的知识,但是想要开发一个用户欢迎的App,没有一个炫酷的UI肯定是不行的,就好比一个人想成为武林高手仅仅修炼内功是不行的,必须内功和外功同时修炼才能成为真正的武林高手。很多人在开发Android的App过程中有这样的感觉,我不用学习Android系统原理照样可以进行Android开发,而且可以做出非常炫酷的效果,确实是这样的 ,因为对于一款App,它的大部分需求无非就是请求数据然后展示数据,至于一些炫酷的UI控件网上有很多开源的,所以我们不用懂Android系统的原理同样可以进行App开发,相信你自己也发现了,长期做这样的工作,你的技术是永远得不到提升的,当你使用别人开源的炫酷的控件时,你并不知道别人是怎么实现的,即使你有兴趣看别人的实现方法,如果你不懂Android系统原理,估计你也看不懂别人为什么要这么做。所以从今天起的半年时间,我会将学习重点放在应用层的开发,主要是自定义View和炫酷的动画的学习上。

开始今天的主题:可伸缩的TextView,先来看看效果图

[置顶] Android自定义View系列之可伸缩的TextView_第2张图片

该自定义View具备如下特性:

  1. 当显示的内容没有2行(可以通过属性配置)时,和普通的TextView一样
  2. 当显示的内容大于2行时,仅仅显示两行,并且没有显示完的内容使用省略号代替,并显示向下箭头表示还有内容没有显示全
  3. 当用户点击TextView时,内容全部展开,当用户再次点击时,TextView又变为压缩模式

下面我们开始代码实现,正如上面所说,此View支持自定义属性,所以我们先要定义attrs.xml文件,如何编写attrs.xml文件网上有很多教程,我这里就不在说了。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ExpandTextView">
        <attr name="textcolor" format="color"></attr>
        <attr name="textsize" format="dimension"></attr>
        <attr name="icon" format="reference"></attr>
        <attr name="lines" format="integer"></attr>
        <attr name="text" format="string"></attr>
    </declare-styleable>
</resources>

这几个属性的意义如下:

  • textcolor : 字体颜色
  • textsize :字体大小
  • icon :标识内容没有显示完成的图片,默认是向下箭头
  • lines:压缩状态下显示的行数
  • text :显示的内容

    如果需要,你可以自己定义更多的属性。下面进入代码部分

/** * com.gavin.expandable.textview.ExpandTextView * @author yuanzeyao <br/> * create at 2015年10月10日 下午5:41:12 */
public class ExpandTextView extends LinearLayout implements OnClickListener{
  public static final String TAG="ExpandTextView";

  public static final int DEFAULT_TEXT_COLOR=0XFF000000;
  public static final int DEFAULT_LINE_NUM=3;
  public static final int DEFAULT_TEXT_SIZE=12;
  public static final int DEFAULT_MARGIN_TOP=10;
  private TextView mTextView;
  private ImageView mImageView;
  /**TextView字体的颜色*/
  private int textColor;
  /**TextView字体的大小*/
  private float textSize;
  /**TextView默认显示行数*/
  private int maxLine;
  /**TextView的文本内容*/
  private String text;
  /**ImageView使用的图片*/
  private Drawable mIcon;
  /**TextView所有的内容暂用的行数*/
  private int contentLine=0;
  /**是否展开*/
  private boolean isExpand=false;

  public ExpandTextView(Context context) {
    super(context);
    init(null,0);
  }

  public ExpandTextView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(attrs,0);
  }

  public ExpandTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init(attrs,defStyleAttr);
  }

  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  public ExpandTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    init(attrs,defStyleAttr);
  }

  private void init(AttributeSet attrs,int defStyleAttr){
    setOrientation(VERTICAL);
    setGravity(Gravity.CENTER_HORIZONTAL);
    TypedArray array=this.getContext().obtainStyledAttributes(attrs, R.styleable.ExpandTextView,defStyleAttr,0);
    textColor=array.getColor(R.styleable.ExpandTextView_textcolor,DEFAULT_TEXT_COLOR);
    textSize=array.getDimensionPixelOffset(R.styleable.ExpandTextView_textsize,dp2px(DEFAULT_TEXT_SIZE));
    maxLine=array.getInt(R.styleable.ExpandTextView_lines,DEFAULT_LINE_NUM);
    mIcon=array.getDrawable(R.styleable.ExpandTextView_icon);
    text=array.getString(R.styleable.ExpandTextView_text);
    if(mIcon==null){
      mIcon=this.getContext().getResources().getDrawable(R.drawable.text_expand);
    }
    array.recycle();
    initViewAttribute();
  }

  private void initViewAttribute(){
    mTextView=new TextView(this.getContext());
    //设置属性
    mTextView.setText(text);
    mTextView.setTextColor(textColor);
    mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);


    int textHeight=mTextView.getLineHeight()*maxLine;
    LayoutParams mParams_txt=new LayoutParams(LayoutParams.MATCH_PARENT,textHeight);
    addView(mTextView,mParams_txt);

    mImageView=new ImageView(this.getContext());
    mImageView.setImageDrawable(mIcon);
    LayoutParams mParams_img=new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT);
    mParams_img.topMargin=dp2px(DEFAULT_MARGIN_TOP);
    addView(mImageView,mParams_img);
    mImageView.setOnClickListener(this);
    this.setOnClickListener(this);

    this.post(new Runnable() {
      @Override
      public void run() {
        contentLine=mTextView.getLineCount();
        if(contentLine<=maxLine){
          mImageView.setVisibility(View.GONE);
          LayoutParams mParam=(LayoutParams) mTextView.getLayoutParams();
          mParam.height=LayoutParams.WRAP_CONTENT;
          mTextView.setLayoutParams(mParam);
          ExpandTextView.this.setOnClickListener(null);
        }else{
          //默认是非展开模式,那么设置最大行为maxLine
          mTextView.setMaxLines(maxLine);
          mTextView.setEllipsize(TruncateAt.END);
          mImageView.setVisibility(View.VISIBLE);
        }
      }
    });
  }

  /** * dp单位和px单位的转化 * @param dp * @return */
  private int dp2px(int dp){
    return (int)(this.getResources().getDisplayMetrics().density*dp+0.5);
  }

  @Override
  public void onClick(View v) {
    if(v==mImageView|| v==this){
      flexibleHeight();
    }
  }

  /** * 对TextView进行伸缩处理 */
  private void flexibleHeight() {
    isExpand=!isExpand;
    int textHeight=0;
    float startDegree=0.0f;
    float endDegree=180.0f;
    if(isExpand){
      //如果是展开模式,那么取消最大行为maxLine的限制
      textHeight=contentLine*mTextView.getLineHeight();
      mTextView.setMaxLines(contentLine);
    }else{
      textHeight=mTextView.getLineHeight()*maxLine;
      endDegree=0.0f;
      startDegree=180.0f;
    }
    final LayoutParams mParam=(LayoutParams) mTextView.getLayoutParams();
    //TextView的平移动画
    ValueAnimator animator_textView= ValueAnimator.ofInt(mTextView.getHeight(),textHeight);
    animator_textView.addUpdateListener(new AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
        mParam.height=(Integer)animation.getAnimatedValue();
        mTextView.setLayoutParams(mParam);
      }
    });
    //imageView的旋转动画
    ObjectAnimator animator_img=ObjectAnimator.ofFloat(mImageView, "rotation", startDegree, endDegree);

    AnimatorSet mAnimatorSets=new AnimatorSet();
    mAnimatorSets.setDuration(500);
    mAnimatorSets.play(animator_img).with(animator_textView);
    mAnimatorSets.start();
    mAnimatorSets.addListener(new AnimatorListenerAdapter(){
      @Override
      public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
        //动画结束之后,如果是非展开模式,则设置最大行数为maxLine
        if(!isExpand){
          mTextView.setMaxLines(maxLine);
        }
      }

    });
  }
}

其实代码并不复杂,这里就是继承LinearLayout 并在里面放置一个普通的TextViewImageView,并在构造函数中对自定义的属性进行解析然后分别赋值给TextViewImageView的相应属性。

这里有一个细节需要注意,调用TextViewgetLineCount() 方法获取TextView的行数是放置在View的post方法中执行,为什么要这么做呢,因为如果在该View的构造方法中直接调用getLineCount() 方法,必然返回0,因为这个时候View 的 measure,layout,draw过程都没有执行,必然无法获取行数。

由于没有显示完的内容需要使用省略号表示,所以这里需要对TextView 进行一些属性的设置:

 mTextView.setMaxLines(maxLine);
 mTextView.setEllipsize(TruncateAt.END);

写到这里,估计有些同学就要纳闷了,这里都设置最大行数的属性了,为何还要通过getLineCount() 方法获取TextView 的内容 的行数?你能注意到这里,我不得不给你一个赞…,我现在给你解释一下我为什么这么做,为了让TextView 将为显示完的内容变为省略号,不得不设置上面连个属性,但是设置了两个属性之后,我就无法知道TextView 展开后的高度是多少,所以我是先调用了getLineCount() 之后,再设置上面属性的。然后在用户点击的时候将最大行数设置成contentLine 并通过动画不断设置TextView 的高度,从maxLine 行的高度变为contentLine 行的高度。

最后不得不说一下这里使用到的动画了,这里我使用的是属性动画,属性动画是Android 3.0之后引入的一种动画,较之前Android提供的两种动画,属性动画功能强大得多,网上也有很多关于属性动画的教程,不熟悉的同学可以在网上找找资料,想要学会自己定义炫酷的View,掌握各种动画的使用时必不可少的。

代码下载地址

你可能感兴趣的:(android,移动互联网)