Search code examples
androidtextviewstickyandroid-transitionsandroid-motionlayout

How to animate a wrap_content TextView in MotionLayout?


First of all, I have created a minimal demo to demonstrate the issue.

What I want to do

I have 2 TextView on the top of my screen. The second one is on the right of the first one, with smaller size. Both display some content returned by API (So I don't know how long it needs to be - i.e. they have to be wrap_content.)

Now I want to implement a sticky header with transition effect: When user scrolls up, the first TextView will shrink to almost the same size with the second one, and keep themselves on the top of the screen. To achieve this, MotionLayout seems to be a perfect fit.

My approach

Since I don't know the length of the content, so in order to animate the size of the first TextView, I can only choose one of the below:

  1. Animate textSize
  2. Animate scale

Approach 1 can be done by this solution. However, the animation is not smooth at all so it cannot be used.

So I tried approach 2. The layoutDescription file transit the first TextView from

        <Constraint
                android:id="@+id/tvFirst"
                android:layout_width="wrap_content"
                android:layout_height="32dp"
                app:layout_constraintTop_toBottomOf="@id/vTop"
                app:layout_constraintStart_toStartOf="parent"/>

to

        <Constraint
                android:id="@+id/tvFirst"
                android:layout_width="wrap_content"
                android:layout_height="24dp"
                app:layout_constraintTop_toBottomOf="@id/vTop"
                app:layout_constraintStart_toStartOf="parent"
                android:transformPivotX="0dp"
                android:transformPivotY="0dp"
                android:scaleX="0.6"
                android:scaleY="0.6"/>

The animation effect is great, but

The problem

The width of the TextView does not change. Only its content scaled down. The problem here is, I want the second TextView to stick to the end of the content, but now there is a large gap between them.

What did I tried

I tried to combine 2 approaches. i.e. I created a transparent TextView as a dummy of the first TextView, which animates using approach 1 (TextSize), and let the second TextView constraint to the dummy instead. However, the width change occurs only after completing the transition, the second TextView still stays at the same location during the transition.

My question

How can I animate it smoothly with the second TextView sticking to the end of the content of the first TextView?


Solution

  • This is a know problem with out a good solution. By default MotionLayout Blocks relaying out during animation. This can be changed by the flag Transition:

     <Transition     motion:layoutDuringTransition="honorRequest" \>
    

    This then means the layout is being resolved at every pass (which is slow) but might suit your needs. Another approach would be to use MotionLabel which was designed to do smooth scaling.

    Which is designed to mitigate the scaling quantization with an attribute scaleFromTextSize

      <androidx.constraintlayout.utils.widget.MotionLabel
        app:textPanX="0"
        app:scaleFromTextSize="40sp"
        android:textSize="90sp"/>
    

    This grabs "outlines" of the text from size and scales them to the desired size.

    Long term we are looking for better solutions for this problem.