参考开源项目ExpendableTextView,原项目使用LinearLayout包装TextView和Button的做法,需要保证id的一致,,文本内容需要在代码中设置,操作较为复杂,故修改继承自TextView,保留TextView特性,简化操作。

//TODO 转为gif

效果图

1.定义一些属性

  • animDuration textview展开的动效时长
  • animAlphaStart 透明度渐变初始值
  • expandDrawable 指示可以展开的小箭头图标
  • collapseDrawable 指示可以折叠的小箭头图标
  • maxCollapsedLines 开始折叠的起始行数

    1
    2
    3
    4
    5
    6
    7
    <declare-styleable name="ExpandableTextView">
    <attr name="maxCollapsedLines" format="integer"/>
    <attr name="animDuration" format="integer"/>
    <attr name="animAlphaStart" format="float"/>
    <attr name="expandDrawable" format="reference"/>
    <attr name="collapseDrawable" format="reference"/>
    </declare-styleable>

2.设置点击事件

点击时,行数不足,不响应点击事件,行数足够,开始展开/折叠 动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@Override
public void onClick(View v) {
if(!needCollapse){
return;//行数不足,不响应点击事件
}
mCollapsed = !mCollapsed;
if (mCollapsedStatus != null) {
mCollapsedStatus.put(mPosition, mCollapsed);
}
// 标志是否正在进行动画,动画是不重复进行
mAnimating = true;
Animation animation;
if (mCollapsed) {
animation = new ExpandCollapseAnimation(this, getHeight(), mCollapsedHeight);
} else {
animation = new ExpandCollapseAnimation(this, getHeight(),mTextHeightWithMaxLines);
}
animation.setFillAfter(true);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
applyAlphaAnimation(ExpandTextView.this, mAnimAlphaStart);
}
@Override
public void onAnimationEnd(Animation animation) {
// clear animation here to avoid repeated applyTransformation() calls
clearAnimation();
// clear the animation flag
mAnimating = false;
// notify the listener
if (mListener != null) {
mListener.onExpandStateChanged(ExpandTextView.this, !mCollapsed);
}
}
@Override
public void onAnimationRepeat(Animation animation) { }
});
clearAnimation();
startAnimation(animation);
}

3.重写onMeasure()方法

  • 先取消对TextView显示行数的限制,调用super.onMeasure()方法计算出TextView的总高度;
  • 然后判断TextView的行数满不满足折叠条件,满足时致needCollapse为true;
  • 使用setMaxLines()方法设置TextView能显示的最大行数,并重新使用super.onMeasure()方法计算出此时TextView的高度,以方便做动效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(getVisibility()==GONE){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
setMaxLines(Integer.MAX_VALUE);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
System.out.println("行数:"+getLineCount());
if (getLineCount() <= mMaxCollapsedLines) {
//不需要折叠
needCollapse = false;
return;
}
needCollapse = true;
mTextHeightWithMaxLines = getRealTextViewHeight(this);
if (mCollapsed) {
setMaxLines(mMaxCollapsedLines);
}
//设置完成后重新测量
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mCollapsed) {
mCollapsedHeight = getMeasuredHeight();
}
}

3.重写onDraw()方法,在合适的位置绘制展开/折叠 箭头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(needCollapse){
if(mCollapsed){
int right = getRight()-getTotalPaddingRight()-sp2dp(16);
int left = right-mExpandDrawable.getIntrinsicWidth();
int bottom = getBottom() - getTotalPaddingBottom()-sp2dp(16);
int top = bottom - mExpandDrawable.getIntrinsicHeight();
canvas.translate(left,top);
mExpandDrawable.setBounds(0,0,mExpandDrawable.getIntrinsicWidth(),mExpandDrawable.getIntrinsicHeight());
mExpandDrawable.draw(canvas);
}else{
int right = getRight()-getTotalPaddingRight()-sp2dp(16);
int left = right-mCollapseDrawable.getIntrinsicWidth();
int bottom = getBottom() - getTotalPaddingBottom()-sp2dp(16);
int top = bottom - mCollapseDrawable.getIntrinsicHeight();
canvas.translate(left,top);
mCollapseDrawable.setBounds(0,0,mCollapseDrawable.getIntrinsicWidth(),mCollapseDrawable.getIntrinsicHeight());
mCollapseDrawable.draw(canvas);
}
}
}

4.动效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class ExpandCollapseAnimation extends Animation {
private final View mTargetView;
private final int mStartHeight;
private final int mEndHeight;
public ExpandCollapseAnimation(View view, int startHeight, int endHeight) {
mTargetView = view;
mStartHeight = startHeight;
mEndHeight = endHeight;
setDuration(mAnimationDuration);
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final int newHeight = (int)((mEndHeight - mStartHeight) * interpolatedTime + mStartHeight);
setMaxHeight(newHeight);
if (Float.compare(mAnimAlphaStart, 1.0f) != 0) {
applyAlphaAnimation(ExpandTextView.this, mAnimAlphaStart + interpolatedTime * (1.0f - mAnimAlphaStart));
}
mTargetView.getLayoutParams().height = newHeight;
mTargetView.requestLayout();
}
@Override
public void initialize( int width, int height, int parentWidth, int parentHeight ) {
super.initialize(width, height, parentWidth, parentHeight);
}
@Override
public boolean willChangeBounds( ) {
return true;
}
};

5.当前存在的bug

  • onDraw()方法中,小箭头的绘制位置的计算还有问题,TextView的padding值的计算需要