WidgetAnimator
MicroPythonOS provides a WidgetAnimator utility for creating smooth, non-blocking animations on LVGL widgets. It handles common animation patterns like fade-in/fade-out, slide transitions, and value interpolation with automatic cleanup and error handling.
Overview
WidgetAnimator provides:
- Fade animations - Smooth opacity transitions (fade-in/fade-out)
- Slide animations - Smooth position transitions (slide-up/slide-down)
- Value interpolation - Animate numeric values with custom display callbacks
- Non-blocking - Animations run asynchronously without blocking the UI
- Safe widget access - Automatically handles deleted widgets without crashes
- Automatic cleanup - Stops previous animations to prevent visual glitches
- Easing functions - Built-in ease-in-out path for smooth motion
Quick Start
Fade In/Out
from mpos.ui.anim import WidgetAnimator
import lvgl as lv
# Create a widget
my_widget = lv.label(screen)
my_widget.set_text("Hello")
# Fade in (500ms)
WidgetAnimator.show_widget(my_widget, anim_type="fade", duration=500)
# Fade out (500ms)
WidgetAnimator.hide_widget(my_widget, anim_type="fade", duration=500)
Convenience Functions
For the most common use case (fade animations), use the convenience functions:
from mpos import smooth_show, smooth_hide
# Fade in with default 500ms duration
smooth_show(my_widget)
# Fade out with default 500ms duration
smooth_hide(my_widget)
# Fade out without hiding the widget (just opacity)
smooth_hide(my_widget, hide=False)
Slide Animations
from mpos.ui.anim import WidgetAnimator
# Slide down from above
WidgetAnimator.show_widget(my_widget, anim_type="slide_down", duration=500)
# Slide up from below
WidgetAnimator.show_widget(my_widget, anim_type="slide_up", duration=500)
# Hide by sliding down
WidgetAnimator.hide_widget(my_widget, anim_type="slide_down", duration=500)
# Hide by sliding up
WidgetAnimator.hide_widget(my_widget, anim_type="slide_up", duration=500)
Value Interpolation
Animate numeric values with a custom display callback:
from mpos.ui.anim import WidgetAnimator
# Animate balance from 1000 to 1500 over 5 seconds
def display_balance(value):
balance_label.set_text(f"{int(value)} sats")
WidgetAnimator.change_widget(
balance_label,
anim_type="interpolate",
duration=5000,
begin_value=1000,
end_value=1500,
display_change=display_balance
)
API Reference
show_widget()
Show a widget with an animation.
WidgetAnimator.show_widget(widget, anim_type="fade", duration=500, delay=0)
Parameters:
- widget (lv.obj): The widget to show
- anim_type (str): Animation type - "fade", "slide_down", or "slide_up" (default: "fade")
- duration (int): Animation duration in milliseconds (default: 500)
- delay (int): Animation delay in milliseconds (default: 0)
Returns: The animation object
Behavior:
- Removes the HIDDEN flag at the start of animation
- Animates opacity (fade) or position (slide)
- Resets final state after animation completes
hide_widget()
Hide a widget with an animation.
WidgetAnimator.hide_widget(widget, anim_type="fade", duration=500, delay=0, hide=True)
Parameters:
- widget (lv.obj): The widget to hide
- anim_type (str): Animation type - "fade", "slide_down", or "slide_up" (default: "fade")
- duration (int): Animation duration in milliseconds (default: 500)
- delay (int): Animation delay in milliseconds (default: 0)
- hide (bool): If True, adds HIDDEN flag after animation. If False, only animates opacity/position (default: True)
Returns: The animation object
Behavior:
- Animates opacity (fade) or position (slide)
- Adds HIDDEN flag after animation (if hide=True)
- Restores original position after slide animations
change_widget()
Animate a numeric value with a custom display callback.
WidgetAnimator.change_widget(
widget,
anim_type="interpolate",
duration=5000,
delay=0,
begin_value=0,
end_value=100,
display_change=None
)
Parameters:
- widget (lv.obj): The widget being animated (used for cleanup)
- anim_type (str): Animation type - currently only "interpolate" is supported
- duration (int): Animation duration in milliseconds (default: 5000)
- delay (int): Animation delay in milliseconds (default: 0)
- begin_value (int/float): Starting value for interpolation (default: 0)
- end_value (int/float): Ending value for interpolation (default: 100)
- display_change (callable, optional): Callback function called with each interpolated value. If None, calls widget.set_text(str(value))
Returns: The animation object
Behavior:
- Interpolates between begin_value and end_value
- Calls display_change(value) for each frame
- Ensures final value is set after animation completes
- Uses ease-in-out path for smooth motion
Convenience Functions
smooth_show(widget, duration=500, delay=0)
Fade in a widget (shorthand for show_widget() with fade animation).
smooth_hide(widget, hide=True, duration=500, delay=0)
Fade out a widget (shorthand for hide_widget() with fade animation).
Animation Types
Fade
Animates widget opacity from 0 to 255 (show) or 255 to 0 (hide).
# Fade in
WidgetAnimator.show_widget(widget, anim_type="fade", duration=500)
# Fade out
WidgetAnimator.hide_widget(widget, anim_type="fade", duration=500)
Use cases: - Smooth appearance/disappearance of UI elements - Transitioning between screens - Highlighting important information
Slide Down
Animates widget position from above the screen to its original position (show) or from original position to below (hide).
# Slide down from above
WidgetAnimator.show_widget(widget, anim_type="slide_down", duration=500)
# Slide down to below
WidgetAnimator.hide_widget(widget, anim_type="slide_down", duration=500)
Use cases: - Drawer-like panels sliding down - Notifications appearing from top - Menu items sliding in
Slide Up
Animates widget position from below the screen to its original position (show) or from original position to above (hide).
# Slide up from below
WidgetAnimator.show_widget(widget, anim_type="slide_up", duration=500)
# Slide up to above
WidgetAnimator.hide_widget(widget, anim_type="slide_up", duration=500)
Use cases: - Keyboard sliding up from bottom - Bottom sheets appearing - Menu items sliding in from bottom
Interpolate
Animates numeric values with smooth easing.
WidgetAnimator.change_widget(
widget,
anim_type="interpolate",
duration=5000,
begin_value=0,
end_value=100,
display_change=lambda v: label.set_text(f"{int(v)}")
)
Use cases: - Animating balance updates - Progress indicators - Counters - Numeric displays
Complete Examples
Image Viewer with Fade Transitions
from mpos import Activity, smooth_show, smooth_hide
import lvgl as lv
class ImageViewerActivity(Activity):
def onCreate(self):
self.screen = lv.obj()
# Image widget
self.image = lv.image(self.screen)
self.image.center()
self.image.add_flag(lv.obj.FLAG.HIDDEN) # Start hidden
# Navigation buttons
prev_btn = lv.button(self.screen)
prev_btn.align(lv.ALIGN.BOTTOM_LEFT, 0, 0)
prev_btn.add_event_cb(lambda e: self.show_prev_image(), lv.EVENT.CLICKED, None)
next_btn = lv.button(self.screen)
next_btn.align(lv.ALIGN.BOTTOM_RIGHT, 0, 0)
next_btn.add_event_cb(lambda e: self.show_next_image(), lv.EVENT.CLICKED, None)
self.setContentView(self.screen)
def show_prev_image(self):
# Fade out current image
smooth_hide(self.image)
# Load and fade in next image
self.load_image("prev_image.png")
smooth_show(self.image)
def show_next_image(self):
# Fade out current image
smooth_hide(self.image)
# Load and fade in next image
self.load_image("next_image.png")
smooth_show(self.image)
def load_image(self, path):
self.image.set_src(f"M:{path}")
Wallet Balance Animation
from mpos import Activity
from mpos.ui.anim import WidgetAnimator
import lvgl as lv
class WalletActivity(Activity):
def onCreate(self):
self.screen = lv.obj()
self.balance_label = lv.label(self.screen)
self.balance_label.set_text("0 sats")
self.balance_label.align(lv.ALIGN.TOP_MID, 0, 20)
self.setContentView(self.screen)
def on_balance_received(self, old_balance, new_balance, sats_added):
"""Animate balance update when payment received."""
def display_balance(value):
self.balance_label.set_text(f"{int(value)} sats")
# Animate from old balance to new balance over 3 seconds
WidgetAnimator.change_widget(
self.balance_label,
anim_type="interpolate",
duration=3000,
begin_value=old_balance,
end_value=new_balance,
display_change=display_balance
)
Fullscreen Mode Toggle with Slide Animations
from mpos import Activity, smooth_show, smooth_hide
from mpos.ui.anim import WidgetAnimator
import lvgl as lv
class ImageViewerActivity(Activity):
def onCreate(self):
self.screen = lv.obj()
self.fullscreen = False
# Image
self.image = lv.image(self.screen)
self.image.center()
# UI controls
self.controls = lv.obj(self.screen)
self.controls.set_size(lv.pct(100), 50)
self.controls.align(lv.ALIGN.BOTTOM_MID, 0, 0)
prev_btn = lv.button(self.controls)
prev_btn.align(lv.ALIGN.LEFT, 0, 0)
next_btn = lv.button(self.controls)
next_btn.align(lv.ALIGN.RIGHT, 0, 0)
self.image.add_event_cb(lambda e: self.toggle_fullscreen(), lv.EVENT.CLICKED, None)
self.setContentView(self.screen)
def toggle_fullscreen(self):
if self.fullscreen:
# Exit fullscreen - slide controls back up
self.fullscreen = False
WidgetAnimator.show_widget(self.controls, anim_type="slide_up", duration=300)
else:
# Enter fullscreen - slide controls down
self.fullscreen = True
WidgetAnimator.hide_widget(self.controls, anim_type="slide_down", duration=300, hide=False)
Best Practices
1. Use Convenience Functions for Simple Cases
# Good - simple and readable
smooth_show(widget)
smooth_hide(widget)
# Less readable - more verbose
WidgetAnimator.show_widget(widget, anim_type="fade", duration=500, delay=0)
2. Provide Custom Display Callbacks for Value Animations
# Good - custom formatting
def display_balance(value):
balance_label.set_text(f"{int(value):,} sats")
WidgetAnimator.change_widget(
balance_label,
anim_type="interpolate",
duration=3000,
begin_value=old_balance,
end_value=new_balance,
display_change=display_balance
)
# Less good - default string conversion
WidgetAnimator.change_widget(
balance_label,
anim_type="interpolate",
duration=3000,
begin_value=old_balance,
end_value=new_balance
)
3. Use Appropriate Durations
# Quick feedback (UI elements)
smooth_show(button, duration=200)
# Standard transitions (screens, panels)
smooth_hide(panel, duration=500)
# Slow animations (value updates, important transitions)
WidgetAnimator.change_widget(
balance_label,
anim_type="interpolate",
duration=3000,
begin_value=old,
end_value=new,
display_change=display_fn
)
4. Handle Widget Deletion Gracefully
WidgetAnimator automatically handles deleted widgets through safe_widget_access(). No special handling needed:
# Safe - animation continues even if widget is deleted
WidgetAnimator.show_widget(widget, duration=500)
# ... widget might be deleted here ...
# Animation completes silently without crashing
5. Combine Animations for Complex Transitions
# Fade out old content
smooth_hide(old_content, duration=300)
# After fade completes, show new content
# (Use delay to sequence animations)
smooth_show(new_content, duration=300, delay=300)
Performance Tips
1. Use Shorter Durations for Frequent Animations
# Good - quick feedback for user interactions
smooth_show(button, duration=200)
# Avoid - slow animations for frequent events
smooth_show(button, duration=2000)
2. Limit Simultaneous Animations
WidgetAnimator automatically stops previous animations on the same widget:
# Safe - previous animation is stopped
WidgetAnimator.show_widget(widget, duration=500)
WidgetAnimator.show_widget(widget, duration=500) # Previous animation stops
3. Use Appropriate Easing
All animations use path_ease_in_out for smooth, natural motion. This is optimal for most use cases.
Troubleshooting
Animation Not Visible
Symptom: Animation runs but widget doesn't appear to move/fade
Possible causes: 1. Widget is already in the target state 2. Widget is outside the visible area 3. Animation duration is too short to notice
Solution:
# Ensure widget is in correct initial state
widget.remove_flag(lv.obj.FLAG.HIDDEN) # For show animations
widget.set_style_opa(255, 0) # For fade animations
# Use longer duration for testing
smooth_show(widget, duration=1000)
Widget Disappears After Animation
Symptom: Widget fades out but doesn't reappear
Possible cause: Using hide=True (default) which adds HIDDEN flag
Solution:
# If you want to keep widget visible but transparent
smooth_hide(widget, hide=False, duration=500)
# If you want to hide it completely
smooth_hide(widget, hide=True, duration=500)
Animation Stutters or Jitters
Symptom: Animation is not smooth, has visual glitches
Possible causes: 1. Too many simultaneous animations 2. Heavy processing during animation 3. Widget being modified during animation
Solution:
# Avoid modifying widget during animation
# Stop any previous animations first
lv.anim_delete(widget, None)
# Then start new animation
smooth_show(widget, duration=500)
Value Animation Shows Wrong Final Value
Symptom: Animation completes but final value is incorrect
Possible cause: display_change callback not handling final value correctly
Solution:
# Ensure callback handles all values correctly
def display_balance(value):
# Handle both intermediate and final values
self.balance_label.set_text(f"{int(value)} sats")
WidgetAnimator.change_widget(
balance_label,
anim_type="interpolate",
duration=3000,
begin_value=old_balance,
end_value=new_balance,
display_change=display_balance
)
See Also
- Creating Apps - Learn how to create MicroPythonOS apps
- Activity - Activity lifecycle and UI management
- LVGL Documentation - LVGL animation API reference