I use the below two methods (inspired/copied from here) to expand and collapse some TextViews in a ScrollView by clicking on the "header"-TextView.
Pseudo layout structure:
<ScrollView>
<LinearLayout>
<LinearLayout>
<!-- some other stuff here -->
</LinearLayout>
<TextView "header1"/>
<View "fancydivider"/>
<TextView "content1">
<TextView "header2"/>
<View "fancydivider"/>
<TextView "content2">
</LinearLayout>
</ScrollView>
Divider is a simple View, heightset to 1dp. The content-TextViews style includes:
<item name="android:layout_height">0dp</item>
<item name="android:layout_width">match_parent</item>
and some margin & padding.
Methods here:
public static void expand(final View v) {
//v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
int matchParentMeasureSpec = View.MeasureSpec.makeMeasureSpec(((View) v.getParent()).getWidth(), View.MeasureSpec.EXACTLY);
int wrapContentMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
v.measure(matchParentMeasureSpec, wrapContentMeasureSpec);
final int targetHeight = v.getMeasuredHeight();
// Older versions of android (pre API 21) cancel animations for views with a height of 0.
v.getLayoutParams().height = 1;
v.setVisibility(View.VISIBLE);
Animation a = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
v.getLayoutParams().height = interpolatedTime == 1
? ViewGroup.LayoutParams.WRAP_CONTENT
: (int) (targetHeight * interpolatedTime);
scrollView.smoothScrollTo(0, (int) (targetHeight * interpolatedTime));
v.requestLayout();
}
@Override
public boolean willChangeBounds() {
return true;
}
};
a.setInterpolator(easeInOutQuart);
a.setDuration(computeDurationFromHeight(v));
v.startAnimation(a);
}
public static void collapse(final View v) {
final int initialHeight = v.getMeasuredHeight();
Animation a = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (interpolatedTime == 1) {
v.setVisibility(View.GONE);
} else {
v.getLayoutParams().height = initialHeight - (int) (initialHeight * interpolatedTime);
v.requestLayout();
}
}
@Override
public boolean willChangeBounds() {
return true;
}
};
a.setInterpolator(easeInOutQuart);
a.setDuration(computeDurationFromHeight(v));
v.startAnimation(a);
}
private static int computeDurationFromHeight(View view) {
// 1dp/ms * multiplier
return (int) (view.getMeasuredHeight() / view.getContext().getResources().getDisplayMetrics().density) * 4;
}
Problem here: Everything works fine - until the expand animation reaches the last line of text - if there are too few characters on it, then it lags, jumps, explodes? - however you want to call it - until fully expanded.
Collapsing seems to work fine.
I tried other Interpolator values, another multiplier in method computeDurationFromHeight.
Some testing:
4 lines, on fourth line everything more than 17 chars works fine, fewer than 18 chars and it lags.
3 lines and irrelevant amount of chars on the last line working fine.
sometimes the
animationworks on firstexpand, but not on second.- It seems that the
TextViewgets calculated wrong. With a highmultiplierI have seen sometextplopping up for < 0.5s over the next headerTextView - removing the
smoothScrollToinexpanddoes not change anything (except scrolling of course..) - other interpolators also have 'hiccups', but shorter
- some Logging in
applyTransformation(see below) got me to the point, that I see thatfinal heightis printed twice - with exactly 50 points(pixels? dp?) difference.//smoothly increasing height and then:final height = 202 height = 252 final height = 252While I gettargetHeight = 203- so theheightgets calculated wrong first, but then some magic happens?
important:
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
v.getLayoutParams().height = interpolatedTime == 1
? ViewGroup.LayoutParams.WRAP_CONTENT
: (int) (targetHeight * interpolatedTime);
v.requestLayout();
scrollView.smoothScrollTo(0, interpolatedTime == 1
? v.getHeight() : (int) (targetHeight * interpolatedTime));
Log.d("Anim", "height = " + v.getHeight());
if (interpolatedTime == 1){
Log.d("Anim", "final height = " + v.getHeight());
}
}
Can anyone point out what I am missing?