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
, height
set 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
animation
works on firstexpand
, but not on second.- It seems that the
TextView
gets calculated wrong. With a highmultiplier
I have seen sometext
plopping up for < 0.5s over the next headerTextView
- removing the
smoothScrollTo
inexpand
does 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 height
is printed twice - with exactly 50 points(pixels? dp?) difference.//smoothly increasing height and then:
final height = 202 height = 252 final height = 252
While I gettargetHeight = 203
- so theheight
gets 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?