Wave animation in Android TextView
Categories:
Creating a Dynamic Wave Animation in Android TextView

Learn how to implement a captivating wave animation effect on text within an Android TextView, enhancing UI engagement and visual appeal.
Adding subtle animations to your Android application can significantly improve the user experience and make your UI more engaging. A wave animation, where text characters appear to move up and down in a fluid motion, is a visually appealing effect that can draw attention to specific text elements. This article will guide you through the process of creating such an animation for an Android TextView, leveraging custom Shader and Animation techniques.
Understanding the Core Concept: Custom Shader
The key to achieving a wave effect on text lies in manipulating how the text is drawn. Android's TextView uses a Paint object to render its text. By setting a custom Shader on this Paint object, we can control the color and pattern used to draw the text. For a wave effect, we'll use a LinearGradient shader, but dynamically adjust its position over time to create the illusion of movement.
A LinearGradient defines a gradient of colors along a line. If we create a gradient that cycles through colors (e.g., transparent to a color and back to transparent), and then shift this gradient horizontally or vertically, it can simulate a wave. The challenge is to apply this gradient in a way that affects the text's appearance as if it's undulating.
flowchart TD
A[TextView Render Text] --> B{Get Paint Object}
B --> C{Create Custom Shader}
C --> D{Apply Shader to Paint}
D --> E{Invalidate TextView}
E --> F[Redraw with Animated Shader]
C -- periodically update --> CFlowchart of applying a custom shader for text animation
Implementing the Wave Shader
To implement the wave, we'll extend TextView and override its onDraw method. Inside onDraw, we'll create a LinearGradient that spans the width of the text. The colors in this gradient will define the 'wave' itself. By continuously updating the matrix of this LinearGradient and invalidating the TextView, we can make the wave appear to move across the text.
Consider a simple gradient from transparent to a color and back. When this gradient is applied to text, only the colored part will be visible. By shifting this gradient's starting point, we can make the colored 'wave' move. We'll use a ValueAnimator to smoothly change the offset of our gradient over time.
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.LinearGradient
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.Shader
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
class WaveTextView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr) {
private var mLinearGradient: LinearGradient? = null
private var mGradientMatrix: Matrix? = null
private var mTranslate = 0f
private var mWaveWidth = 0f
private var mAnimator: ValueAnimator? = null
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
if (mWaveWidth == 0f) {
mWaveWidth = w.toFloat() / 2 // Half the width of the TextView
}
val colors = intArrayOf(
currentTextColor, // Start color (text color)
0x00FFFFFF, // Transparent (wave trough)
currentTextColor // End color (text color)
)
val positions = floatArrayOf(0f, 0.5f, 1f)
mLinearGradient = LinearGradient(
-mWaveWidth, 0f, 0f, 0f, colors, positions, Shader.TileMode.CLAMP
)
paint.shader = mLinearGradient
mGradientMatrix = Matrix()
startWaveAnimation()
}
private fun startWaveAnimation() {
mAnimator?.cancel()
mAnimator = ValueAnimator.ofFloat(0f, mWaveWidth * 2).apply {
duration = 1500 // Animation duration
repeatMode = ValueAnimator.RESTART
repeatCount = ValueAnimator.INFINITE
addUpdateListener { animation ->
mTranslate = animation.animatedValue as Float
postInvalidateOnAnimation()
}
start()
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
mGradientMatrix?.let { matrix ->
matrix.setTranslate(mTranslate, 0f)
mLinearGradient?.setLocalMatrix(matrix)
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
mAnimator?.cancel()
mAnimator = null
}
}
Kotlin code for WaveTextView implementing a horizontal wave animation.
mWaveWidth variable determines the length of one 'wave cycle'. Adjusting this value, along with the duration of the ValueAnimator, allows you to control the speed and appearance of the wave. A smaller mWaveWidth relative to the text width will create more frequent, tighter waves.Integrating into Layouts and Customization
Once you have your WaveTextView class, you can use it in your XML layouts just like any other TextView. Remember to use the fully qualified class name.
To customize the wave, you can modify the colors and positions arrays in the LinearGradient constructor. For example, to make the wave more subtle, you could use colors closer to your text color. To change the direction of the wave, you can adjust the LinearGradient's start and end points (e.g., 0f, -mWaveWidth, 0f, 0f for a vertical wave, and adjust mTranslate accordingly).
For a more complex wave, you might consider using a BitmapShader with a pre-rendered wave pattern, or even a custom Shader that calculates sine waves directly, though this adds significant complexity.
<!-- activity_main.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
tools:context=".MainActivity">
<com.example.yourapp.WaveTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello Wave!"
android:textSize="48sp"
android:textColor="#FF0000"
android:textStyle="bold" />
</LinearLayout>
Using the WaveTextView in an Android XML layout.
LinearGradient is generally efficient, excessive postInvalidateOnAnimation() calls or very complex shaders can impact UI smoothness. Test your animations thoroughly across various devices.1. Create the Custom TextView Class
Define a new Kotlin or Java class (e.g., WaveTextView.kt) that extends AppCompatTextView.
2. Override onSizeChanged
Initialize your LinearGradient and Matrix within the onSizeChanged method to ensure they are set up correctly after the view's dimensions are known.
3. Implement ValueAnimator
Create a ValueAnimator to continuously update the mTranslate value, which controls the gradient's position. Set it to repeat infinitely.
4. Override onDraw
In onDraw, apply the mTranslate value to the mGradientMatrix and then set this matrix to your mLinearGradient. Call super.onDraw(canvas) to draw the text with the updated shader.
5. Handle Lifecycle
Override onDetachedFromWindow() to cancel the ValueAnimator to prevent memory leaks and unnecessary processing when the view is no longer visible.
6. Use in XML Layout
Integrate your WaveTextView into your activity_main.xml or any other layout file using its fully qualified class name.