如下圖所示:
- 在第一次 測量的時候, 計算出, 抽縮時候, 和展開時候的 TexView 文字的高度
- 動畫的過程中動態的 改變 TextView 高度 或者 改變 TextView maxLine
- 通過 setCompoundDrawables(null, null, null, mExpandDrawable); 方法 來實現 設置 textView 底部的的高度
- 自己在OnDraw 方法畫處 展開收縮 的圖片
mExpandDrawable.setBounds(0, 0, 0, mDrawableHeight);
// 這 TextView 底部的 drawable
setCompoundDrawables(null, null, null, mExpandDrawable);
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();
}
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;
}
}