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

http://www.tuicool.com/articles/NNVZzuU

http://www.tuicool.com/articles/NNVZzuU

http://www.tuicool.com/articles/NNVZzuU

http://www.tuicool.com/articles/NNVZzuU




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

时间 2016-06-29 23:04:20 Raye Blog

原文

  http://www.raye.wang/2016/06/29/androidzi-ding-yi-kong-jian-zhi-quan-wen-shou-qi-textview-kong-jian-qian-tao-fa/

主题 TextView

前言

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值