Search code examples
javaandroidtextviewandroid-linearlayout

Android Scrolling/Fading Text Programatically Appended To


I'm building an old-school type android game, and one of the things I'd like to have is a "combat results text window" where I can add text as actions happen, so the player knows what's going on.

I currently have used a textview and a valueAnimator to fade the text out:

public void updateCommentary(String updatedText){

    runOnUiThread(new Runnable() {

      @Override
      public void run() {
          ValueAnimator valueAnimator = ValueAnimator.ofFloat(1f, 0f);
          valueAnimator.setDuration(5000);
          valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
              @Override
              public void onAnimationUpdate(ValueAnimator animation) {
                  TextView tv = (TextView) findViewById(R.id.txtRunningCommentary);
                  tv.setText(updatedText);
                  float alpha = (float) animation.getAnimatedValue();
                  tv.setAlpha(alpha);
              }
          });
          valueAnimator.start();
      }
    });
}

This works to the point where any updates will drop in and fade out, but if called a quickly in succession, the following text over-write what's there already.

What I'd like to do, is instead have say, 5 lines of text, and let me keep adding new updates, which push the previous updates up, and as they get further up they fade out. If there are no updates for long enough, have the text fade out on it's own. Finally, in a perfect world, the widget would not have a fixed height, but understand how many lines can fit in it, and have the desired functionality based on that user's device/screen layout.

Currently Working with Single Textview

I have thought of doing this with a number of textviews, and at the start of the function moving the contents of textview2 into textview1, then moving trextview3 into textview2 and so on, and finally adding the newest update into textview5 - but this feels limited on a few points:

  • Firstly, it seems horribly clunky and inefficient in operation.
  • Secondly, I'm hoping that individual lines might fade on their own - so a lengthy update might fade out the top line, while keeping remaining text there.
  • Lastly, it would certainly not function in an auto-magical with it shrinking or expanding rows to the user device - but rather always be fixed to the number of textviews I have set up.

I have also considered adding a scrollable vertical linearLayout and appending new textviews to it... and somehow fading them out and deleting them... but again, starting to feel like it's getting overly complicated for the expected functionality?

Is there a android class that I could use in some way to achieve this, or is my horrid way about as good as it's going to get?


Solution

  • If you only need to fade out the text, no additional transformation, or scaling at all, I have a small trick for you:

    Create a gradient background drawable that smoothly fades from white to transparent:

    bgr_shadow_white_down.xml

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
    
        <gradient
            android:angle="-90"
            android:endColor="@android:color/transparent"
            android:startColor="@color/white" />
    </shape>
    

    You can create another drawable that fading bottom up:

    bgr_shadow_white_up.xml

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
    
        <gradient
            android:angle="90"
            android:endColor="@android:color/transparent"
            android:startColor="@color/white" />
    </shape>
    

    Then you can have a layout like this:

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="100dp">
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginHorizontal="10dp"
            tools:text="@tools:sample/lorem/random" />
    
        <View
            android:layout_width="match_parent"
            android:layout_height="32dp"
            android:background="@drawable/bgr_shadow_white_down" />
    
        <View
            android:layout_width="match_parent"
            android:layout_height="32dp"
            android:layout_gravity="bottom"
            android:background="@drawable/bgr_shadow_white_up" />
    </FrameLayout>
    

    Visual:

    Visualize fade overlay

    You will have a TextView with a fade effect on top and bottom. Now in your use case, you can:

    • Have a single TextView like this, when a new line is appended, translate the TextView up by a lineHeight so the old line moves up, which will be covered by our fade gradient, and will be completely covered by white color or clipped out from the parent when it keeps moving up. Clear everything and reset the animation when timeout and no new line is appended.

    • Have a vertical LinearLayout and add multiple TextView to the stack, translate up, same as above. Clear everything on timeout.