ExpandTextView 实现

最近在项目中遇到一个 需要实现 一个 点击可展开 收起 TextView

如下图所示: 



在网上看了一下 相关类库 找到一个还好的


上面的那个 是用LinearLayout 包装 TextView  和 ImageView 组成
然后 在动画的工程中改变  TextView的高度来实现

实现起来页是不错的, 后来又想了想了, 能不能直接用一个 TextView 来实现呢?
后来自己尝试了下, 过来实现了.

先讲一下自己的思路吧:
  1. 在第一次 测量的时候, 计算出,  抽缩时候, 和展开时候的 TexView 文字的高度
  2. 动画的过程中动态的 改变 TextView 高度 或者 改变 TextView maxLine
  3. 通过 setCompoundDrawables(null, null, null, mExpandDrawable); 方法 来实现 设置 textView 底部的的高度
  4. 自己在OnDraw 方法画处  展开收缩 的图片

有一点需要记录的是:
一开始不知道 如何 很好的控制  TextView 底部空白位置: 
mExpandDrawable.setBounds(0, 0, 0, mDrawableHeight);
// 这 TextView 底部的 drawable
setCompoundDrawables(null, null, null, mExpandDrawable);

通过像设置 drawableLeft 那样的, 我们在底部放在一个 draw 但是 设置它的宽度为0 ,  高度就可以我们自己设置了.
这样的画,  就可以只要的控制  TextView 底部空白位置 的高度 和显示与否了

还有一点就是画 底部的 收缩 展开的 图片: 
if (mIsNeedExpand && mDrawable != null) {
    int left = getWidth() - mDrawable.getIntrinsicWidth() - mExpandDrawablePadding;
    int right = left + mDrawable.getIntrinsicWidth();
    int top = getHeight() - mDrawable.getIntrinsicHeight() - mExpandDrawablePadding;
    int bottom = top + mDrawable.getIntrinsicHeight();
    Rect drawableRect = new Rect(left, top, right, bottom);
    mDrawable.setBounds(drawableRect);
    mDrawable.draw(canvas);
    canvas.restore();
}

这里可以 简单的计算 空白的位置, 然后画上相应的drawable:

下面直接来看源码吧: 
public class MyExpandTextView extends TextView implements View.OnClickListener {
    public static final String TAG = "MyExpandTextView";
    public static final int MAX_COLLAPSED_LINES = 4;
    public static final int DEFAULT_ANIM_DURATION = 300;

    public MyExpandTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        init(attrs);
    }

    private Context mContext;

    private int mAnimationDuration;
    private int mMaxCollapsedLines;

    private Drawable mExpandDrawable;
    private Drawable mCollapseDrawable;
    private Drawable mDrawable;

    private int mExpandDrawablePadding;
    private int mDrawableHeight;

    private boolean mIsCollapsed = true;
    private boolean isAnimating = false;

    private ValueAnimator animator;

    private SparseBooleanArray mCollapsedStatus;
    private int mPosition;

    private void init(AttributeSet attrs) {
        TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.ExpandableTextView);
        mMaxCollapsedLines = typedArray.getInt(R.styleable.ExpandableTextView_maxCollapsedLines, MAX_COLLAPSED_LINES);
        mAnimationDuration = typedArray.getInt(R.styleable.ExpandableTextView_animDuration, DEFAULT_ANIM_DURATION);
        mExpandDrawable = typedArray.getDrawable(R.styleable.ExpandableTextView_expandDrawable);
        mCollapseDrawable = typedArray.getDrawable(R.styleable.ExpandableTextView_collapseDrawable);

        mExpandDrawablePadding = 3 * 3;

        if (mExpandDrawable == null) {
            mExpandDrawable = mContext.getResources().getDrawable(R.drawable.ic_expand_small_holo_light);
        }

        if (mCollapseDrawable == null) {
            mCollapseDrawable = mContext.getResources().getDrawable(R.drawable.ic_collapse_small_holo_light);
        }
        typedArray.recycle();

        setOnClickListener(this);

        // 设置 当显示不下时 ,结尾显示  ...
        setEllipsize(TextUtils.TruncateAt.valueOf("END"));

        // 设置 textView 下面的 展开收缩图片的高度
        mDrawableHeight = mExpandDrawable.getMinimumHeight() + mExpandDrawablePadding * 2;

    }

    @Override
    public void onClick(View v) {
        if (!mIsNeedExpand || isAnimating) {
            return;
        }

        if (mIsCollapsed) {
            expandTxt();
        } else {
            collapseTxt();
        }
    }

    public void collapseTxt() {
        LogUtil.d(TAG, "collapseTxt  getHeight = " + getHeight());
        if (isAnimating) {
            return;
        }
        animator = ValueAnimator.ofInt(getHeight(), mCollapsedTextHeight);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                setMaxHeight(value);
            }
        });
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                setMaxLines(mMaxCollapsedLines);
                isAnimating = false;
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                super.onAnimationCancel(animation);
                setMaxLines(mMaxCollapsedLines);
                isAnimating = false;
            }
        });
        animator.setDuration(mAnimationDuration);
        mDrawable = mExpandDrawable;
        animator.start();
        isAnimating = true;
        mIsCollapsed = true;
        if (mCollapsedStatus != null) {
            mCollapsedStatus.put(mPosition, mIsCollapsed);
        }
    }

    public void expandTxt() {
        LogUtil.d(TAG, "expandTxt  getHeight = " + getHeight());
        if (isAnimating) {
            return;
        }
        animator = ValueAnimator.ofInt(getHeight(), mExpandTextHeight);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                setMaxHeight(value);
            }
        });
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                isAnimating = false;
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                super.onAnimationCancel(animation);
                isAnimating = false;
            }
        });
        animator.setDuration(mAnimationDuration);
        mDrawable = mCollapseDrawable;
        animator.start();
        mIsCollapsed = false;
        if (mCollapsedStatus != null) {
            mCollapsedStatus.put(mPosition, mIsCollapsed);
        }
    }

    @Override
    public void setText(CharSequence text, BufferType type) {
        mHaveGetLineCount = false;
        setMaxLines(Integer.MAX_VALUE);
        requestLayout();
        if (animator != null && isAnimating) {
            animator.cancel();
        }
        super.setText(text, type);
    }

    public void setText(CharSequence txt, boolean isCollapsed) {
        LogUtil.w(TAG, "setText");
        mHaveGetLineCount = false;
        mIsCollapsed = isCollapsed;
        setText(txt);
    }

    public void setText(@Nullable CharSequence text, @NonNull SparseBooleanArray collapsedStatus, int position) {
        mCollapsedStatus = collapsedStatus;
        mPosition = position;
        mIsCollapsed = collapsedStatus.get(position, true);
        setText(text, mIsCollapsed);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mIsNeedExpand && mDrawable != null) {
            int left = getWidth() - mDrawable.getIntrinsicWidth() - mExpandDrawablePadding;
            int right = left + mDrawable.getIntrinsicWidth();
            int top = getHeight() - mDrawable.getIntrinsicHeight() - mExpandDrawablePadding;
            int bottom = top + mDrawable.getIntrinsicHeight();
            Rect drawableRect = new Rect(left, top, right, bottom);
            mDrawable.setBounds(drawableRect);
            mDrawable.draw(canvas);
            canvas.restore();
        }

    }

    int mLineCount;
    boolean mHaveGetLineCount = false;
    boolean mIsNeedExpand = true;

    int mExpandTextHeight;
    int mCollapsedTextHeight;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        if (mHaveGetLineCount || getVisibility() == GONE) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            return;
        }
        mHaveGetLineCount = true;
        setMaxLines(Integer.MAX_VALUE);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        mLineCount = getLineCount();
        // 获取展开的Textview 的文字的高度
        mExpandTextHeight = getLayout().getLineTop(getLineCount()) + getCompoundPaddingTop() + getCompoundPaddingBottom();

        if (getLineCount() <= mMaxCollapsedLines) {
            mIsNeedExpand = false;
            setCompoundDrawables(null, null, null, null);
            return;
        }

        // 获取收缩时 的文字的高度
        mCollapsedTextHeight = getLayout().getLineTop(mMaxCollapsedLines) + getCompoundPaddingTop() + getCompoundPaddingBottom();
        LogUtil.w(TAG, "onMeasure mLineCount = " + mLineCount
                        + " , mExpandTextHeight = " + mExpandTextHeight
                        + " , mCollapsedTextHeight = " + mCollapsedTextHeight
        );

        mIsNeedExpand = true;
        mExpandTextHeight = mExpandTextHeight + mDrawableHeight;
        mCollapsedTextHeight = mCollapsedTextHeight + mDrawableHeight;
        LogUtil.e(TAG, "onMeasure"
                        + " , mExpandTextHeight = " + mExpandTextHeight
                        + " , mCollapsedTextHeight = " + mCollapsedTextHeight
                        + " , mDrawableHeight = " + mDrawableHeight
        );

        // 这里需要通过post 放去执行, 才会去重新测量改变了View
        post(new Runnable() {
            @Override
            public void run() {
                mExpandDrawable.setBounds(0, 0, 0, mDrawableHeight);
                // 这 TextView 底部的 drawable
                setCompoundDrawables(null, null, null, mExpandDrawable);

                if (mIsCollapsed) {
                    setMaxLines(mMaxCollapsedLines);
                    mDrawable = mExpandDrawable;
                } else {
                    setMaxLines(Integer.MAX_VALUE);
                    mDrawable = mCollapseDrawable;
                }
            }
        });
    }

    private static int getRealTextViewHeight(@NonNull TextView textView) {
        int textHeight = textView.getLayout().getLineTop(textView.getLineCount());
        int padding = textView.getCompoundPaddingTop() + textView.getCompoundPaddingBottom();
        return textHeight + padding;
    }

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值