可伸缩的TextView

来源:互联网 发布:北京学java最好的学校 编辑:程序博客网 时间:2024/04/29 21:41

GIF录制效果不是太好:

这里写图片描述

原理:
1,获取textview有多少行,根据行数显示箭头图标。
2,获取只有一行时(自己设定的行数),textview的高度,并且记录。
3,点击textview或者图标执行动画,动态改变textview的高度。

ExpandableTextViewHorizontal

package com.ms.square.android.expandabletextview;public class ExpandableTextViewHorizontal extends LinearLayout implements View.OnClickListener {    private static final String TAG = ExpandableTextViewHorizontal.class.getSimpleName();    /* The default number of lines */    private static final int MAX_COLLAPSED_LINES = 8;    /* The default animation duration */    private static final int DEFAULT_ANIM_DURATION = 300;    /* The default alpha value when the animation starts */    private static final float DEFAULT_ANIM_ALPHA_START = 0.7f;    protected TextView mTv;    protected ImageButton mButton; // Button to expand/collapse    private boolean mRelayout = true;    /**     * true 折叠,箭头是向下的,false 展开,箭头是向上的<br/>     * 同时也影响xml布局界面<br/>     * 默认必须为true     */    private boolean mCollapsed = true; // Show short version as default.    private int mCollapsedHeight;    /**     * textview整体高度     */    private int mTextHeightWithMaxLines;    private int mMaxCollapsedLines;    private int mMarginBetweenTxtAndBottom;    /**     * 箭头向下 展开     */    private Drawable mExpandDrawable;    /**     * 箭头向上 折叠     */    private Drawable mCollapseDrawable;    private int mAnimationDuration;    private float mAnimAlphaStart;    private boolean mAnimating;    /* Listener for callback */    private OnExpandStateChangeListener mListener;    /* For saving collapsed status when used in ListView */    private SparseBooleanArray mCollapsedStatus;    private int mPosition;    public ExpandableTextViewHorizontal(Context context) {        this(context, null);    }    public ExpandableTextViewHorizontal(Context context, AttributeSet attrs) {        super(context, attrs);        init(attrs);    }    @TargetApi(Build.VERSION_CODES.HONEYCOMB)    public ExpandableTextViewHorizontal(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        init(attrs);    }    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);        mAnimAlphaStart = typedArray.getFloat(R.styleable.ExpandableTextView_animAlphaStart, DEFAULT_ANIM_ALPHA_START);        mExpandDrawable = typedArray.getDrawable(R.styleable.ExpandableTextView_expandDrawable);        mCollapseDrawable = typedArray.getDrawable(R.styleable.ExpandableTextView_collapseDrawable);        if (mExpandDrawable == null) {            mExpandDrawable = getDrawable(getContext(), R.drawable.ic_expand_more_black_12dp);        }        if (mCollapseDrawable == null) {            mCollapseDrawable = getDrawable(getContext(), R.drawable.ic_expand_less_black_12dp);        }        typedArray.recycle();        setOrientation(getOrientation());    }    private void findViews() {        mTv = (TextView) getChildAt(0);        mTv.setOnClickListener(this);        mButton = (ImageButton) getChildAt(1);        mButton.setImageDrawable(mCollapsed ? mExpandDrawable : mCollapseDrawable);        mButton.setOnClickListener(this);        getRootView().setOnClickListener(this);    }    @Override    public void setOrientation(int orientation){       /* if(LinearLayout.HORIZONTAL == orientation){            throw new IllegalArgumentException("ExpandableTextView only supports Vertical Orientation.");        }*/        super.setOrientation(orientation);    }    @Override    public void onClick(View view) {        startAnimationClick();    }    private void startAnimationClick() {        if (mButton.getVisibility() != View.VISIBLE) {            return;        }        mCollapsed = !mCollapsed;        mButton.setImageDrawable(mCollapsed ? mExpandDrawable : mCollapseDrawable);        if (mCollapsedStatus != null) {            mCollapsedStatus.put(mPosition, mCollapsed);        }        // mark that the animation is in progress        mAnimating = true;        Animation animation;        if (mCollapsed) {            animation = new ExpandCollapseAnimation(this, getHeight(), mCollapsedHeight);        } else {            animation = new ExpandCollapseAnimation(this, getHeight(), getHeight() +  mTextHeightWithMaxLines - mTv.getHeight());        }        Log.e("onClick","mCollapsedHeight = "+ mCollapsedHeight + "  hhhhh = "+(getHeight() +  mTextHeightWithMaxLines - mTv.getHeight()));        animation.setFillAfter(true);        animation.setAnimationListener(new Animation.AnimationListener() {            @Override            public void onAnimationStart(Animation animation) {                applyAlphaAnimation(mTv, 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(mTv, !mCollapsed);                }            }            @Override            public void onAnimationRepeat(Animation animation) { }        });        clearAnimation();        startAnimation(animation);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        return mAnimating;    }    @Override    protected void onFinishInflate() {        super.onFinishInflate();        findViews();    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        // If no change, measure and return//        if (!mRelayout || getVisibility() == View.GONE) {        if (!mRelayout) {            super.onMeasure(widthMeasureSpec, heightMeasureSpec);            return;        }        mRelayout = false;        // Setup with optimistic case        // i.e. Everything fits. No button needed        mButton.setVisibility(View.GONE);        mTv.setMaxLines(Integer.MAX_VALUE);        // Measure        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        Log.e("onMeasure","mCollapsedHeight = "+ mCollapsedHeight + " mCollapsed = " + mCollapsed + " line count = "+ mTv.getLineCount() + " mCollapsed = "+ mCollapsed);        // If the text fits in collapsed mode, we are done.        if (mTv.getLineCount() <= mMaxCollapsedLines) {            return;        }        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        // Saves the text height w/ max lines        mTextHeightWithMaxLines = getRealTextViewHeight(mTv);        // Doesn't fit in collapsed mode. Collapse text view as needed. Show        // button.        if (mCollapsed) {            mTv.setMaxLines(mMaxCollapsedLines);        }        mButton.setVisibility(View.VISIBLE);        // Re-measure with new setup        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        if (mCollapsed) {            // Gets the margin between the TextView's bottom and the ViewGroup's bottom            mTv.post(new Runnable() {                @Override                public void run() {                    mMarginBetweenTxtAndBottom = getHeight() - mTv.getHeight();                    Log.e("onMeasure","mTv height  = "+ mTv.getHeight() + " mMarginBetweenTxtAndBottom = " + mMarginBetweenTxtAndBottom );                }            });            // Saves the collapsed height of this ViewGroup            mCollapsedHeight = getMeasuredHeight();            Log.e("onMeasure","mCollapsedHeight = "+ mCollapsedHeight + " mTextHeightWithMaxLines = " + mTextHeightWithMaxLines );        }    }    public void setOnExpandStateChangeListener(@Nullable OnExpandStateChangeListener listener) {        mListener = listener;    }    public void setText(@Nullable CharSequence text) {        mRelayout = true;        mTv.setText(text);        setVisibility(TextUtils.isEmpty(text) ? View.GONE : View.VISIBLE);    }    public void setText(@Nullable CharSequence text, @NonNull SparseBooleanArray collapsedStatus, int position) {        mCollapsedStatus = collapsedStatus;        mPosition = position;        boolean isCollapsed = collapsedStatus.get(position, true);        clearAnimation();        mCollapsed = isCollapsed;        mButton.setImageDrawable(mCollapsed ? mExpandDrawable : mCollapseDrawable);        setText(text);        getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;        requestLayout();    }    /**     * 其他控件点击的时候,执行缩放或者展开动画     */    public void startAnimation() {      startAnimationClick();    }    @Nullable    public CharSequence getText() {        if (mTv == null) {            return "";        }        return mTv.getText();    }    private static boolean isPostHoneycomb() {        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;    }    private static boolean isPostLolipop() {        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;    }    @TargetApi(Build.VERSION_CODES.HONEYCOMB)    private static void applyAlphaAnimation(View view, float alpha) {        if (isPostHoneycomb()) {            view.setAlpha(alpha);        } else {            AlphaAnimation alphaAnimation = new AlphaAnimation(alpha, alpha);            // make it instant            alphaAnimation.setDuration(0);            alphaAnimation.setFillAfter(true);            view.startAnimation(alphaAnimation);        }    }    @TargetApi(Build.VERSION_CODES.LOLLIPOP)    private static Drawable getDrawable(@NonNull Context context, @DrawableRes int resId) {        Resources resources = context.getResources();        if (isPostLolipop()) {            return resources.getDrawable(resId, context.getTheme());        } else {            return resources.getDrawable(resId);        }    }    private static int getRealTextViewHeight(@NonNull TextView textView) {        int textHeight = textView.getLayout().getLineTop(textView.getLineCount());        Log.e("getRealTextViewHeight","textHeight = "+ textHeight + " getHeight = " + textView.getMeasuredHeight());        int padding = textView.getCompoundPaddingTop() + textView.getCompoundPaddingBottom();        return textHeight + padding;    }    public boolean isCollapsed() {        return mCollapsed;    }    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);            Log.e("applyTransformation","newHeight = "+ newHeight + " mMarginBetweenTxtAndBottom = " + mMarginBetweenTxtAndBottom  + " mStartHeight = " + mStartHeight);            mTv.setMaxHeight(newHeight - mMarginBetweenTxtAndBottom);            if (Float.compare(mAnimAlphaStart, 1.0f) != 0) {                applyAlphaAnimation(mTv, 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;        }    }    public interface OnExpandStateChangeListener {        /**         * Called when the expand/collapse animation has been finished         *         * @param textView - TextView being expanded/collapsed         * @param isExpanded - true if the TextView has been expanded         */        void onExpandStateChanged(TextView textView, boolean isExpanded);    }}

自定义属性attrs

 <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>

代码修改地方:

1,去掉原固定设置linearlayout的方向为竖直方向
这样水平方向和竖直方向都支持

  @Override    public void setOrientation(int orientation){        if(LinearLayout.HORIZONTAL == orientation){            throw new IllegalArgumentException("ExpandableTextView only supports Vertical Orientation.");        }        super.setOrientation(orientation);    }

2,通过getChildAt来获取对应的控件,
不再使用R.id.expandable_text 和 R.id.expand_collapse

添加getRootView().setOnClickListener(this);

   private void findViews() {        mTv = (TextView) getChildAt(0);        mTv.setOnClickListener(this);        mButton = (ImageButton) getChildAt(1);        mButton.setImageDrawable(mCollapsed ? mExpandDrawable : mCollapseDrawable);        mButton.setOnClickListener(this);        // 添加根布局点击事件        getRootView().setOnClickListener(this);    }

DEMO下载:http://download.csdn.net/detail/huyuchaoheaven/9607448

参考和感谢:
https://github.com/Manabu-GT/ExpandableTextView
这里写图片描述

0 0
原创粉丝点击