我有一个TextView
,它最初显示长文本的一小部分。
用户可以按下"查看更多"按钮来展开TextView
并查看剩余的文本。
在进行测试时,我可以通过简单地在折叠和展开之间交换TextView.setMaxLines
的值(4和Integer.MAX_VALUE)来实现。
现在,我希望这种行为能够伴随动画效果。我知道在这个问题中,一个解决方案已经几乎完成了,但是我尝试实施它却没有成功。
有人可以帮我吗?
我有一个TextView
,它最初显示长文本的一小部分。
用户可以按下"查看更多"按钮来展开TextView
并查看剩余的文本。
在进行测试时,我可以通过简单地在折叠和展开之间交换TextView.setMaxLines
的值(4和Integer.MAX_VALUE)来实现。
现在,我希望这种行为能够伴随动画效果。我知道在这个问题中,一个解决方案已经几乎完成了,但是我尝试实施它却没有成功。
有人可以帮我吗?
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: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
RecylerView
中也能够正常工作。 - CoolMindprivate 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();
}
}
创建简单的解决方案,不使用库和自定义类。
首先创建一个 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>
它看起来像这样:
下一步是为展开文本添加逻辑。我们将在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
中它看起来很丑。而且我喜欢没有任何动画的效果。
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属性,并使用上述任何答案来添加展开/折叠动画(我没有编写处理展开/折叠的代码,但可以轻松使用上述动画答案之一完成)。
我将此放在这里,更多是为了那些正在尝试为其文本视图找到“查看更多”功能的人。
步骤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
}
}
<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展开技术的最大行数和动画持续时间。