我们在开发中,不免要用到可扩展的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() { @Override public 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); } @Override protected 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(); } @Override public void initialize(int width, int height, int parentWidth, int parentHeight) { super.initialize(width, height, parentWidth, parentHeight); } @Override public 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); } } @Override public 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() { @Override public void onAnimationStart(Animation animation) { ExpandableTextView.applyAlphaAnimation(ExpandableTextView.this.mTv, ExpandableTextView.this.mAnimAlphaStart); } @Override public 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); } } @Override public void onAnimationRepeat(Animation animation) { } }); this.clearAnimation(); this.startAnimation(animation); } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return this.mAnimating; } @SuppressLint("MissingSuperCall") @Override protected 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() { @Override public 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(); } @Nullable public 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); } @Override protected 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(); } @Override public void initialize(int width, int height, int parentWidth, int parentHeight) { super.initialize(width, height, parentWidth, parentHeight); } @Override public 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>