我们在开发中,不免要用到可扩展的textView,今天我们就自定义一个:
第一步:初始化控件:
TypedArray typedArray = this.getContext().obtainStyledAttributes(attrs, R.styleable.ExpandableTextView);
this.mMaxCollapsedLines = typedArray.getInt(R.styleable.ExpandableTextView_maxCollapsedLines, 8);
this.mAnimationDuration = typedArray.getInt(R.styleable.ExpandableTextView_animDuration, 300);
this.mAnimAlphaStart = typedArray.getFloat(R.styleable.ExpandableTextView_animAlphaStart, 0.7F);
this.mExpandDrawable = typedArray.getDrawable(R.styleable.ExpandableTextView_expandDrawable);
this.mCollapseDrawable = typedArray.getDrawable(R.styleable.ExpandableTextView_collapseDrawable);
if (this.mExpandDrawable == null) {this.mExpandDrawable = getDrawable(this.getContext(), R.drawable.icon_live_home_arrow);
}if (this.mCollapseDrawable == null) {this.mCollapseDrawable = getDrawable(this.getContext(), R.drawable.icon_live_home_below);
}typedArray.recycle();
this.setVisibility(GONE);
第二步:找对应的控件
this.mTv = (TextView) this.findViewById(R.id.expandable_text); this.mTv.setOnClickListener(this); this.mButton = (ImageButton) this.findViewById(R.id.expand_collapse); this.mButton.setImageDrawable(this.mCollapsed ? this.mExpandDrawable : this.mCollapseDrawable); this.mButton.setOnClickListener(this);
第三步:调用onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {if (this.mRelayout && this.getVisibility() != GONE) {this.mRelayout = false;this.mButton.setVisibility(GONE);this.mTv.setMaxLines(Integer.MAX_VALUE);super.onMeasure(widthMeasureSpec, heightMeasureSpec);if (this.mTv.getLineCount() > this.mMaxCollapsedLines) {this.mTextHeightWithMaxLines = getRealTextViewHeight(this.mTv);if (this.mCollapsed) {this.mTv.setMaxLines(this.mMaxCollapsedLines);}this.mButton.setVisibility(VISIBLE);super.onMeasure(widthMeasureSpec, heightMeasureSpec);if (this.mCollapsed) {this.mTv.post(new Runnable() {@Overridepublic void run() {ExpandableTextView.this.mMarginBetweenTxtAndBottom = ExpandableTextView.this.getHeight() - ExpandableTextView.this.mTv.getHeight();}});this.mCollapsedHeight = this.getMeasuredHeight();}}} else {super.onMeasure(widthMeasureSpec, heightMeasureSpec);}
}
第四步:添加动画:
class ExpandCollapseAnimation extends Animation {private final View mTargetView;private final int mStartHeight;private final int mEndHeight;public ExpandCollapseAnimation(View view, int startHeight, int endHeight) {this.mTargetView = view;this.mStartHeight = startHeight;this.mEndHeight = endHeight;this.setDuration((long) ExpandableTextView.this.mAnimationDuration);}@Overrideprotected void applyTransformation(float interpolatedTime, Transformation t) {int newHeight = (int) ((float) (this.mEndHeight - this.mStartHeight) * interpolatedTime + (float) this.mStartHeight);ExpandableTextView.this.mTv.setMaxHeight(newHeight - ExpandableTextView.this.mMarginBetweenTxtAndBottom);if (Float.compare(ExpandableTextView.this.mAnimAlphaStart, 1.0F) != 0) {ExpandableTextView.applyAlphaAnimation(ExpandableTextView.this.mTv, ExpandableTextView.this.mAnimAlphaStart + interpolatedTime * (1.0F - ExpandableTextView.this.mAnimAlphaStart));}this.mTargetView.getLayoutParams().height = newHeight;this.mTargetView.requestLayout();}@Overridepublic void initialize(int width, int height, int parentWidth, int parentHeight) {super.initialize(width, height, parentWidth, parentHeight);}@Overridepublic boolean willChangeBounds() {return true;}
}
下面附上全部的代码以供参考:
public class ExpandableTextView extends RelativeLayout implements View.OnClickListener {private static final String TAG = ExpandableTextView.class.getSimpleName();private static final int MAX_COLLAPSED_LINES = 8;private static final int DEFAULT_ANIM_DURATION = 300;private static final float DEFAULT_ANIM_ALPHA_START = 0.7F;protected TextView mTv;protected ImageButton mButton;private boolean mRelayout;private boolean mCollapsed;private int mCollapsedHeight;private int mTextHeightWithMaxLines;private int mMaxCollapsedLines;private int mMarginBetweenTxtAndBottom;private Drawable mExpandDrawable;private Drawable mCollapseDrawable;private int mAnimationDuration;private float mAnimAlphaStart;private boolean mAnimating;private ExpandableTextView.OnExpandStateChangeListener mListener;private SparseBooleanArray mCollapsedStatus;private int mPosition;public ExpandableTextView(Context context) {this(context, (AttributeSet) null);}public ExpandableTextView(Context context, AttributeSet attrs) {super(context, attrs);this.mCollapsed = true;this.init(attrs);}@TargetApi(11)public ExpandableTextView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);this.mCollapsed = true;this.init(attrs);}public void setOrientation(int orientation) {if (0 == orientation) {throw new IllegalArgumentException("ExpandableTextView only supports Vertical Orientation.");} else {
// super.setOrientation(orientation);}}@Overridepublic void onClick(View view) {if (this.mButton.getVisibility() == VISIBLE) {this.mCollapsed = !this.mCollapsed;this.mButton.setImageDrawable(this.mCollapsed ? this.mExpandDrawable : this.mCollapseDrawable);if (this.mCollapsedStatus != null) {this.mCollapsedStatus.put(this.mPosition, this.mCollapsed);}this.mAnimating = true;ExpandableTextView.ExpandCollapseAnimation animation;if (this.mCollapsed) {animation = new ExpandableTextView.ExpandCollapseAnimation(this, this.getHeight(), this.mCollapsedHeight);} else {animation = new ExpandableTextView.ExpandCollapseAnimation(this, this.getHeight(), this.getHeight() + this.mTextHeightWithMaxLines - this.mTv.getHeight());}animation.setFillAfter(true);animation.setAnimationListener(new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {ExpandableTextView.applyAlphaAnimation(ExpandableTextView.this.mTv, ExpandableTextView.this.mAnimAlphaStart);}@Overridepublic void onAnimationEnd(Animation animation) {ExpandableTextView.this.clearAnimation();ExpandableTextView.this.mAnimating = false;if (ExpandableTextView.this.mListener != null) {ExpandableTextView.this.mListener.onExpandStateChanged(ExpandableTextView.this.mTv, !ExpandableTextView.this.mCollapsed);}}@Overridepublic void onAnimationRepeat(Animation animation) {}});this.clearAnimation();this.startAnimation(animation);}}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {return this.mAnimating;}@SuppressLint("MissingSuperCall")@Overrideprotected void onFinishInflate() {this.findViews();}@Override@SuppressLint("WrongConstant")protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {if (this.mRelayout && this.getVisibility() != GONE) {this.mRelayout = false;this.mButton.setVisibility(GONE);this.mTv.setMaxLines(Integer.MAX_VALUE);super.onMeasure(widthMeasureSpec, heightMeasureSpec);if (this.mTv.getLineCount() > this.mMaxCollapsedLines) {this.mTextHeightWithMaxLines = getRealTextViewHeight(this.mTv);if (this.mCollapsed) {this.mTv.setMaxLines(this.mMaxCollapsedLines);}this.mButton.setVisibility(VISIBLE);super.onMeasure(widthMeasureSpec, heightMeasureSpec);if (this.mCollapsed) {this.mTv.post(new Runnable() {@Overridepublic void run() {ExpandableTextView.this.mMarginBetweenTxtAndBottom = ExpandableTextView.this.getHeight() - ExpandableTextView.this.mTv.getHeight();}});this.mCollapsedHeight = this.getMeasuredHeight();}}} else {super.onMeasure(widthMeasureSpec, heightMeasureSpec);}}public void setOnExpandStateChangeListener(@Nullable ExpandableTextView.OnExpandStateChangeListener listener) {this.mListener = listener;}@SuppressLint("WrongConstant")public void setText(@Nullable CharSequence text) {this.mRelayout = true;this.mTv.setText(text);this.setVisibility(TextUtils.isEmpty(text) ? GONE : VISIBLE);}public void setText(@Nullable CharSequence text, @NonNull SparseBooleanArray collapsedStatus, int position) {this.mCollapsedStatus = collapsedStatus;this.mPosition = position;boolean isCollapsed = collapsedStatus.get(position, true);this.clearAnimation();this.mCollapsed = isCollapsed;this.mButton.setImageDrawable(this.mCollapsed ? this.mExpandDrawable : this.mCollapseDrawable);this.setText(text);this.getLayoutParams().height = -2;this.requestLayout();}@Nullablepublic CharSequence getText() {return (CharSequence) (this.mTv == null ? "" : this.mTv.getText());}@SuppressLint("WrongConstant")private void init(AttributeSet attrs) {TypedArray typedArray = this.getContext().obtainStyledAttributes(attrs, R.styleable.ExpandableTextView);this.mMaxCollapsedLines = typedArray.getInt(R.styleable.ExpandableTextView_maxCollapsedLines, 8);this.mAnimationDuration = typedArray.getInt(R.styleable.ExpandableTextView_animDuration, 300);this.mAnimAlphaStart = typedArray.getFloat(R.styleable.ExpandableTextView_animAlphaStart, 0.7F);this.mExpandDrawable = typedArray.getDrawable(R.styleable.ExpandableTextView_expandDrawable);this.mCollapseDrawable = typedArray.getDrawable(R.styleable.ExpandableTextView_collapseDrawable);if (this.mExpandDrawable == null) {this.mExpandDrawable = getDrawable(this.getContext(), R.drawable.icon_live_home_arrow);}if (this.mCollapseDrawable == null) {this.mCollapseDrawable = getDrawable(this.getContext(), R.drawable.icon_live_home_below);}typedArray.recycle();this.setVisibility(GONE);}private void findViews() {this.mTv = (TextView) this.findViewById(R.id.expandable_text);this.mTv.setOnClickListener(this);this.mButton = (ImageButton) this.findViewById(R.id.expand_collapse);this.mButton.setImageDrawable(this.mCollapsed ? this.mExpandDrawable : this.mCollapseDrawable);this.mButton.setOnClickListener(this);}private static boolean isPostHoneycomb() {return Build.VERSION.SDK_INT >= 11;}private static boolean isPostLolipop() {return Build.VERSION.SDK_INT >= 21;}@TargetApi(11)private static void applyAlphaAnimation(View view, float alpha) {if (isPostHoneycomb()) {view.setAlpha(alpha);} else {AlphaAnimation alphaAnimation = new AlphaAnimation(alpha, alpha);alphaAnimation.setDuration(0L);alphaAnimation.setFillAfter(true);view.startAnimation(alphaAnimation);}}@TargetApi(21)private static Drawable getDrawable(@NonNull Context context, int resId) {Resources resources = context.getResources();return isPostLolipop() ? resources.getDrawable(resId, context.getTheme()) : resources.getDrawable(resId);}private static int getRealTextViewHeight(@NonNull TextView textView) {int textHeight = textView.getLayout().getLineTop(textView.getLineCount());int padding = textView.getCompoundPaddingTop() + textView.getCompoundPaddingBottom();return textHeight + padding;}public interface OnExpandStateChangeListener {void onExpandStateChanged(TextView var1, boolean var2);}class ExpandCollapseAnimation extends Animation {private final View mTargetView;private final int mStartHeight;private final int mEndHeight;public ExpandCollapseAnimation(View view, int startHeight, int endHeight) {this.mTargetView = view;this.mStartHeight = startHeight;this.mEndHeight = endHeight;this.setDuration((long) ExpandableTextView.this.mAnimationDuration);}@Overrideprotected void applyTransformation(float interpolatedTime, Transformation t) {int newHeight = (int) ((float) (this.mEndHeight - this.mStartHeight) * interpolatedTime + (float) this.mStartHeight);ExpandableTextView.this.mTv.setMaxHeight(newHeight - ExpandableTextView.this.mMarginBetweenTxtAndBottom);if (Float.compare(ExpandableTextView.this.mAnimAlphaStart, 1.0F) != 0) {ExpandableTextView.applyAlphaAnimation(ExpandableTextView.this.mTv, ExpandableTextView.this.mAnimAlphaStart + interpolatedTime * (1.0F - ExpandableTextView.this.mAnimAlphaStart));}this.mTargetView.getLayoutParams().height = newHeight;this.mTargetView.requestLayout();}@Overridepublic void initialize(int width, int height, int parentWidth, int parentHeight) {super.initialize(width, height, parentWidth, parentHeight);}@Overridepublic boolean willChangeBounds() {return true;}}
}
注意:其中用到的资源如下:
<declare-styleable name="ExpandableTextView"><attr name="maxCollapsedLines" format="integer"/><attr name="animDuration" format="integer"/><attr name="contentTextSize" format="dimension"/><attr name="contentTextColor" format="color"/><attr name="contentLineSpacingMultiplier" format="float"/><attr name="expandDrawable" format="reference"/><attr name="expandText" format="string"/><attr name="collapseDrawable" format="reference"/><attr name="collapseText" format="string"/><attr name="expandCollapseTextColor" format="color"/><attr name="animAlphaStart" format="float"/><attr name="DrawableAndTextGravity"><enum name="left" value="0"/><enum name="center" value="1"/><enum name="right" value="2"/></attr> </declare-styleable>