Android - 带有动画的可扩展 TextView

Posted

技术标签:

【中文标题】Android - 带有动画的可扩展 TextView【英文标题】:Android - Expandable TextView with Animation 【发布时间】:2013-03-15 16:26:51 【问题描述】:

我有一个TextView,它首先显示一小部分长文本。

用户可以按下“查看更多”按钮来展开 TextView 并查看该文本的其余部分。

进行测试,我可以通过简单地将 TextView.setMaxLines 的值在 4 用于折叠和 Integer.MAX_VALUE 用于展开来实现。

现在,我希望这种行为伴随着动画。我知道在this question 中几乎完成了一个解决方案,但我尝试实施它并没有成功。

有人可以帮我解决这个问题吗?

【问题讨论】:

更新一个新方法来达到同样的效果,无需任何自定义视图,我已经回答了***.com/a/64090510/7200713。 【参考方案1】:

你可以查看我在 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_
     android:layout_>

     <com.rokonoid.widget.ExpandableTextView
         android:id="@+id/lorem_ipsum"
         android:layout_
         android:layout_/>

 </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 – Expandable TextView

【讨论】:

请注意,link-only answers are discouraged,SO 答案应该是搜索解决方案的终点(相对于另一个参考中途停留,随着时间的推移往往会变得陈旧)。请考虑在此处添加独立的概要,并保留链接作为参考。 这很有帮助,但似乎只是根据字符数来剪辑文本。如果它可以按行数展开/折叠就好了。 @greg7gkb 你好,你知道如何按行数展开/折叠..? 这也不回答动画部分。 首先这个答案是否定的。字符而不是行,我想在 listview 中使用它。它保持扩展和关闭等状态【参考方案2】:

使用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 尚未设置,或者您已设置 set the height of your textView in pixels,您可能会得到 ArrayIndexOutOfBounds 异常。

上面的例子总是需要 200ms,无论是扩展 3 行还是 400 行。如果你想要一个一致的扩展率,你可以这样做:

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

【讨论】:

这是一个不错的解决方案,但仅供参考,使用的最低 API 为 11。 截至 2015 年 8 月,支持 API 11 的设备覆盖了超过 95% 的设备。 您是否找到了一种方法来使您原本出色的解决方案与省略号一起使用?我希望用户知道是否有更多文本可以查看他们是否单击 TextView。但是,每当我尝试在 XML 或 Java 中设置 TextView 的 ellipsize 属性(设置为“结束”)时,TextView 不再可点击,您也无法再展开它。 我正在使用 API +14,我只是在检查我想到的这个问题的可能实现。我认为这绝对应该被接受为解决方案。干净整洁。 感谢这些小代码行,它们正是我想要的! :)【参考方案3】:

我为此创建了一个开源库,因为我对在互联网上找到的其他解决方案不满意。我已经把它放在了 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_
    android:layout_
    android:orientation="vertical">

    <at.blogc.android.views.ExpandableTextView
        android:id="@+id/expandableTextView"
        android:layout_
        android:layout_
        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_
        android:layout_
        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

【讨论】:

它对我不起作用。我设置了最大行数 4。第一次加载时我有 4 行。我点击文本,它会展开。比我点击折叠,它折叠到 4,然后展开回来。如果我再次点击什么都不会发生。文本保持扩展。下一条评论中的片段 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(); ); 您可以在github.com/Blogcat/Android-ExpandableTextView/issues 上报告您的问题吗? 好的,我已经在github上回复了一个重复的相同问题。 显然,这提出了一个清晰的想法,但是当没有足够的空间用于未修剪的文本视图时,我想添加实例。在这种情况下, getMeasuredHeight() 不再正确。所以我建议你应该自己控制。我使用文本视图的行高乘以行数(记得加上行距)来获得文本视图的正确高度。【参考方案4】:

平滑扩展(使用高度和ObjectAnimator) 仅供参考:需要 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 限制。 P.S.S.感谢 Amagi82 ObjectAnimator 示例

【讨论】:

尝试用“collapseExpandable”找出这个函数的反面。有什么想法吗? 最后,我找到了一个做得很好的兄弟:github.com/Blogcat/Android-ExpandableTextView。【参考方案5】:

如果你想根据行数来做,这里有一个方法:

(完整代码的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);
    

【讨论】:

而不是 addOnGlobalLayoutListener 你可以使用 post 方法 @AndriyAntonov 如果方法被调用之前父布局将不起作用。我们只能在确定 TextVIew 的宽度后调用它。【参考方案6】:

以下是使用上述一些响应对我有用的方法(我在示例中使用的是 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+。

我遇到的问题是行数在某些点返回零,而行数和最大计数在某些点是相同的值。

【讨论】:

【参考方案7】:

你可以做这样的事情。它适用于任何类型的视图,无论是普通视图,还是 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,新的TextViews 将在滚动期间展开。要重置TextView,请在RecyclerView 中使用textView.setLines(2)【参考方案8】:

动画可以使用新的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)

【讨论】:

首先它会闪烁一点,然后扩展 TextView 大约 200-500 毫秒。如果您在RecyclerView 内展开TextView,新的TextViews 将在滚动期间展开。要重置TextView,请在RecyclerView 中使用textView.setLines(2)【参考方案9】:

请参阅下面的链接以获取可扩展的 TextView,其中包含多行和更多文本的选项。

Resizeable Text View(View More and View Less)

在 TextView 中设置文本后,在 Java 类中添加以下行。

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

【讨论】:

【参考方案10】:

Cliffus' answer 与我所寻找的很接近,但它不支持使用 setMaxLines() 方法,当您无法通过 XML 设置最大行数时会导致问题。

我已经 forked their library 并做到了,因此使用 setMaxLines() 不会破坏展开/折叠操作。我还更新了 Gradle 配置并将其迁移到 AndroidX。否则,用法和以前一样。

您可以使用 Jitpack 将其包含在您的项目中:

allprojects 
    repositories 
        ...
        maven  url 'https://jitpack.io' 
    

dependencies 
    implementation 'com.github.zacharee:Android-ExpandableTextView:Tag'

Tag 是最新的提交标签 (https://jitpack.io/#zacharee/Android-ExpandableTextView/)。

用法与原库完全相同。在您的 XML 中包含 ExpandableTextView:

<at.blogc.android.views.ExpandableTextView
    ...
    android:maxLines="10"
    />

并在代码中展开/折叠:

if (expandable.isExpanded) 
    expandable.collapse()
else 
    expandable.expand()

【讨论】:

任务 ':app:checkDebugAarMetadata' 执行失败。 > 无法解析配置“:app:debugRuntimeClasspath”的所有文件。 > 找不到 com.github.zacharee:Android-ExpandableTextView:1.0.5。在以下位置搜索:-dl.google.com/dl/android/maven2/com/github/zacharee/…...-jcenter.bintray.com/com/github/zacharee/…...-jitpack.io/com/github/zacharee/Android-ExpandableTextView/1.0.5/… 如果您从 GitHub 下载示例,它将起作用。它也适用于RecylerView【参考方案11】:

在 ListView 或 RecyclerView 中,我们总是使用 OnPreDrawListener 而不是使用 OnGlobalLayoutListener。在开始时也会为不可见的行触发此回调。来自官方文档:

private void makeTextViewResizable(final TextView tv, final int maxLine, final String expandText, final boolean viewMore)
        try 
            if (tv.getTag() == null) 
                tv.setTag(tv.getText());
            
            //OnGlobalLayoutListener
            ViewTreeObserver vto = tv.getViewTreeObserver();
            vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() 

                @Override
                public boolean onPreDraw() 

                        ViewTreeObserver obs = tv.getViewTreeObserver();
                       // obs.removeGlobalOnLayoutListener((ViewTreeObserver.OnGlobalLayoutListener) mActivity);
                        obs.removeOnPreDrawListener(this);
                        if (maxLine == 0) 
                            int lineEndIndex = tv.getLayout().getLineEnd(0);
                            String text = tv.getText().subSequence(0, lineEndIndex - expandText.length() + 1) + " " + expandText;
                            tv.setText(text);
                            tv.setMovementMethod(LinkMovementMethod.getInstance());
                            tv.setText(
                                    addClickablePartTextViewResizable(html.fromHtml(tv.getText().toString()), tv, expandText,
                                            viewMore), TextView.BufferType.SPANNABLE);
                         else if (maxLine > 0 && tv.getLineCount() >= maxLine) 
                            int lineEndIndex = tv.getLayout().getLineEnd(maxLine - 1);
                            String text = tv.getText().subSequence(0, lineEndIndex - expandText.length() + 1) + " " + expandText;
                            tv.setText(text);
                            tv.setMovementMethod(LinkMovementMethod.getInstance());
                            tv.setText(
                                    addClickablePartTextViewResizable(Html.fromHtml(tv.getText().toString()), tv, expandText,
                                            viewMore), TextView.BufferType.SPANNABLE);
                         else 
                            int lineEndIndex = tv.getLayout().getLineEnd(tv.getLayout().getLineCount() - 1);
                            String text = tv.getText().subSequence(0, lineEndIndex) + " " + expandText;
                            tv.setText(text);
                            tv.setMovementMethod(LinkMovementMethod.getInstance());
                            tv.setText(
                                    addClickablePartTextViewResizable(Html.fromHtml(tv.getText().toString()), tv, expandText,
                                            viewMore), TextView.BufferType.SPANNABLE);
                        


                    return true;
                


            );
         catch (Exception e) 
            e.printStackTrace();
        

    

【讨论】:

addClickablePartTextViewResizable 方法在哪里?【参考方案12】:

主要是为了在文本末尾添加“查看更多”的情况,我向您展示我的截断文本视图。经过大量实验后,在 RecyclerView 项目视图中加载这些文本视图时,它似乎可以无缝工作。

package com.example.android.widgets;

import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v7.widget.AppCompatTextView;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan;
import android.util.AttributeSet;

import com.example.android.R;

public class TruncatingTextView extends AppCompatTextView 
    public static final String TWO_SPACES = "  ";

    private int truncateAfter = Integer.MAX_VALUE;

    private String suffix;
    private RelativeSizeSpan truncateTextSpan = new RelativeSizeSpan(0.75f);
    private ForegroundColorSpan viewMoreTextSpan = new ForegroundColorSpan(Color.BLUE);
    private static final String MORE_STRING = getContext().getString(R.string.more);

    private static final String ELLIPSIS = getContext().getString(R.string.ellipsis);

    public TruncatingTextView(Context context) 
        super(context);
    

    public TruncatingTextView(Context context, AttributeSet attrs) 
        super(context, attrs);
    

    public TruncatingTextView(Context context, AttributeSet attrs, int defStyleAttr) 
        super(context, attrs, defStyleAttr);
    

    public void setText(CharSequence fullText, @Nullable CharSequence afterTruncation, int truncateAfterLineCount) 
        this.suffix = TWO_SPACES + MORE_STRING;

        if (!TextUtils.isEmpty(afterTruncation)) 
            suffix += TWO_SPACES + afterTruncation;
        

        // Don't call setMaxLines() unless we have to, since it does a redraw.
        if (this.truncateAfter != truncateAfterLineCount) 
            this.truncateAfter = truncateAfterLineCount;
            setMaxLines(truncateAfter);
        

        setText(fullText);
    

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) 
        super.onLayout(changed, left, top, right, bottom);

        if (getLayout() != null && getLayout().getLineCount() > truncateAfter) 
            int lastCharToShowOfFullTextAfterTruncation = getLayout().getLineVisibleEnd(truncateAfter - 1) - suffix.length() - ELLIPSIS.length();

            if (getText().length() <= lastCharToShowOfFullTextAfterTruncation) 
                // No idea why this would be the case, but to prevent a crash, here it is. Besides, if this is true, we should be less than our maximum lines and thus good to go.
                return;
            

            int startIndexOfMoreString = lastCharToShowOfFullTextAfterTruncation + TWO_SPACES.length() + 1;

            SpannableString truncatedSpannableString = new SpannableString(getText().subSequence(0, lastCharToShowOfFullTextAfterTruncation) + ELLIPSIS + suffix);
            truncatedSpannableString.setSpan(truncateTextSpan, startIndexOfMoreString, truncatedSpannableString.length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
            truncatedSpannableString.setSpan(viewMoreTextSpan, startIndexOfMoreString, startIndexOfMoreString + MORE_STRING.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            setText(truncatedSpannableString);
        
    

您始终可以选择为 truncateAfter 添加自己的属性,并使用上述任何答案来添加展开/折叠动画(我没有处理展开/折叠的代码,但使用上述动画之一很容易完成答案)。

我把这个放在这里更多是为了那些试图为他们的文本视图找到“查看更多”功能的人。

【讨论】:

嘿!你能举例说明如何将 Text 设置为这个 textview 吗? 认真的吗?您无法弄清楚如何从代码中设置文本?人,阅读! 您所要做的就是调用 setText("超长字符串我想要截断等等等等", "查看更多", 2); 这将导致 textview 将字符串截断为适合 2 行的任何内容,包括附加在截断字符串末尾的“查看更多”。【参考方案13】:

现在,使用这个很棒的库 ExpandableTextView 为请求的 TextView 提供动画和所有必需的控件变得更加容易,在这个库中,您只需将其添加到您的 gradle 中,然后在您的xml:

  <com.ms.square.android.expandabletextview.ExpandableTextView
      xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:expandableTextView="http://schemas.android.com/apk/res-auto"
      android:id="@+id/expand_text_view"
      android:layout_
      android:layout_
      expandableTextView:maxCollapsedLines="4"
      expandableTextView:animDuration="200">
      <TextView
          android:id="@id/expandable_text"
          android:layout_
          android:layout_
          android:layout_marginLeft="10dp"
          android:layout_marginRight="10dp"
          android:textSize="16sp"
          android:textColor="#666666" />
      <ImageButton
          android:id="@id/expand_collapse"
          android:layout_
          android:layout_
          android:padding="16dp"
          android:layout_gravity="right|bottom"
          android:background="@android:color/transparent"/>
  </com.ms.square.android.expandabletextview.ExpandableTextView>

然后在您的代码中使用它,例如:

TextView expandableTextView = (ExpandableTextView) findViewById(R.id.expand_text_view);

如您所见,您可以控制所需的最大行数、动画持续时间以及 TextView 扩展技术所需的所有设置。

【讨论】:

【参考方案14】:

这是一个类似方法的仓库: https://github.com/CorradiSebastian/ExpandableTextView

从这个问题中得出:

Custom Expandable TextView

【讨论】:

【参考方案15】:

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

第 2 步

<TextView
    android:id="@+id/overviewText"
    android:layout_
    android:layout_
    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_
        android:layout_
        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" />

第 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
        
    

【讨论】:

动画在哪里?【参考方案16】:

创建没有库和自定义类的简单解决方案。

首先使用(例如)两个TextView 创建item.xml。一个用于显示文本,它将被展开,另一个用于按钮 - “显示更多”。

...

<TextView
    android:id="@+id/item_info_text"
    android:layout_
    android:layout_
    android:textSize="16sp"
    tools:text="Test long text info\nTest long text info\nTest long text info\nTest long text info | Test long text info | Test long text info"
    android:maxLines="@integer/info_collected_lines"
    android:fontFamily="@string/font_roboto_regular"
    android:textColor="@color/text_second"
    android:layout_marginTop="8dp"
    android:ellipsize="end"/>

<TextView
    android:id="@+id/item_more_text"
    android:layout_
    android:layout_
    android:textSize="12sp"
    android:text="@string/see_more"
    android:singleLine="true"
    android:fontFamily="@string/font_roboto_regular"
    android:textColor="@color/text_accent"
    android:ellipsize="marquee"/>

...

其他资源:

<color name="text_accent">#0070AA</color>
<color name="text_second">#616161</color>

<string name="font_roboto_regular" translatable="false">sans-serif</string>
<string name="font_roboto_medium" translatable="false">sans-serif-medium</string>

<string name="see_more">Show more</string>

<integer name="club_info_collected_lines">4</integer>
<integer name="club_info_expanded_lines">10</integer>

它看起来像这样:

下一步是为展开文本添加逻辑。我们在RecyclerView.ViewHolder 里面做:

class ItemHolder(view: View) : RecyclerView.ViewHolder(view) 

    ...

    private val infoText = view.findViewById<TextView>(R.id.item_info_text)
    private val moreText = view.findViewById<TextView>(R.id.item_more_text)

    fun bind(item: Item, callback: Callback) 
        infoText.text = item.info
        
        // This is extension (show code later) need for getting correct [TextView.getLineCount]. Because before draw view it always == 0.
        infoText.afterLayoutConfiguration 
            val hasEllipsize = infoText.layout.getEllipsisCount(infoText.lineCount - 1) > 0

            moreText.visibility = if (hasEllipsize) View.VISIBLE else View.GONE

            if (hasEllipsize) 
                val maxLines = itemView.context.resources.getInteger(R.integer.club_info_expanded_lines)
                moreText.setOnClickListener 
                    infoText.maxLines = maxLines
                    it.visibility = View.GONE
                
            
        

        ...
    

    // Call this inside [RecyclerView.Adapter.onViewRecycled] for prevent memory leaks.
    fun unbind() 
        moreText.setOnClickListener(null)
    

扩展名:

/**
 * Function for detect when layout completely configure.
 */
fun View.afterLayoutConfiguration(func: () -> Unit) 
    viewTreeObserver?.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener 
        override fun onGlobalLayout() 
            viewTreeObserver?.removeOnGlobalLayoutListener(this)
            func()
        
    )

我尝试在TransitionManager.beginDelayedTransition 中使用动画,但在RecyclerView 内部看起来很难看。就像没有任何动画的样子。

【讨论】:

【参考方案17】:

在你的应用模块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

【讨论】:

以上是关于Android - 带有动画的可扩展 TextView的主要内容,如果未能解决你的问题,请参考以下文章

使用最小高度的可扩展列表的可访问性问题

带有图像的可扩展字符串不适用于推送通知

带有对象数组的可扩展动态嵌套列表

带有组和子复选框的可扩展列表视图给出空指针异常

HTML5+Three.js实现的可拖拽镜面反射与球面折射全景动画

带有分页的可扩展嵌套 Angular 材料数据表未按预期工作?