TextView收缩

widget

package widget;

import android.content.Context;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.AppCompatTextView;
import android.text.Layout;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;

import com.self.zsp.mutualhelp.R;

import util.LogUtils;
import value.Magic;

/**
 * Created on 2018/3/31.
 * 静/动设
 *
 * @author xxx
 * @desc 隐显
 */
public class LittleMoreTextView extends AppCompatTextView {
    public LittleMoreTextView(Context context) {
        super(context, null);
    }

    public LittleMoreTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        limitTextViewString(this.getText().toString(), 140, this, new OnClickListener() {
            @Override
            public void onClick(View v) {
                // 设监听函数
            }
        });
    }

    /**
     * 动设
     *
     * @param content
     */
    public void setContent(String content) {
        limitTextViewString(content, 140, this, new OnClickListener() {
            @Override
            public void onClick(View v) {
                // 设监听函数
            }
        });
    }

    /**
     * get the last char index for max limit row,if not exceed the limit, return -1
     *
     * @param textView
     * @param content
     * @param width
     * @param maxLine
     * @return
     */
    private int getLastCharIndexForLimitTextView(TextView textView, String content, int width, int maxLine) {
        TextPaint textPaint = textView.getPaint();
        StaticLayout staticLayout = new StaticLayout(content, textPaint, width, Layout.Alignment.ALIGN_NORMAL, 1, 0, false);
        if (staticLayout.getLineCount() > maxLine) {
            // exceed
            return staticLayout.getLineStart(maxLine) - 1;
        } else {
            // not exceed the max line
            return -1;
        }
    }

    /**
     * 限TextView显字符且添showMore和showLittle点击事件
     *
     * @param textString
     * @param textView
     * @param clickListener textView点击监听器
     */
    private void limitTextViewString(String textString, int maxFirstShowCharCount, final TextView textView, final OnClickListener clickListener) {
        // 算处理花费时间
        final long startTime = System.currentTimeMillis();
        if (textView == null) {
            return;
        }
        // RecyclerView和ListView中,因复用原因,该TextView可能以前即画好,可获宽
        int width = textView.getWidth();
        if (width == 0) {
            // 获TextView实际宽,可用各方式(通dp转px写死)填TextView宽
            width = 1000;
        }
        int lastCharIndex = getLastCharIndexForLimitTextView(textView, textString, width, 4);
        // 返-1表没达行限
        if (lastCharIndex < 0 && textString.length() <= maxFirstShowCharCount) {
            // 行数没超限
            textView.setText(textString);
            return;
        }
        // 行数超限
        // this will deprive the recyclerView's focus
        textView.setMovementMethod(LinkMovementMethod.getInstance());
        if (lastCharIndex > maxFirstShowCharCount || lastCharIndex < 0) {
            lastCharIndex = maxFirstShowCharCount;
        }
        // 构造spannableString
        String explicitText = null;
        String explicitTextAll;
        if (textString.charAt(lastCharIndex) == Magic.CHAR_N) {
            // manual enter
            explicitText = textString.substring(0, lastCharIndex);
        } else if (lastCharIndex > Magic.INT_SE) {
            // 最大行数限制的那行达12后直显更多
            explicitText = textString.substring(0, lastCharIndex - 12);
        }
        int sourceLength = explicitText.length();
        String showMore = getContext().getString(R.string.textViewMore);
        explicitText = explicitText + "..." + showMore;
        final SpannableString mSpan = new SpannableString(explicitText);
        String dismissMore = getContext().getString(R.string.packUp);
        explicitTextAll = textString + " " + dismissMore;
        final SpannableString mSpanALL = new SpannableString(explicitTextAll);
        mSpanALL.setSpan(new ClickableSpan() {
            @Override
            public void updateDrawState(TextPaint ds) {
                super.updateDrawState(ds);
                // 收起色
                /*ds.setColor(textView.getResources().getColor(R.color.colorPrimary));*/
                ds.setColor(ContextCompat.getColor(getContext(), R.color.orange));
                ds.setAntiAlias(true);
                ds.setUnderlineText(false);
            }

            @Override
            public void onClick(View widget) {
                textView.setText(mSpan);
                textView.setOnClickListener(null);
                new Handler().postDelayed(() -> {
                    if (clickListener != null) {
                        // prevent the double click
                        textView.setOnClickListener(clickListener);
                    }
                }, 20);
            }
        }, textString.length(), explicitTextAll.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        mSpan.setSpan(new ClickableSpan() {
            @Override
            public void updateDrawState(TextPaint ds) {
                super.updateDrawState(ds);
                // 更多色
                /*ds.setColor(textView.getResources().getColor(R.color.colorPrimary));*/
                ds.setColor(ContextCompat.getColor(getContext(), R.color.orange));
                ds.setAntiAlias(true);
                ds.setUnderlineText(false);
            }

            @Override
            public void onClick(View widget) {
                //"...show more" click event
                textView.setText(mSpanALL);
                textView.setOnClickListener(null);
                new Handler().postDelayed(() -> {
                    if (clickListener != null) {
                        // prevent the double click
                        textView.setOnClickListener(clickListener);
                    }
                }, 20);
            }
        }, sourceLength, explicitText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        // 显更多状TextView
        textView.setText(mSpan);
        LogUtils.e("info", "字符串处理耗时" + (System.currentTimeMillis() - startTime) + " ms");
    }
}

布局

<widget.LittleMoreTextView
    android:id="@+id/tvActiveDetailTitle"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textColor="@color/fontHint"
    android:textSize="@dimen/s13" />

主代码

tvActiveDetailTitle.setContent(dataBean.getAcTitle());

widget

package widget;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v4.content.ContextCompat;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ImageSpan;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.TextView;

import com.self.zsp.mutualhelp.R;

import util.DensityUtils;
import value.Magic;

/**
 * @decs: 两模式、三对齐、一padding
 * @date: 2018/5/27 0027 上午 9:50
 * @version: v 1.0
 */
public class ExpandTextView extends android.support.v7.widget.AppCompatTextView implements View.OnClickListener {
    /**
     * 默显8行
     */
    private static final int MAX_COLLAPSED_LINES = 8;
    /**
     * 默动时长300ms
     */
    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;
    /**
     * 最大显行数
     */
    private int mMaxCollapsedLines = 8;
    private int mAnimationDuration;
    private float mAnimAlphaStart;
    /**
     * 展前图
     */
    private Drawable mExpandDrawable;
    /**
     * 展后图
     */
    private Drawable mCollapseDrawable;
    private int mCollapsedHeight;
    private int mTextHeightWithMaxLines;
    /**
     * 此刻折叠状
     */
    private boolean mCollapsed = true;
    private boolean mAnimating = false;
    /**
     * 需折叠已显尾图否
     */
    private boolean needCollapse = true;
    private int mDrawableSize = 0;
    /**
     * 箭头对齐方式(左/上、居中、右/下)
     */
    private static final int ALIGN_RIGHT_BOTTOM = 0;
    private static final int ALIGN_LEFT_TOP = 1;
    private static final int ALIGN_CENTER = 2;
    private int arrowAlign = ALIGN_RIGHT_BOTTOM;
    /**
     * 箭头显位(文右、文下)
     */
    private static final int POSITION_RIGHT = 0;
    private static final int POSITION_BELOW = 1;
    private int arrowPosition = POSITION_RIGHT;
    /**
     * 图文间距
     */
    private int arrowDrawablePadding = 0;
    /**
     * listener for callback
     */
    private OnExpandStateChangeListener mListener;

    public ExpandTextView(Context context) {
        this(context, null);
    }

    public ExpandTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ExpandTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ExpandTextView, defStyleAttr, 0);
        mMaxCollapsedLines = typedArray.getInt(R.styleable.ExpandTextView_maxCollapsedLines, MAX_COLLAPSED_LINES);
        mAnimationDuration = typedArray.getInt(R.styleable.ExpandTextView_animDuration, DEFAULT_ANIM_DURATION);
        mAnimAlphaStart = typedArray.getFloat(R.styleable.ExpandTextView_animAlphaStart, DEFAULT_ANIM_ALPHA_START);
        mExpandDrawable = typedArray.getDrawable(R.styleable.ExpandTextView_expandDrawable);
        mCollapseDrawable = typedArray.getDrawable(R.styleable.ExpandTextView_collapseDrawable);
        arrowAlign = typedArray.getInteger(R.styleable.ExpandTextView_arrowAlign, ALIGN_RIGHT_BOTTOM);
        arrowPosition = typedArray.getInteger(R.styleable.ExpandTextView_arrowPosition, POSITION_RIGHT);
        arrowDrawablePadding = (int) typedArray.getDimension(R.styleable.ExpandTextView_arrowPadding, DensityUtils.dip2px(context, 2f));
        typedArray.recycle();
        if (mExpandDrawable == null) {
            mExpandDrawable = getDrawable(getContext(), R.drawable.right);
        }
        if (mCollapseDrawable == null) {
            mCollapseDrawable = getDrawable(getContext(), R.drawable.right);
        }
        setClickable(true);
        setOnClickListener(this);
    }

    private boolean isDrawablePaddingResolved = false;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (getVisibility() == GONE || mAnimating) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            return;
        }
        // 重置高重测
        // 设wrap_content(重新measure)
        getLayoutParams().height = -2;
        setMaxLines(Integer.MAX_VALUE);
        // 测TextView总高
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (getLineCount() <= mMaxCollapsedLines) {
            needCollapse = false;
            return;
        }
        needCollapse = true;
        mTextHeightWithMaxLines = getRealTextViewHeight(this);
        if (mCollapsed) {
            setMaxLines(mMaxCollapsedLines);
        }
        mDrawableSize = mExpandDrawable.getIntrinsicWidth();
        if (!isDrawablePaddingResolved) {
            if (arrowPosition == POSITION_RIGHT) {
                setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight() + mDrawableSize + arrowDrawablePadding, getPaddingBottom());
            } else {
                setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom() + mExpandDrawable.getIntrinsicHeight() + arrowDrawablePadding);
            }
            isDrawablePaddingResolved = true;
        }
        // 设后重测
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (mCollapsed) {
            mCollapsedHeight = getMeasuredHeight();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (needCollapse) {
            int left, top;
            if (arrowPosition == POSITION_RIGHT) {
                left = getWidth() - getTotalPaddingRight() + arrowDrawablePadding;
                switch (arrowAlign) {
                    case ALIGN_LEFT_TOP:
                        top = getTotalPaddingTop();
                        break;
                    case ALIGN_CENTER:
                        top = (getHeight() - mExpandDrawable.getIntrinsicHeight()) / 2;
                        break;
                    case ALIGN_RIGHT_BOTTOM:
                    default:
                        top = getHeight() - getTotalPaddingBottom() - mExpandDrawable.getIntrinsicHeight();
                        break;
                }
            } else {
                top = getHeight() - getTotalPaddingBottom() + arrowDrawablePadding;
                switch (arrowAlign) {
                    case ALIGN_LEFT_TOP:
                        left = getTotalPaddingLeft();
                        break;
                    case ALIGN_CENTER:
                        left = (getWidth() - mExpandDrawable.getIntrinsicWidth()) / 2;
                        break;
                    case ALIGN_RIGHT_BOTTOM:
                    default:
                        left = getWidth() - getTotalPaddingRight() - mExpandDrawable.getIntrinsicWidth();
                        break;
                }
            }
            canvas.translate(left, top);
            if (mCollapsed) {
                mExpandDrawable.setBounds(0, 0, mExpandDrawable.getIntrinsicWidth(), mExpandDrawable.getIntrinsicHeight());
                mExpandDrawable.draw(canvas);
            } else {
                mCollapseDrawable.setBounds(0, 0, mCollapseDrawable.getIntrinsicWidth(), mCollapseDrawable.getIntrinsicHeight());
                mCollapseDrawable.draw(canvas);
            }
        }
    }

    @Override
    public void setText(CharSequence text, BufferType type) {
        setCollapsed(true);
        super.setText(text, type);
    }

    @Override
    public void onClick(View v) {
        if (!needCollapse) {
            // 行数不足(不响应点事件)
            return;
        }
        mCollapsed = !mCollapsed;
        Bitmap collapseBM = Bitmap.createBitmap(mCollapseDrawable.getIntrinsicWidth(), mCollapseDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        Canvas cv2 = new Canvas(collapseBM);
        mCollapseDrawable.setBounds(0, 0, mCollapseDrawable.getIntrinsicWidth(), mCollapseDrawable.getIntrinsicHeight());
        mCollapseDrawable.draw(cv2);
        ImageSpan isExpand = new ImageSpan(mExpandDrawable);
        ImageSpan isCollapse = new ImageSpan(getContext(), collapseBM);
        SpannableString spannableString = new SpannableString("icon");
        spannableString.setSpan(mCollapsed ? isExpand : isCollapse, 0, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        // 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(), mTextHeightWithMaxLines);
        }
        animation.setFillAfter(true);
        animation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                if (mListener != null) {
                    mListener.onChangeStateStart(!mCollapsed);
                }
                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);
    }

    private class ExpandCollapseAnimation extends Animation {
        private final View mTargetView;
        private final int mStartHeight;
        private final int mEndHeight;

        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);
            mTargetView.getLayoutParams().height = newHeight;
            setMaxHeight(newHeight);
            if (Float.compare(mAnimAlphaStart, Magic.FLOAT_YDL) != 0) {
                applyAlphaAnimation(ExpandTextView.this, mAnimAlphaStart + interpolatedTime * (1.0f - mAnimAlphaStart));
            }
        }

        @Override
        public void initialize(int width, int height, int parentWidth, int parentHeight) {
            super.initialize(width, height, parentWidth, parentHeight);
        }

        @Override
        public boolean willChangeBounds() {
            return true;
        }
    }

    private Drawable getDrawable(Context context, int drawableResId) {
        Resources resources = context.getResources();
        if (isPostLollipop()) {
            return resources.getDrawable(drawableResId, context.getTheme());
        } else {
            return ContextCompat.getDrawable(context, drawableResId);
        }
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private 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);
        }
    }

    private boolean isPostHoneycomb() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
    }

    private boolean isPostLollipop() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
    }

    private int getRealTextViewHeight(TextView textView) {
        int textHeight = textView.getLayout().getLineTop(textView.getLineCount());
        int padding = textView.getCompoundPaddingTop() + textView.getCompoundPaddingBottom();
        return textHeight + padding;
    }

    public void setCollapsed(boolean isCollapsed) {
        mCollapsed = isCollapsed;
    }

    public interface OnExpandStateChangeListener {
        /**
         * xxx
         *
         * @param willExpand 将展
         */
        void onChangeStateStart(boolean willExpand);

        /**
         * 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);
    }

    public void setOnExpandStateChangeListener(OnExpandStateChangeListener listener) {
        mListener = listener;
    }
}

attrs

<!--ExpandTextView-->
<declare-styleable name="ExpandTextView">
    <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" />
    <!--三对齐 左上/居中/右下-->
    <attr name="arrowAlign" format="enum">
        <enum name="bottom" value="0" />
        <enum name="top" value="1" />
        <enum name="center" value="2" />
        <enum name="right" value="0" />
        <enum name="left" value="1" />
    </attr>
    <!--两模式 右/下-->
    <attr name="arrowPosition" format="enum">
        <enum name="right" value="0" />
        <enum name="below" value="1" />
    </attr>
    <attr name="arrowPadding" format="dimension" />
</declare-styleable>

布局

<widget.ExpandTextView
    android:id="@+id/tvBillDetailContent"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:ellipsize="end"      
    android:textColor="@color/fontHint"
    android:textSize="@dimen/s13"
    app:arrowAlign="left"
    app:arrowPosition="below"
    app:collapseDrawable="@drawable/ic_arrow_up"
    app:expandDrawable="@drawable/ic_arrow_down"
    app:maxCollapsedLines="3" />

属性

maxCollapsedLines 开始折叠行数
animDuration 展开/折叠动画时长
animAlphaStart 透明度渐变起始值
expandDrawable 展开图
collapseDrawable 折叠图
arrowAlign 对齐方式(左/上、居中、右/下可选)
arrowPosition 位置(text右、text下)
arrowPadding 图文间距

注意

布局不设

app:collapseDrawable="@drawable/ic_arrow_up"
app:expandDrawable="@drawable/ic_arrow_down"

则用widget设图

if (mCollapsed) {
    mExpandDrawable.setBounds(0, 0, mExpandDrawable.getIntrinsicWidth(), mExpandDrawable.getIntrinsicHeight());
    mExpandDrawable.draw(canvas);
} else {
    mCollapseDrawable.setBounds(0, 0, mCollapseDrawable.getIntrinsicWidth(), mCollapseDrawable.getIntrinsicHeight());
    mCollapseDrawable.draw(canvas);
}

猜你喜欢

转载自blog.csdn.net/zsp_android_com/article/details/80471324