Android - 带动画的可展开TextView

70

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

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

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

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

有人可以帮我吗?


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

1

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

我已经派生了他们的库,使得使用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。 已搜索以下位置: - https://dl.google.com/dl/android/maven2/com/github/zacharee/Android-ExpandableTextView/1.0.5/... - https://jcenter.bintray.com/com/github/zacharee/Android-ExpandableTextView/1.0.5/... - https://jitpack.io/com/github/zacharee/Android-ExpandableTextView/1.0.5/Android-ExpandableTextView-1.0.5.pom
- CoolMind
如果您从GitHub下载一个示例,它将可以正常工作。另外,它在RecylerView中也能够正常工作。 - CoolMind

0
在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方法在哪里? - Brill Pappin

0

创建简单的解决方案,不使用库和自定义类。

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

...

<TextView
    android:id="@+id/item_info_text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    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_width="match_parent"
    android:layout_height="wrap_content"
    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>

它看起来像这样:

XML layout preview

下一步是为展开文本添加逻辑。我们将在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 中它看起来很丑。而且我喜欢没有任何动画的效果。


0

0

我知道可能有点晚了。关于实现“查看更多”操作的部分,我写了一篇博客文章在这里。基本上,我使用了静态布局来进行所有文本测量。然而,博客文章并没有涵盖动画部分。正如许多人指出的那样,我们可以使用ValueAnimator或ObjectAnimator来实现。您可以在这个代码库中找到完整的可扩展文本视图动画代码,甚至可以直接使用该库。


0
主要是为了在文本末尾添加“查看更多”按钮,我向您展示我的 TruncatingTextView。经过多次实验,当在 RecyclerView 的 item view 中加载这些文本视图时,它似乎能够无缝运行。
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属性,并使用上述任何答案来添加展开/折叠动画(我没有编写处理展开/折叠的代码,但可以轻松使用上述动画答案之一完成)。

我将此放在这里,更多是为了那些正在尝试为其文本视图找到“查看更多”功能的人。


嘿!你能示范一下如何将文本设置到这个textview吗? - Hardik Joshi
1
严肃点吧?你不知道如何从代码中设置文本吗?人们,读一下啊! - Chantell Osejo
你所需要做的就是调用setText("我想要截断的超级长的字符串blah blah blah", "查看更多", 2);。 - Chantell Osejo
这将导致TextView将字符串截断为适合2行的任何内容,包括在截断字符串末尾附加的“查看更多”文本。 - Chantell Osejo

0

enter image description here

enter image description here

步骤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_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" />

步骤三

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

2
动画在哪里? - CoolMind

0
现在,使用这个神奇的库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_width="match_parent"
      android:layout_height="wrap_content"
      expandableTextView:maxCollapsedLines="4"
      expandableTextView:animDuration="200">
      <TextView
          android:id="@id/expandable_text"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:layout_marginLeft="10dp"
          android:layout_marginRight="10dp"
          android:textSize="16sp"
          android:textColor="#666666" />
      <ImageButton
          android:id="@id/expand_collapse"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          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展开技术的最大行数和动画持续时间。


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