Android - 带动画的可展开TextView

70

我有一个TextView,它最初显示长文本的一小部分。

用户可以按下"查看更多"按钮来展开TextView并查看剩余的文本。

在进行测试时,我可以通过简单地在折叠和展开之间交换TextView.setMaxLines的值(4和Integer.MAX_VALUE)来实现。

现在,我希望这种行为能够伴随动画效果。我知道在这个问题中,一个解决方案已经几乎完成了,但是我尝试实施它却没有成功。

有人可以帮我吗?


更新一个新的方法来实现相同的功能,而不需要任何自定义视图,我已经在 https://dev59.com/4lwZ5IYBdhLWcg3wQ-Xk#64090510 中回答了。 - Kaveri
18个回答

87

您可以查看我的博客文章关于 ExpandableTexTView:

这个想法是,初始时 TextView 会显示长文本的一小部分,当它被点击时,它会显示剩余的文本。

下面是我解决问题的代码。

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

在您的 attr.xml 文件中添加以下行:

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

将以下内容放入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>

测试你的活动

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

参考: Android - 可展开的TextView


请注意,仅提供链接的答案是不被鼓励的,SO的答案应该是寻找解决方案的终点(而不是另一个参考站点,这些站点往往会随着时间的推移而过时)。请考虑在此处添加一个独立的摘要,将链接作为参考。 - kleopatra
13
这很有帮助,但似乎仅基于字符数截取文本。如果可以根据行数进行展开/折叠,那就更好了。 - greg7gkb
@greg7gkb 你好,你知道如何按行数展开/折叠吗? - Dory
4
这也没有回答关于动画部分的问题。 - blub
1
首先,这个答案是针对字符数而不是行数的,并且我想在列表视图中使用它,该视图保持展开和关闭状态。 - Avinash
显示剩余6条评论

81

使用ObjectAnimator

ObjectAnimator animation = ObjectAnimator.ofInt(yourTextView, "maxLines", tv.getLineCount());
animation.setDuration(200).start();

这将在200毫秒内完全展开您的TextView。您可以将tv.getLineCount()替换为您希望折叠回去的文本行数。

----更新----

这里是一些方便的方法,您可以直接使用:

private void expandTextView(TextView tv){
    ObjectAnimator animation = ObjectAnimator.ofInt(tv, "maxLines", tv.getLineCount());
    animation.setDuration(200).start();
}

private void collapseTextView(TextView tv, int numLines){
    ObjectAnimator animation = ObjectAnimator.ofInt(tv, "maxLines", numLines);
    animation.setDuration(200).start();
}

如果你的API版本在16或以上,你可以使用textView.getMaxLines()来轻松确定你的textView是否已经展开。

private void cycleTextViewExpansion(TextView tv){
    int collapsedMaxLines = 3;
    ObjectAnimator animation = ObjectAnimator.ofInt(tv, "maxLines", 
        tv.getMaxLines() == collapsedMaxLines? tv.getLineCount() : collapsedMaxLines);
    animation.setDuration(200).start();
}

注意:

如果maxLines未设置,或者您已经以像素为单位设置了textView的高度,则可能会出现ArrayIndexOutOfBounds异常。

以上示例无论扩展3行还是400行,始终需要200ms。如果您想要一个一致的扩展速率,可以尝试以下方式:

int duration = (textView.getLineCount() - collapsedMaxLines) * 10;

1
这是一个不错的解决方案,但需要注意的是最低可使用的API版本为11。 - Henrique de Sousa
4
截至2015年8月,支持API 11已覆盖95%以上的设备。 - Jim Pekarek
7
你是否找到了一种方法让你原本很好的解决方案能够与省略号一起使用?我希望用户在点击TextView后知道还有更多文本可见。但是,每当我尝试在XML或Java中设置TextView的 ellipsize 属性(设为'end'),TextView就无法再被点击并且也无法展开了。 - snark
1
我正在使用API +14,只是在寻找可能实现我想到的这个问题的方法。我认为这应该被接受为解决方案。简洁明了。 - Edison Spencer
谢谢您提供这些小代码,它们正好符合我的需求! :) - Julia Hexen
显示剩余3条评论

22

我创建了一个开源库来解决这个问题,因为在网上找到的其他解决方案并没有让我满意。我把这个东西放到了GitHub上,任何人都可以免费使用。

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

使用ExpandableTextView非常简单,它只是一个带有一些额外功能的TextView。通过定义android:maxLines属性,您可以设置TextView折叠状态下的默认行数。

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

在您的 Activity 或 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");
        }
    });

您可以轻松将此库作为Gradle依赖项添加到您的Android项目中。请查看Github上的项目以获取进一步的说明:

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


1
它对我不起作用。我已经设置了最大行数为4。在第一次加载时,我有4行。我点击文本,它会展开。然后我再点击折叠,它会折叠到4行,然后再展开。然后如果我再次点击,什么也不会发生。文本保持展开状态。请参见下面的代码片段。 - parohy
expandText.setInterpolator(new OvershootInterpolator()); expandText.setMaxLines(4); expandText.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if(expandText.isExpanded()){ expandText.collapse(); } else{ expandText.expand(); } } }); - parohy
你能否在 https://github.com/Blogcat/Android-ExpandableTextView/issues 上报告你的问题? - Cliffus
好的,我已经在 GitHub 中回复了一个重复的相同问题。 - parohy
这显然提出了一个清晰的想法,但我想补充一下当没有足够的空间来显示未修剪的文本视图时的情况。在这种情况下,getMeasuredHeight() 不再正确。因此,我建议您自己进行控制。我使用文本视图的行高乘以行数(记得加上行间距)来获取文本视图的适当高度。 - Nguyen Tan Dat

7
如果你想根据行数来做,这里有一种方法:
(完整代码的Gist
/**
 * Ellipsize the text when the lines of text exceeds the value provided by {@link #makeExpandable} methods.
 * Appends {@link #MORE} or {@link #LESS} as needed.
 * TODO: add animation
 * Created by vedant on 3/10/15.
 */
public class ExpandableTextView extends TextView {
    private static final String TAG = "ExpandableTextView";
    private static final String ELLIPSIZE = "... ";
    private static final String MORE = "more";
    private static final String LESS = "less";

    private String mFullText;
    private int mMaxLines;

    //...constructors...

    public void makeExpandable(String fullText, int maxLines) {
        mFullText =fullText;
        mMaxLines = maxLines;
        ViewTreeObserver vto = getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                ViewTreeObserver obs = getViewTreeObserver();
                obs.removeOnGlobalLayoutListener(this);
                if (getLineCount() <= maxLines) {
                    setText(mFullText);
                } else {
                    setMovementMethod(LinkMovementMethod.getInstance());
                    showLess();
                }
            }
        });
    }

    /**
     * truncate text and append a clickable {@link #MORE}
     */
    private void showLess() {
        int lineEndIndex = getLayout().getLineEnd(mMaxLines - 1);
        String newText = mFullText.substring(0, lineEndIndex - (ELLIPSIZE.length() + MORE.length() + 1))
                + ELLIPSIZE + MORE;
        SpannableStringBuilder builder = new SpannableStringBuilder(newText);
        builder.setSpan(new ClickableSpan() {
            @Override
            public void onClick(View widget) {
                showMore();
            }
        }, newText.length() - MORE.length(), newText.length(), 0);
        setText(builder, BufferType.SPANNABLE);
    }

    /**
     * show full text and append a clickable {@link #LESS}
     */
    private void showMore() {
        // create a text like subText + ELLIPSIZE + MORE
        SpannableStringBuilder builder = new SpannableStringBuilder(mFullText + LESS);
        builder.setSpan(new ClickableSpan() {
            @Override
            public void onClick(View widget) {
                showLess();
            }
        }, builder.length() - LESS.length(), builder.length(), 0);
        setText(builder, BufferType.SPANNABLE);
    }
}

你可以使用post方法代替addOnGlobalLayoutListener。 - Andriy Antonov
@AndriyAntonov 如果在父元素布局之前调用该方法,那么它将无法正常工作。我们只能在确定TextView的宽度后才能调用它。 - Vedant Agarwala

7

平滑扩展(使用高度和ObjectAnimator
FYI:需要API 11

public static void expandCollapsedByMaxLines(@NonNull final TextView text) {
    final int height = text.getMeasuredHeight();
    text.setHeight(height);
    text.setMaxLines(Integer.MAX_VALUE); //expand fully
    text.measure(View.MeasureSpec.makeMeasureSpec(text.getMeasuredWidth(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(ViewGroup.LayoutParams.WRAP_CONTENT, View.MeasureSpec.UNSPECIFIED));
    final int newHeight = text.getMeasuredHeight();
    ObjectAnimator animation = ObjectAnimator.ofInt(text, "height", height, newHeight);
    animation.setDuration(250).start();
}

注:我假设TextView被maxLines所限制。
注:感谢Amagi82提供ObjectAnimator示例。


尝试使用“collapseExpandable”函数来找出该函数的反向操作。有什么想法吗? - Damiii
1
最后,我找到了一个做得非常好的兄弟:https://github.com/Blogcat/Android-ExpandableTextView。 - Damiii

5
您可以使用新的TransitionManager进行动画操作,并通过设置maxLines属性来调整显示的行数。
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)
}

首先它会闪烁一下,然后在大约200-500毫秒内扩展一个TextView。如果你在RecyclerView中扩展TextView,那么在滚动期间新的TextView将被扩展。要重置TextView,请在RecyclerView中使用textView.setLines(2)。 - CoolMind

3
您可以像这样做。它适用于任何类型的视图,无论是普通视图还是ListView或RecyclerView中的视图:
在 onCreate() 或类似方法中添加以下内容:
// initialize integers
int collapsedHeight, expandedHeight;

// get collapsed height after TextView is drawn
textView.post(new Runnable() {
    @Override
    public void run() {
        collapsedHeight = textView.getMeasuredHeight();
    }
});

// view that will expand/collapse your TextView
view.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        // number of max lines when collapsed
        if (textView.getMaxLines() == 2) {
            // expand
            textView.setMaxLines(Integer.MAX_VALUE);
            textView.measure(View.MeasureSpec.makeMeasureSpec(notifMessage.getMeasuredWidth(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED));
            expandedHeight = textView.getMeasuredHeight();
            ObjectAnimator animation = ObjectAnimator.ofInt(textView, "height", collapsedHeight, expandedHeight);
            animation.setDuration(250).start();
        } else {
            // collapse
            ObjectAnimator animation = ObjectAnimator.ofInt(textView, "height", expandedHeight, collapsedHeight);
            animation.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animator) {

                }

                @Override
                public void onAnimationEnd(Animator animator) {
                    // number of max lines when collapsed
                    textView.setMaxLines(2);
                }

                @Override
                public void onAnimationCancel(Animator animator) {

                }

                @Override
                public void onAnimationRepeat(Animator animator) {

                }
            });
            animation.setDuration(250).start();
        }
    }
});

这将让您通过单击任何您想要的视图(当然也可以选择TextView本身)来展开/折叠TextView。

虽然它能工作,但如果您在RecyclerView内展开TextView,则在滚动期间会展开新的TextView。要重置TextView,请在RecyclerView中使用textView.setLines(2) - CoolMind

3

以下是我使用上述一些答案后的解决方案(此示例中我使用ButterKnife):

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

当用户点击描述时,它将根据最大行数折叠或展开。这仅适用于API 16及以上版本。 我遇到的问题是在某些点上行数返回为零,并且行数和最大计数在某些点上是相同的值。

2
请参考以下链接,了解带有行数和更多/更少文本选项的可扩展TextView。 可调整大小的TextView(查看更多和查看更少) 在您的Java类中,在设置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

1
在您的应用模块gradle中添加依赖项。
dependencies {
          implementation 'com.github.arshadbinhamza:ViewMore:1.0.9'
}

 //        ViewMoreHolder.load(textView_description,text, Typeface of end Text,UnderLine,number_of_lines,click_for_end_text_only);
  //  ViewMoreHolder.load(tv_description,description, Typeface.DEFAULT,true,3,false);

请看我添加的示例(它是从先前答案中提取出来的我的应用程序要求的解决方案)。我们可以根据要求更新/增强库。 https://github.com/arshadbinhamza/ViewMore

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接