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

}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章