I have textview which contains a part of a text. When the user clicks the arrow, the textview resizes so the full text is shown. See the images below for an example:
The TextView has a wrap_content height, and when collapsed a maxLines="4".
The onClick of the arrow contains this code:
if (isExpanded) {
btnToggle.setImageDrawable(getResources().getDrawable(
R.drawable.arrow_down));
tvText.setMaxLines(4);
tvText.setEllipsize(TruncateAt.END);
} else {
btnToggle.setImageDrawable(getResources().getDrawable(
R.drawable.arrow_up));
tvText.setMaxLines(Integer.MAX_VALUE);
tvText.setEllipsize(null);
}
isExpanded = !isExpanded;
This code works, but it is not animated. I need to animate the expansion, so the TextView animates to it's full height. I can't find anything about animating properties like MaxLines. Who can help me out?
You can achieve this using an ObjectAnimator
ObjectAnimator animation = ObjectAnimator.ofInt(
tvText,
"maxLines",
25);
animation.setDuration(4000);
animation.start();
This will increase the "maxLines" property of the "tvText" TextView from whatever it initially is set to, to 25, over the period of 4000 milliseconds.
See more here and here.
While animating maxLines
works, the result is a bit choppy since your view height jumps a lot.
int startHeight = content.getMeasuredHeight();
content.setMaxLines(Integer.MAX_VALUE);
content.measure(
View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.AT_MOST),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
int endHeight = content.getMeasuredHeight();
content
is a TextView
with maxLines
set to 2. Now you can animate the TextView
height instead.
Edit: TextView
scrolls when it can't fit it's content vertically, you'll need workarounds. setMovementMethod(null)
disables scrolling, but it also disables link clicking. Because Android.
The accepted answer is fundamentally wrong, because it forces the call of TextView.setMaxLines() lots of times with the same value without reason, making the resulting animation jerky.
You could use a simple ValueAnimator with a lastValue flag instead:
ValueAnimator animator = ValueAnimator.ofInt(fromThisLineCount, toThisLineCount).setDuration(250);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
int lastValue = -1;
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
if (value == lastValue) {
return;
}
lastValue = value;
yourTextView.setMaxLines(value);
}
});
animator.start();
Remember that the animation adds/removes 1 whole line every time, so it may still look jerky if the animation duration is too high or if it affects just a few lines. The best approach is to create a custom View and make the appropriate measurements onMeasure (see e.g. https://github.com/Manabu-GT/ExpandableTextView).
All of the other solutions didn't work nicely for me. Animation was choppy or even blinking.
What I chose to do is animating of the layoutParams height instead. This solution might not fit for every case, but for me it seems to work nicely. Here's a demonstration:
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val expectedWidthOfTextView = resources.displayMetrics.widthPixels
val originalMaxLines = textView.maxLines
if (originalMaxLines < 0 || originalMaxLines == Integer.MAX_VALUE)
Log.d("AppLog", "already unbounded textView maxLines")
else {
textView.maxLines = Integer.MAX_VALUE
textView.measure(
View.MeasureSpec.makeMeasureSpec(expectedWidthOfTextView, View.MeasureSpec.AT_MOST),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
)
val measuredLineCount = textView.lineCount
val measuredTargetHeight = textView.measuredHeight
Log.d("AppLog", "lines:$measuredLineCount/$originalMaxLines")
textView.maxLines = originalMaxLines
if (measuredLineCount <= originalMaxLines)
Log.d("AppLog", "fit in original maxLines")
else {
Log.d("AppLog", "exceeded original maxLines")
textView.setOnClickListener {
textView.setOnClickListener(null)
textView.maxLines = Integer.MAX_VALUE
val layoutParams = textView.layoutParams
val animation = ValueAnimator.ofInt(textView.height, measuredTargetHeight)
animation.addUpdateListener { valueAnimator ->
val value: Int = valueAnimator.animatedValue as Int
layoutParams.height = value
textView.requestLayout()
}
animation.start()
layoutParams.height = textView.height
}
}
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:orientation="vertical" android:gravity="center_horizontal"
android:layout_height="match_parent" android:animateLayoutChanges="true"
tools:context=".MainActivity">
<ImageView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:src="@android:drawable/sym_def_app_icon"/>
<TextView
android:id="@+id/textView" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ellipsize="end" android:maxLines="4" android:clickable="true" android:focusable="true"
android:paddingEnd="16dp" android:paddingStart="16dp"
android:textColor="#c1000000" android:textSize="14sp"
android:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."/>
<ImageView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:src="@android:drawable/sym_def_app_icon"/>
</LinearLayout>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With