最近在项目中遇到一个 需要实现 一个 点击可展开 收起 TextView
如下图所示:
在网上看了一下 相关类库 找到一个还好的
上面的那个 是用LinearLayout 包装 TextView 和 ImageView 组成
然后 在动画的工程中改变 TextView的高度来实现
实现起来页是不错的, 后来又想了想了, 能不能直接用一个 TextView 来实现呢?
后来自己尝试了下, 过来实现了.
先讲一下自己的思路吧:
- 在第一次 测量的时候, 计算出, 抽缩时候, 和展开时候的 TexView 文字的高度
- 动画的过程中动态的 改变 TextView 高度 或者 改变 TextView maxLine
- 通过 setCompoundDrawables(null, null, null, mExpandDrawable); 方法 来实现 设置 textView 底部的的高度
- 自己在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;
}
}