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 --> C
Flowchart 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.