Android - Expandable TextView with Animation

后端 未结 15 1018
小蘑菇
小蘑菇 2020-11-28 18:24

I have a TextView which firstly shows a small portion of a long text.

The user can press a \"see more\" button to expand the TextView and s

相关标签:
15条回答
  • 2020-11-28 18:48

    You can check my blog post on ExpandableTexTView:

    The idea is, initially the TextView will show a small portion of a long text and when it is clicked, it will show the rest of the text.

    So here is the code that how I solved it.

    package com.rokonoid.widget;
    
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.text.SpannableStringBuilder;
    import android.util.AttributeSet;
    import android.view.View;
    import android.widget.TextView;
    /**
     * User: Bazlur Rahman Rokon
     * Date: 9/7/13 - 3:33 AM
     */
    public class ExpandableTextView extends TextView {
        private static final int DEFAULT_TRIM_LENGTH = 200;
        private static final String ELLIPSIS = ".....";
    
        private CharSequence originalText;
        private CharSequence trimmedText;
        private BufferType bufferType;
        private boolean trim = true;
        private int trimLength;
    
        public ExpandableTextView(Context context) {
            this(context, null);
        }
    
        public ExpandableTextView(Context context, AttributeSet attrs) {
            super(context, attrs);
    
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ExpandableTextView);
            this.trimLength = typedArray.getInt(R.styleable.ExpandableTextView_trimLength, DEFAULT_TRIM_LENGTH);
            typedArray.recycle();
    
            setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    trim = !trim;
                    setText();
                    requestFocusFromTouch();
                }
            });
        }
    
        private void setText() {
            super.setText(getDisplayableText(), bufferType);
        }
    
        private CharSequence getDisplayableText() {
            return trim ? trimmedText : originalText;
        }
    
        @Override
        public void setText(CharSequence text, BufferType type) {
            originalText = text;
            trimmedText = getTrimmedText(text);
            bufferType = type;
            setText();
        }
    
        private CharSequence getTrimmedText(CharSequence text) {
            if (originalText != null && originalText.length() > trimLength) {
                return new SpannableStringBuilder(originalText, 0, trimLength + 1).append(ELLIPSIS);
            } else {
                return originalText;
            }
        }
    
        public CharSequence getOriginalText() {
            return originalText;
        }
    
        public void setTrimLength(int trimLength) {
            this.trimLength = trimLength;
            trimmedText = getTrimmedText(originalText);
            setText();
        }
    
        public int getTrimLength() {
            return trimLength;
        }
    }
    

    And add the following line in your attr.xml

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="ExpandableTextView">
            <attr name="trimLength" format="integer"/>
        </declare-styleable>
    </resources>
    

    Put the following in your main.xml

    <?xml version="1.0" encoding="utf-8"?>
     <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
         android:orientation="vertical"
         android:layout_width="fill_parent"
         android:layout_height="fill_parent">
    
         <com.rokonoid.widget.ExpandableTextView
             android:id="@+id/lorem_ipsum"
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"/>
    
     </LinearLayout>
    

    And test your activity

    package com.rokonoid.widget;
    
    import android.app.Activity;
    import android.os.Bundle;
    
    public class MyActivity extends Activity {
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
    
            String yourText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
                    "Ut volutpat interdum interdum. Nulla laoreet lacus diam, vitae " +
                    "sodales sapien commodo faucibus. Vestibulum et feugiat enim. Donec " +
                    "semper mi et euismod tempor. Sed sodales eleifend mi id varius. Nam " +
                    "et ornare enim, sit amet gravida sapien. Quisque gravida et enim vel " +
                    "volutpat. Vivamus egestas ut felis a blandit. Vivamus fringilla " +
                    "dignissim mollis. Maecenas imperdiet interdum hendrerit. Aliquam" +
                    " dictum hendrerit ultrices. Ut vitae vestibulum dolor. Donec auctor ante" +
                    " eget libero molestie porta. Nam tempor fringilla ultricies. Nam sem " +
                    "lectus, feugiat eget ullamcorper vitae, ornare et sem. Fusce dapibus ipsum" +
                    " sed laoreet suscipit. ";
    
            ExpandableTextView expandableTextView = (ExpandableTextView) findViewById(R.id.lorem_ipsum);
            expandableTextView.setText(yourText);
        }
    }
    

    Reference: Android – Expandable TextView

    0 讨论(0)
  • 2020-11-28 18:48

    Here is what worked for me using some of the above responses (I am using ButterKnife in the example):

    private static final MAX_LINE_COUNT = 3;    
    
    @Bind(R.id.description)
    TextView mDescription;    
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
      if(!TextUtils.isEmpty(mDescription)) {
        mDescription.setText(mItem.description);
        mDescription.setMaxLines(MAX_LINE_COUNT);
        mDescription.setEllipsize(TextUtils.TruncateAt.END);
      } else {
        mDescription.setVisibility(View.GONE);
      }
    
    }
    
    @OnClick(R.id.description)
    void collapseExpandTextView(TextView tv) {
    
        if (tv.getMaxLines() == MAX_LINE_COUNT) {
            // collapsed - expand it
            tv.setEllipsize(null);
            tv.setMaxLines(Integer.MAX_VALUE);
        } else {
            // expanded - collapse it
            tv.setEllipsize(TextUtils.TruncateAt.END);
            tv.setMaxLines(MAX_LINE_COUNT);
        }
    
        ObjectAnimator animation = ObjectAnimator.ofInt(tv, "maxLines", tv.getMaxLines());
        animation.setDuration(200).start();
    }   
    

    When the user clicks on the description it will either collapse or expand based on the max lines. This will only work for API 16+.

    The problem that I ran into was that line count was returning zero at points and line count and max count were the same values at certain points.

    0 讨论(0)
  • 2020-11-28 18:50

    Refer below link for expandable TextView with options for a number of lines and more less text.

    Resizeable Text View(View More and View Less)

    Add below line in Java class after setting text in your TextView.

    // YourCustomeClass.class [your customized class]
    // yourTextView [ TextView yourTextView = findViewById(R.id.yourTextView) ];
    
    YourCustomeClass.doResizeTextView(yourTextView, 3, "More", true);
    
    // 3 - No of lines after user wants to expand it. 
    // "More" : text want to see end of your TextView after shrink
    // True : flag for viewMore
    
    0 讨论(0)
  • 2020-11-28 18:54

    You can use the new TransitionManager for the animation and calling the maxLines attribute to set the amount

    fun toggleReadMoreTextView(linesWhenCollapsed: Float) {
        if (viewDataBinding.textView.maxLines != Integer.MAX_VALUE) {
            // exapand
            viewDataBinding.textView.maxLines = Integer.MAX_VALUE
        } else {
            // collapse
            viewDataBinding.textView.maxLines = linesWhenCollapsed
        }
        // start animation
        TransitionManager.beginDelayedTransition(viewDataBinding.constraintLayout)
    }
    
    0 讨论(0)
  • 2020-11-28 18:56

    I created an open-source library for this, because I wasn’t satisfied with the other solutions I found on the internet. I’ve put the thing on GitHub, and it’s free to use by anyone.

    public class ExpandableTextView extends TextView
    {
        // copy off TextView.LINES
        private static final int MAXMODE_LINES = 1;
    
        private OnExpandListener onExpandListener;
        private TimeInterpolator expandInterpolator;
        private TimeInterpolator collapseInterpolator;
    
        private final int maxLines;
        private long animationDuration;
        private boolean animating;
        private boolean expanded;
        private int originalHeight;
    
        public ExpandableTextView(final Context context)
        {
            this(context, null);
        }
    
        public ExpandableTextView(final Context context, final AttributeSet attrs)
        {
            this(context, attrs, 0);
        }
    
        public ExpandableTextView(final Context context, final AttributeSet attrs, final int defStyle)
        {
            super(context, attrs, defStyle);
    
            // read attributes
            final TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.ExpandableTextView, defStyle, 0);
            this.animationDuration = attributes.getInt(R.styleable.ExpandableTextView_animation_duration, BuildConfig.DEFAULT_ANIMATION_DURATION);
            attributes.recycle();
    
            // keep the original value of maxLines
            this.maxLines = this.getMaxLines();
    
            // create default interpolators
            this.expandInterpolator = new AccelerateDecelerateInterpolator();
            this.collapseInterpolator = new AccelerateDecelerateInterpolator();
        }
    
        @Override
        public int getMaxLines()
        {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
            {
                return super.getMaxLines();
            }
    
            try
            {
                final Field mMaxMode = TextView.class.getField("mMaxMode");
                mMaxMode.setAccessible(true);
                final Field mMaximum = TextView.class.getField("mMaximum");
                mMaximum.setAccessible(true);
    
                final int mMaxModeValue = (int) mMaxMode.get(this);
                final int mMaximumValue = (int) mMaximum.get(this);
    
                return mMaxModeValue == MAXMODE_LINES ? mMaximumValue : -1;
            }
            catch (final Exception e)
            {
               return -1;
            }
        }
    
        /**
         * Toggle the expanded state of this {@link ExpandableTextView}.
         * @return true if toggled, false otherwise.
         */
        public boolean toggle()
        {
            if (this.expanded)
            {
                return this.collapse();
            }
    
            return this.expand();
        }
    
        /**
         * Expand this {@link ExpandableTextView}.
         * @return true if expanded, false otherwise.
         */
        public boolean expand()
        {
            if (!this.expanded && !this.animating && this.maxLines >= 0)
            {
                this.animating = true;
    
                // notify listener
                if (this.onExpandListener != null)
                {
                    this.onExpandListener.onExpand(this);
                }
    
                // get original height
                this.measure
                (
                    MeasureSpec.makeMeasureSpec(this.getMeasuredWidth(), MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
                );
    
                this.originalHeight = this.getMeasuredHeight();
    
                // set maxLines to MAX Integer
                this.setMaxLines(Integer.MAX_VALUE);
    
                // get new height
                this.measure
                (
                    MeasureSpec.makeMeasureSpec(this.getMeasuredWidth(), MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
                );
    
                final int fullHeight = this.getMeasuredHeight();
    
                final ValueAnimator valueAnimator = ValueAnimator.ofInt(this.originalHeight, fullHeight);
                valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
                {
                    @Override
                    public void onAnimationUpdate(final ValueAnimator animation)
                    {
                        final ViewGroup.LayoutParams layoutParams = ExpandableTextView.this.getLayoutParams();
                        layoutParams.height = (int) animation.getAnimatedValue();
                        ExpandableTextView.this.setLayoutParams(layoutParams);
                    }
                });
                valueAnimator.addListener(new AnimatorListenerAdapter()
                {
                    @Override
                    public void onAnimationEnd(final Animator animation)
                    {
                        ExpandableTextView.this.expanded = true;
                        ExpandableTextView.this.animating = false;
                    }
                });
    
                // set interpolator
                valueAnimator.setInterpolator(this.expandInterpolator);
    
                // start the animation
                valueAnimator
                    .setDuration(this.animationDuration)
                    .start();
    
                return true;
            }
    
            return false;
        }
    
        /**
         * Collapse this {@link TextView}.
         * @return true if collapsed, false otherwise.
         */
        public boolean collapse()
        {
            if (this.expanded && !this.animating && this.maxLines >= 0)
            {
                this.animating = true;
    
                // notify listener
                if (this.onExpandListener != null)
                {
                    this.onExpandListener.onCollapse(this);
                }
    
                // get new height
                final int fullHeight = this.getMeasuredHeight();
    
                final ValueAnimator valueAnimator = ValueAnimator.ofInt(fullHeight, this.originalHeight);
                valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
                {
                    @Override
                    public void onAnimationUpdate(final ValueAnimator animation)
                    {
                        final ViewGroup.LayoutParams layoutParams = ExpandableTextView.this.getLayoutParams();
                        layoutParams.height = (int) animation.getAnimatedValue();
                        ExpandableTextView.this.setLayoutParams(layoutParams);
                    }
                });
                valueAnimator.addListener(new AnimatorListenerAdapter()
                {
                    @Override
                    public void onAnimationEnd(final Animator animation)
                    {
                        // set maxLines to original value
                        ExpandableTextView.this.setMaxLines(ExpandableTextView.this.maxLines);
    
                        ExpandableTextView.this.expanded = false;
                        ExpandableTextView.this.animating = false;
                    }
                });
    
                // set interpolator
                valueAnimator.setInterpolator(this.collapseInterpolator);
    
                // start the animation
                valueAnimator
                    .setDuration(this.animationDuration)
                    .start();
    
                return true;
            }
    
            return false;
        }
    
        /**
         * Sets the duration of the expand / collapse animation.
         * @param animationDuration duration in milliseconds.
         */
        public void setAnimationDuration(final long animationDuration)
        {
            this.animationDuration = animationDuration;
        }
    
        /**
         * Sets a listener which receives updates about this {@link ExpandableTextView}.
         * @param onExpandListener the listener.
         */
        public void setOnExpandListener(final OnExpandListener onExpandListener)
        {
            this.onExpandListener = onExpandListener;
        }
    
        /**
         * Returns the {@link OnExpandListener}.
         * @return the listener.
         */
        public OnExpandListener getOnExpandListener()
        {
            return onExpandListener;
        }
    
        /**
         * Sets a {@link TimeInterpolator} for expanding and collapsing.
         * @param interpolator the interpolator
         */
        public void setInterpolator(final TimeInterpolator interpolator)
        {
            this.expandInterpolator = interpolator;
            this.collapseInterpolator = interpolator;
        }
    
        /**
         * Sets a {@link TimeInterpolator} for expanding.
         * @param expandInterpolator the interpolator
         */
        public void setExpandInterpolator(final TimeInterpolator expandInterpolator)
        {
            this.expandInterpolator = expandInterpolator;
        }
    
        /**
         * Returns the current {@link TimeInterpolator} for expanding.
         * @return the current interpolator, null by default.
         */
        public TimeInterpolator getExpandInterpolator()
        {
            return this.expandInterpolator;
        }
    
        /**
         * Sets a {@link TimeInterpolator} for collpasing.
         * @param collapseInterpolator the interpolator
         */
        public void setCollapseInterpolator(final TimeInterpolator collapseInterpolator)
        {
            this.collapseInterpolator = collapseInterpolator;
        }
    
        /**
         * Returns the current {@link TimeInterpolator} for collapsing.
         * @return the current interpolator, null by default.
         */
        public TimeInterpolator getCollapseInterpolator()
        {
            return this.collapseInterpolator;
        }
    
        /**
         * Is this {@link ExpandableTextView} expanded or not?
         * @return true if expanded, false if collapsed.
         */
        public boolean isExpanded()
        {
            return this.expanded;
        }
    
        public interface OnExpandListener
        {
            void onExpand(ExpandableTextView view);
            void onCollapse(ExpandableTextView view);
        }
    }
    

    Using the ExpandableTextView is very easy, it’s just a regular TextView with some extra functionality added to it. By defining the android:maxLines attribute, you can set the default number of lines for the TextView collapsed state.

    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <at.blogc.android.views.ExpandableTextView
            android:id="@+id/expandableTextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/lorem_ipsum"
            android:maxLines="5"
            android:ellipsize="end"
            app:animation_duration="1000"/>
    
        <!-- Optional parameter animation_duration: sets the duration of the expand animation -->
    
        <Button
            android:id="@+id/button_toggle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/expand"/>
    
    </LinearLayout>
    

    In your Activity or Fragment:

        final ExpandableTextView expandableTextView = (ExpandableTextView) this.findViewById(R.id.expandableTextView);
        final Button buttonToggle = (Button) this.findViewById(R.id.button_toggle);
    
        // set animation duration via code, but preferable in your layout files by using the animation_duration attribute
        expandableTextView.setAnimationDuration(1000L);
    
    // set interpolators for both expanding and collapsing animations
    expandableTextView.setInterpolator(new OvershootInterpolator());
    
    // or set them separately
    expandableTextView.setExpandInterpolator(new OvershootInterpolator());
    expandableTextView.setCollapseInterpolator(new OvershootInterpolator());
    
    
        // toggle the ExpandableTextView
        buttonToggle.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(final View v)
            {
                expandableTextView.toggle();
                buttonToggle.setText(expandableTextView.isExpanded() ? R.string.collapse : R.string.expand);
            }
        });
    
        // but, you can also do the checks yourself
        buttonToggle.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(final View v)
            {
                if (expandableTextView.isExpanded())
                {
                    expandableTextView.collapse();
                    buttonToggle.setText(R.string.expand);
                }
                else
                {
                    expandableTextView.expand();
                    buttonToggle.setText(R.string.collapse);
                }
            }
        });
    
        // listen for expand / collapse events
        expandableTextView.setOnExpandListener(new ExpandableTextView.OnExpandListener()
        {
            @Override
            public void onExpand(final ExpandableTextView view)
            {
                Log.d(TAG, "ExpandableTextView expanded");
            }
    
            @Override
            public void onCollapse(final ExpandableTextView view)
            {
                Log.d(TAG, "ExpandableTextView collapsed");
            }
        });
    

    You can easily add this library as a gradle dependency to your Android project. Take a look at the project on Github for further instructions:

    https://github.com/Blogcat/Android-ExpandableTextView

    0 讨论(0)
  • 2020-11-28 18:58

    Step 1

    <?xml version="1.0" encoding="utf-8"?>
    <shape
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
        <gradient
            android:startColor="#11FFFFFF"
            android:centerColor="#33FFFFFF"
            android:endColor="#99FFFFFF"
            android:angle="270" />
    </shape>
    

    Step 2

    <TextView
        android:id="@+id/overviewText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:maxLines="3"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="@+id/textView8"
        app:layout_constraintTop_toBottomOf="@+id/textView8" />
    
        <ImageView
            android:id="@+id/seeMoreImage"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:background="@drawable/background_white"
            android:visibility="invisible"
            app:layout_constraintBottom_toBottomOf="@+id/overviewText"
            app:layout_constraintEnd_toEndOf="@+id/overviewText"
            app:layout_constraintStart_toStartOf="@+id/overviewText"
            app:srcCompat="@drawable/ic_arrow_down"
            tools:ignore="VectorDrawableCompat" />
    

    Step 3

        var isTextViewClicked = true
        if (binding.overviewText.lineCount > 3)
            binding.seeMoreImage.visibility = View.VISIBLE
        binding.seeMoreImage.setOnClickListener {
            isTextViewClicked = if(isTextViewClicked){
                binding.overviewText.maxLines = Integer.MAX_VALUE
                binding.seeMoreImage.setImageResource(R.drawable.ic_arrow_up)
                false
            } else {
                binding.overviewText.maxLines = 3
                binding.seeMoreImage.setImageResource(R.drawable.ic_arrow_down)
                true
            }
        }
    
    0 讨论(0)
提交回复
热议问题