10

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 animation works on first expand, but not on second.

    • It seems that the TextView gets calculated wrong. With a high multiplier I have seen some text plopping up for < 0.5s over the next header TextView
    • removing the smoothScrollTo in expand does not change anything (except scrolling of course..)
    • other interpolators also have 'hiccups', but shorter

    important:

  • some Logging in applyTransformation (see below) got me to the point, that I see that final 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 get targetHeight = 203 - so the height gets calculated wrong first, but then some magic happens?

@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?

Community
  • 1
  • 1
yennsarah
  • 5,467
  • 2
  • 27
  • 48
  • is there a reason you're checking if the interpolated time = 1, then setting the height to WRAP_CONTENT? Why do this? Why not just say that the height always equals to interpolatedTime * targetHeight? When interpolated time = 1, then the height will be targetHeight as you desire. I think setting the height to WRAP_CONTENT at the end is what's causing the stutter. – Gil Moshayof Jan 14 '16 at 12:23
  • I took that line from the answer linked above - if I only use `interpolatedTime * targetHeight` the last line is not printed at all. I already found the problem is caused by wrong calculation of the `height`.. :/ – yennsarah Jan 14 '16 at 12:41
  • `v.requestLayout();` makes the view parent call layout() method and all children being layout again. Consecutive calls might lead to lags. Attempt to animate the Rect instead of the layout params. – Nikola Despotoski Jan 14 '16 at 12:53
  • There is only one lag, like a 'hiccup'. Is there any other way making this animation possible? @NikolaDespotoski – yennsarah Jan 14 '16 at 12:59
  • apply this v.getParent().requestDisallowInterceptTouchEvent(true); – Radhey Jan 21 '16 at 07:13
  • @Amy Have you found the source of the problem? I didn't see you comment on any of the answers – Alex.F Jan 25 '16 at 10:07
  • Currently not at work. Next week ;) – yennsarah Jan 25 '16 at 14:33

2 Answers2

0

it may cause because expending the last element might trigger scrolling because the height of the layout getting bigger, therefore 'push' the display down

try to add a FrameLayout at the bottom with height of 20dp and try to observer if it makes any difference

ymz
  • 6,602
  • 1
  • 20
  • 39
0

I'm 99% sure that you need to change <item name="android:layout_height">0dp</item> (of the TextView being animated) to be wrap_content and set it's initial state to GONE (as this is you final state after the collapse animation).

Alex.F
  • 5,648
  • 3
  • 39
  • 63