Feature Request: Built-in Animation Widget for Automatic Animations on Load #5149
Replies: 1 comment
-
This is already possible by using a custom class. I think the aim is to have something like this (full context below), where we can easily handle the animate in/out, and other parameters. animated_text_2 = AnimatedText(
"This animates too!",
size=24,
italic=True,
animation_duration=1000, # Slower animation
animation_curve=ft.AnimationCurve.EASE_IN_OUT,
initial_offset=ft.Offset(-0.5, 0), # Animate from left
color=ft.Colors.GREEN
) We can then control that widget via the following: ft.ElevatedButton("Animate 2 In", on_click=animated_text_2.animate_in),
ft.ElevatedButton("Animate 2 Out", on_click=animated_text_2.animate_out), Generally, I have a script full of custom widgets like this which I use throughout my project - as you are right, animations can clutter the code. I don't really object to having a higher level implementation of the existing animation structure.. you are right in a sense, it does make it more plug and play, and more suitable for some projects and users. But there is always the trade off as to how much should be built in, and when higher level abstractions make it more confusing overall, as there are two ways of doing things. Should these type of widgets be spun off into a separate package etc. Working code is below - but note, this is something I've thrown together - I haven't thoroughly debugged it, and this is a reasonably involved: import flet as ft
import time
import threading
class AnimatedText(ft.Text):
"""
A custom Flet Text control that animates in when first added to the page
by fading in and sliding from an initial offset.
Uses threading.Timer for delayed start and page.run_thread for safe UI updates.
"""
def __init__(
self,
value: str,
animation_duration: int = 600, # milliseconds
animation_curve: ft.AnimationCurve = ft.AnimationCurve.EASE_OUT,
initial_offset: ft.Offset = ft.Offset(0, 0.3), # Start position offset
initial_opacity: float = 0.0, # Start opacity (0=invisible)
target_offset: ft.Offset = ft.Offset(0, 0), # Final position offset
target_opacity: float = 1.0, # Final opacity (1=fully visible)
*args,
**kwargs
):
"""
Initializes the AnimatedText control.
Args:
value (str): The text string to display.
animation_duration (int, optional): Duration of the animation in ms.
animation_curve (ft.AnimationCurve, optional): Curve for animation timing.
initial_offset (ft.Offset, optional): Starting offset for slide animation.
initial_opacity (float, optional): Starting opacity.
target_offset (ft.Offset, optional): Final offset position.
target_opacity (float, optional): Final opacity.
*args: Additional positional arguments for ft.Text.
**kwargs: Additional keyword arguments for ft.Text.
"""
super().__init__(value=value, *args, **kwargs)
# Store animation parameters and state values
self.animation_duration_ms = animation_duration
self.animation_curve_style = animation_curve
self.start_offset = initial_offset
self.start_opacity = initial_opacity
self.end_offset = target_offset
self.end_opacity = target_opacity
# --- State Setup ---
# Configure the control's initial appearance before animation
self.visible = False # Start completely invisible
self.opacity = self.start_opacity # Set initial opacity (e.g., 0)
self.offset = self.start_offset # Set initial position offset
# --- Animation Definition ---
# Define how changes to opacity and offset should be animated
anim = ft.Animation(self.animation_duration_ms, self.animation_curve_style)
self.animate_opacity = anim # Apply animation to opacity changes
self.animate_offset = anim # Apply animation to offset changes
def did_mount(self):
"""
Called by Flet after the control is added. Schedules the animation sequence.
"""
# Use a Timer to delay the animation start slightly, ensuring the initial
# state is rendered before the animation begins.
timer = threading.Timer(0.05, self._start_animation_sequence) # 50ms delay
timer.start()
def _start_animation_sequence(self):
"""
Manages the sequence to make the control visible and start animation.
Called by the threading.Timer.
"""
# Ensure the page context is available before proceeding
if not self.page:
# print("Error: self.page not available in timer callback.")
return # Cannot proceed without page reference
# --- Step 1: Make visible at the STARTING animation state ---
# Set properties to how it should look at the very beginning of the animation
self.visible = True
self.opacity = self.start_opacity
self.offset = self.start_offset
# Schedule the first update (to make it visible at start state)
# using page.run_thread to execute _update_safe on the main Flet thread.
self.page.run_thread(self._update_safe)
# --- Step 2: Immediately set the TARGET animation state ---
# Set properties to how it should look at the end of the animation
self.opacity = self.end_opacity
self.offset = self.end_offset
# Schedule the second update (this triggers the animation)
# using page.run_thread again for the same reason.
self.page.run_thread(self._update_safe)
def _update_safe(self):
"""
Helper method that calls self.update().
Designed to be executed on the main Flet thread via page.run_thread.
"""
# Check if control is still attached to the page before updating
if self.page:
try:
self.update() # Mark the control for repaint
except Exception as e:
# Ignore errors if control was removed before update ran
# print(f"Error during update_safe: {e}")
pass
# --- Manual Animation Methods ---
def animate_in(self, e=None):
"""Resets the control and then runs the intro animation sequence."""
# --- Reset Step ---
# Temporarily disable animation logic for instant reset
current_anim_opacity = self.animate_opacity
current_anim_offset = self.animate_offset
self.animate_opacity = None
self.animate_offset = None
# Set state back to initial invisible/offset state
self.visible = False
self.opacity = self.start_opacity
self.offset = self.start_offset
self.update() # Apply reset state immediately
# Restore animation logic
self.animate_opacity = current_anim_opacity
self.animate_offset = current_anim_offset
# Short delay to ensure Flet processes the reset before starting animation
time.sleep(0.02)
# --- Animation Step ---
# Step 1: Make visible at start state
self.visible = True
self.opacity = self.start_opacity
self.offset = self.start_offset
self.update() # Update called directly (likely already on main thread)
# Step 2: Set target state (requires small delay after step 1 update)
time.sleep(0.02) # Delay to allow step 1 to render
self.opacity = self.end_opacity
self.offset = self.end_offset
self.update() # This update triggers the animation
def animate_out(self, e=None):
"""Animates the text out to its initial state and then makes it invisible."""
# Start animating opacity and offset back to their initial values
self.opacity = self.start_opacity
self.offset = self.start_offset
self.update() # Trigger the animation
# Use a timer to set visibility to False *after* the animation duration
def hide_after_animation():
self.visible = False
if self.page:
# Schedule the final update using page.run_thread
self.page.run_thread(self._update_safe)
else:
try:
# Fallback update attempt if page reference lost
self.update()
except: pass # Ignore errors if control removed
# Calculate delay = animation duration + small buffer
delay_s = self.animation_duration_ms / 1000.0 + 0.05
out_timer = threading.Timer(delay_s, hide_after_animation)
out_timer.start()
# --- Example Usage ---
def main(page: ft.Page):
page.title = "Animated Text Example"
page.vertical_alignment = ft.MainAxisAlignment.CENTER
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.spacing = 30 # Add some spacing between page elements
# Create instances of the custom AnimatedText control
animated_text_1 = AnimatedText(
"Hello, Flet!",
size=40,
weight=ft.FontWeight.BOLD,
color=ft.Colors.BLUE_ACCENT
)
animated_text_2 = AnimatedText(
"This animates too!",
size=24,
italic=True,
animation_duration=1000, # Slower animation
animation_curve=ft.AnimationCurve.EASE_IN_OUT,
initial_offset=ft.Offset(-0.5, 0), # Animate from left
color=ft.Colors.GREEN
)
animated_text_3 = AnimatedText(
"Bouncing In!",
size=24,
animation_duration=1200, # Longer duration
animation_curve=ft.AnimationCurve.BOUNCE_OUT, # Bouncy effect
initial_offset=ft.Offset(0, 1), # Animate from further below
color=ft.Colors.ORANGE
)
# Add controls to the page
page.add(
ft.Column(
[
animated_text_1,
animated_text_2,
animated_text_3,
# Spacer before buttons
ft.Divider(height=30, color=ft.Colors.TRANSPARENT),
# Buttons to manually trigger animations
ft.Row(
[
ft.ElevatedButton("Animate 1 In", on_click=animated_text_1.animate_in),
ft.ElevatedButton("Animate 1 Out", on_click=animated_text_1.animate_out),
], alignment=ft.MainAxisAlignment.CENTER),
ft.Row(
[
ft.ElevatedButton("Animate 2 In", on_click=animated_text_2.animate_in),
ft.ElevatedButton("Animate 2 Out", on_click=animated_text_2.animate_out),
], alignment=ft.MainAxisAlignment.CENTER),
ft.Row(
[
ft.ElevatedButton("Animate 3 In", on_click=animated_text_3.animate_in),
ft.ElevatedButton("Animate 3 Out", on_click=animated_text_3.animate_out),
], alignment=ft.MainAxisAlignment.CENTER)
],
# Center content horizontally within the Column
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
# Spacing between elements in the Column
spacing=20
)
)
if __name__ == "__main__":
# Run the Flet application
ft.app(target=main) |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Currently, animations in Flet require manual triggering via a button click or calling a method explicitly. This makes automatic animations on screen load somewhat complex. I propose a new widget that automatically applies an animation to its child when it appears on the screen.
Introduce a widget like:
ft.FadeAnimation( content=ft.Text("Hello, Flet!") )
or a more generic:
ft.AnimatedWidget( animation=ft.AnimationType.FADE_IN, duration=500, child=ft.Text("Hello, Flet!") )
This would allow animations to be applied automatically when a screen loads without needing extra logic.
Benefits
Simplifies automatic animations.
Reduces boilerplate code.
Makes animations more accessible to beginners.
Beta Was this translation helpful? Give feedback.
All reactions