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

前言

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

                int allLine = showTextView.getLineCount();

                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);
        }
    };
}

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

核心代码
/**
     * 获取控件实际高度,并设置最大行数
     */
    private void globalLayout() {
        showTextView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                ViewTreeObserver obs = showTextView.getViewTreeObserver();
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    obs.removeOnGlobalLayoutListener(this);
                } else {
                    obs.removeGlobalOnLayoutListener(this);
                }

                int allLine = showTextView.getLineCount();

                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的高度为行高*指定行数,因为没有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绘制的时候能不能获取到正确的行数,因为我测试过程中发现在onGlobalLayout中获取的每行文字数量会多次变化,但是最后一次获取到的是正确的,所以想知道为啥获取每行字数的时候会有误差,希望知道的解答一下,当然我自己也会查询资料了解,同时附上本控件源码和demo github链接

标签:Android, 自定义控件
下一篇:   Android自定义控件之全文收起TextView(继承TextView法)
上一篇:   自定义控件之重写ScrollView实现图片下拉放大

Copyright © 2017 Raye Blog

如果我的文章对你有帮助,或许可以打赏一下呀!

支付宝
微信