recyclerView使用 ExpandableTextView 列表展开收起功能

1、需求: 文字列表中展示收起和全部

在这里插入图片描述
在这里插入图片描述

2、采用RecyclerView 实现列表,网上查找了许多展开收起的样式,实测结果不如人意,

2.1 设置了最大展示行数之后,出现了展开之后,收起出错的问题
2.2 显示文字的View错位的时候,点击“收起/展开”事件无效。设置了点击事件,实际没有执行 onInterceptTouchEvent 中 mAnimating引起

3、解决方式

借鉴方案:关于ExpandableTextView几点优化

解决方案:点击跳转

4、工具类

package xxx;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.SparseBooleanArray;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.IdRes;


public class ExpandableTextView extends LinearLayout implements View.OnClickListener {

    private static final String TAG = ExpandableTextView.class.getSimpleName();

    private static final int EXPAND_INDICATOR_IMAGE_BUTTON = 0;

    private static final int EXPAND_INDICATOR_TEXT_VIEW = 1;

//    private static final int DEFAULT_TOGGLE_TYPE = EXPAND_INDICATOR_IMAGE_BUTTON;

    private static final int DEFAULT_TOGGLE_TYPE = EXPAND_INDICATOR_TEXT_VIEW;

    /* The default number of lines */
    private static final int MAX_COLLAPSED_LINES = 3;

    /* 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 View mToggleView; // View to expand/collapse

    private boolean mRelayout;

    private boolean mCollapsed = true; // Show short version as default.

    private int mCollapsedHeight;

    private int mTextHeightWithMaxLines;

    private int mMaxCollapsedLines;

    private int mMarginBetweenTxtAndBottom;

    private ExpandIndicatorController mExpandIndicatorController;

    private int mAnimationDuration;

    private float mAnimAlphaStart;

    private boolean mAnimating;

    @IdRes
    private int mExpandableTextId = R.id.item_dynamic_source_textview;

    @IdRes
    private int mExpandCollapseToggleId = R.id.item_dynamic_expandTextview;

    private boolean mExpandToggleOnTextClick;

    /* Listener for callback */
    private OnExpandStateChangeListener mListener;

    /* For saving collapsed status when used in ListView */
    private SparseBooleanArray mCollapsedStatus;
    private int mPosition;

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

    public ExpandableTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public ExpandableTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs);
    }

    @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) {
        if (mToggleView.getVisibility() != View.VISIBLE) {
            return;
        }

        mCollapsed = !mCollapsed;
        mExpandIndicatorController.changeState(mCollapsed);

        if (mCollapsedStatus != null) {
            mCollapsedStatus.put(mPosition, mCollapsed);
        }

        // mark that the animation is in progress
        mAnimating = true;
        ObjectAnimator animator3 = ObjectAnimator.ofFloat(this, "alpha", 1f, 1f);//变淡
        final AnimatorSet set = new AnimatorSet();
        set.playTogether(animator3);
        set.setDuration(mAnimationDuration).start();
        animator3.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int mStartHeight = getHeight();
                int mEndHeight;
                if (mCollapsed) {
                    mEndHeight = mCollapsedHeight;
                } else {
                    mEndHeight = getHeight() + mTextHeightWithMaxLines - mTv.getHeight();
                }
                final int newHeight = (int) ((mEndHeight - mStartHeight) * animation.getAnimatedFraction() + mStartHeight);
                mTv.setMaxHeight(newHeight - mMarginBetweenTxtAndBottom);
                ExpandableTextView.this.getLayoutParams().height = newHeight;
                ExpandableTextView.this.requestLayout();
            }
        });

        set.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }
            @Override
            public void onAnimationEnd(Animator animation) {

                clearAnimation();

                mAnimating = false;

                if (mListener != null) {
                    mListener.onExpandStateChanged(mTv, !mCollapsed);
                }
            }

            @Override
            public void onAnimationCancel(Animator animation) {
            }
            @Override
            public void onAnimationRepeat(Animator animation) {
            }
        });
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // while an animation is in progress, intercept all the touch events to children to
        // prevent extra clicks during the animation
        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) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            return;
        }
        mRelayout = false;

        // Setup with optimistic case
        // i.e. Everything fits. No button needed
        mToggleView.setVisibility(View.GONE);
        mTv.setMaxLines(Integer.MAX_VALUE);

        // Measure
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // If the text fits in collapsed mode, we are done.
        if (mTv.getLineCount() <= mMaxCollapsedLines) {
            return;
        }

        // 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);
        }
        mToggleView.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();
                }
            });
            // Saves the collapsed height of this ViewGroup
            mCollapsedHeight = getMeasuredHeight();
        }
    }

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

    public void setText( CharSequence text) {
        mRelayout = true;
        mTv.setText(text);
        setVisibility(TextUtils.isEmpty(text) ? View.GONE : View.VISIBLE);
        clearAnimation();
        getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
        requestLayout();
    }

    public void setText(CharSequence text,  SparseBooleanArray collapsedStatus, int position) {
        mCollapsedStatus = collapsedStatus;
        mPosition = position;
        boolean isCollapsed = collapsedStatus.get(position, true);
        clearAnimation();
        mCollapsed = isCollapsed;
        mExpandIndicatorController.changeState(mCollapsed);
        setText(text);
    }


    public CharSequence getText() {
        if (mTv == null) {
            return "";
        }
        return mTv.getText();
    }

    private void init(AttributeSet attrs) {
        TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.ExpandableTextViewNew);
        mMaxCollapsedLines = typedArray.getInt(R.styleable.ExpandableTextViewNew_maxCollapsedLines, MAX_COLLAPSED_LINES);
        mAnimationDuration = typedArray.getInt(R.styleable.ExpandableTextViewNew_animDuration, DEFAULT_ANIM_DURATION);
        mAnimAlphaStart = typedArray.getFloat(R.styleable.ExpandableTextViewNew_animAlphaStart, DEFAULT_ANIM_ALPHA_START);
        mExpandableTextId = typedArray.getResourceId(R.styleable.ExpandableTextViewNew_expandableTextId, R.id.item_dynamic_source_textview);
        mExpandCollapseToggleId = typedArray.getResourceId(R.styleable.ExpandableTextViewNew_expandCollapseToggleId, R.id.item_dynamic_expandTextview);
        mExpandToggleOnTextClick = typedArray.getBoolean(R.styleable.ExpandableTextViewNew_expandToggleOnTextClick, true);

        mExpandIndicatorController = setupExpandToggleController(getContext(), typedArray);

        typedArray.recycle();

        // enforces vertical orientation
        setOrientation(LinearLayout.VERTICAL);

        // default visibility is gone
        setVisibility(GONE);
    }

    private void findViews() {
        mTv = (TextView) findViewById(mExpandableTextId);
        if (mExpandToggleOnTextClick) {
            mTv.setOnClickListener(this);
        } else {
            mTv.setOnClickListener(null);
        }
        mToggleView = findViewById(mExpandCollapseToggleId);
        mExpandIndicatorController.setView(mToggleView);
        mExpandIndicatorController.changeState(mCollapsed);
        mToggleView.setOnClickListener(this);
    }

    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( 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( TextView textView) {
        int textHeight = textView.getLayout().getLineTop(textView.getLineCount());
        int padding = textView.getCompoundPaddingTop() + textView.getCompoundPaddingBottom();
        return textHeight + padding;
    }

    private static ExpandIndicatorController setupExpandToggleController( Context context, TypedArray typedArray) {
        final int expandToggleType = typedArray.getInt(R.styleable.ExpandableTextViewNew_expandToggleType, DEFAULT_TOGGLE_TYPE);
        final ExpandIndicatorController expandIndicatorController;
        switch (expandToggleType) {
            case EXPAND_INDICATOR_IMAGE_BUTTON:
                Drawable expandDrawable = typedArray.getDrawable(R.styleable.ExpandableTextViewNew_expandIndicator);
                Drawable collapseDrawable = typedArray.getDrawable(R.styleable.ExpandableTextViewNew_collapseIndicator);

                if (expandDrawable == null) {
                    expandDrawable = getDrawable(context, R.drawable.course_label_down);
                }
                if (collapseDrawable == null) {
                    collapseDrawable = getDrawable(context, R.drawable.course_label_up);
                }
                expandIndicatorController = new ImageButtonExpandController(expandDrawable, collapseDrawable);
                break;
            case EXPAND_INDICATOR_TEXT_VIEW:
                String expandText = typedArray.getString(R.styleable.ExpandableTextViewNew_expandIndicator);
                String collapseText = typedArray.getString(R.styleable.ExpandableTextViewNew_collapseIndicator);
                expandIndicatorController = new TextViewExpandController(expandText, collapseText);
                break;
            default:
                throw new IllegalStateException("Must be of enum: ExpandableTextView_expandToggleType, one of EXPAND_INDICATOR_IMAGE_BUTTON or EXPAND_INDICATOR_TEXT_VIEW.");
        }

        return expandIndicatorController;
    }

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

    interface ExpandIndicatorController {
        void changeState(boolean collapsed);

        void setView(View toggleView);
    }

    static class ImageButtonExpandController implements ExpandIndicatorController {

        private final Drawable mExpandDrawable;
        private final Drawable mCollapseDrawable;

        private ImageButton mImageButton;

        public ImageButtonExpandController(Drawable expandDrawable, Drawable collapseDrawable) {
            mExpandDrawable = expandDrawable;
            mCollapseDrawable = collapseDrawable;
        }

        @Override
        public void changeState(boolean collapsed) {
            mImageButton.setImageDrawable(collapsed ? mExpandDrawable : mCollapseDrawable);
        }

        @Override
        public void setView(View toggleView) {
            mImageButton = (ImageButton) toggleView;
        }
    }

    static class TextViewExpandController implements ExpandIndicatorController {

        private final String mExpandText;
        private final String mCollapseText;

        private TextView mTextView;

        public TextViewExpandController(String expandText, String collapseText) {
            mExpandText = expandText;
            mCollapseText = collapseText;
        }

        @Override
        public void changeState(boolean collapsed) {
            mTextView.setText(collapsed ? mExpandText : mCollapseText);
        }

        @Override
        public void setView(View toggleView) {
            mTextView = (TextView) toggleView;
        }
    }
}

attrs.xml

 <declare-styleable name="ExpandableTextViewNew">
        <attr name="maxCollapsedLines" format="integer"/>
        <attr name="animDuration" format="integer"/>
        <attr name="animAlphaStart" format="float"/>
        <attr name="expandIndicator" format="reference"/>
        <attr name="collapseIndicator" format="reference"/>
        <attr name="expandToggleType" format="enum">
            <enum name="ImageButton" value="0"/>
            <enum name="TextView" value="1"/>
        </attr>
        <attr name="expandableTextId" format="reference"/>
        <attr name="expandCollapseToggleId" format="reference"/>
        <attr name="expandToggleOnTextClick" format="boolean"/>
    </declare-styleable>

5、如何使用

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="@color/white"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

                <com.xxx.xxx.ExpandableTextView
                                android:id="@+id//item_expLayout"
                                android:layout_marginTop="@dimen/dp_10"
                                app:maxCollapsedLines="3"   //最大展示行数
                                app:expandIndicator="@string/allText"  //展开文字
                                app:collapseIndicator="@string/collspText"  //收起文字
                                android:layout_width="match_parent"
                                android:layout_height="wrap_content">

                                <TextView
                                    android:id="@+id/item_source_textview"
                                    android:textSize="@dimen/sp_14"
                                    android:textColor="@color/black_ff_111111"
                                    android:lineSpacingExtra="@dimen/dp_5"
                                    android:layout_width="wrap_content"
                                    android:layout_height="wrap_content" />

                                <TextView
                                    android:id="@+id/item_expandTextview"
                                    android:layout_marginTop="@dimen/dp_10"
                                    android:layout_width="wrap_content"
                                    android:textSize="@dimen/sp_16"
                                    android:layout_height="wrap_content"
                                    android:textColor="@color/underLineYellow"
                                    android:text="全文" />

                            </com.xxx.xxx.ExpandableTextView>
                            
</LinearLayout>

adapter中设置:

public class CommunicateAdapter extends RecyclerAdapter<DynamicListInfo>{
    private SparseBooleanArray mCollapsedStatus;
    //构造器
    public MyAdapter(Context context, List<DynamicListInfo> data) {
        super(context, data, R.layout.item_communicate);
        mCollapsedStatus = new SparseBooleanArray();
    }
    //设置数据
    @Override
    public void convert(RecyclerHolder holder, DynamicListInfo model) {
        ExpandableTextView expText= holder.findViewById(R.id.item_expLayout);
        expText.setText(dynamicText,mCollapsedStatus,holder.position);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值