simple_tv_navigation
A highly optimized TV navigation system for Flutter applications, providing efficient ID-based focus management for TVs, set-top boxes, and game consoles.
Features
- ID-based navigation: Navigate between UI elements using unique IDs
- Direction-based focus management: Define clear navigation paths with up, down, left, and right connections
- Dynamic navigation paths: Support for dynamic navigation paths that can change at runtime
- Autofocus support: Automatically focus elements when they appear
- Keyboard navigation: Built-in support for remote control and keyboard arrow keys
- Focus visualization: Customizable focus highlighting
- Scroll to focused element: Automatically scrolls to bring focused elements into view
- Focus events: Callbacks for focus, blur, selection, and navigation events
- BLoC architecture: Clean, testable architecture using Flutter BLoC pattern
Installation
Add to your pubspec.yaml
:
dependencies:
simple_tv_navigation: ^0.0.1+28
Usage
Basic Setup
Wrap your app with the TvNavigationProvider
:
void main() {
runApp(
TvNavigationProvider(
child: MyApp(),
),
);
}
Creating Focusable Elements
Add focusable elements to your UI:
TVFocusable(
id: 'button1',
leftId: 'sidebar_item',
rightId: 'button2',
upId: 'top_menu',
downId: 'bottom_button',
showDefaultFocusDecoration: true,
onSelect: () {
// Handle selection
print('Button 1 selected');
},
child: Container(
padding: const EdgeInsets.all(16),
color: Colors.blue,
child: const Text('Button 1'),
),
)
Custom Focus Styling
Use the builder to create custom focus effects:
TVFocusable(
id: 'custom_button',
builder: (context, isFocused, child) {
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
transform: isFocused
? Matrix4.identity()..scale(1.1)
: Matrix4.identity(),
decoration: BoxDecoration(
color: isFocused ? Colors.red : Colors.grey,
borderRadius: BorderRadius.circular(8),
boxShadow: isFocused
? [BoxShadow(color: Colors.red.withOpacity(0.5), blurRadius: 10)]
: null,
),
padding: const EdgeInsets.all(16),
child: Text(
'Custom Button',
style: TextStyle(
color: Colors.white,
fontWeight: isFocused ? FontWeight.bold : FontWeight.normal,
),
),
);
},
)
Dynamic Navigation Paths
Use dynamic ID callbacks for complex navigation scenarios:
TVFocusable(
id: 'dynamic_button',
dynamicLeftId: () {
// Return different IDs based on state
return isSpecialCase ? 'special_button' : 'normal_button';
},
child: Text('Dynamic Navigation'),
)
Programmatic Navigation
Control focus programmatically using context extensions:
// Move focus in a direction
context.moveFocus(TvFocusDirection.right);
// Set focus to a specific element
context.setFocus('settings_button');
// Select currently focused element
context.selectCurrent();
// Check if element is focused
bool isFocused = context.isElementFocused('my_button');
// Enable/disable TV navigation
context.setTvNavigationEnabled(false);
Advanced Features
Managing Multiple Navigation Areas
For complex UIs with multiple navigation areas (like sidebars and main content):
- Use separate TvNavigationProvider widgets for each area
- Enable/disable navigation areas as needed
// In your sidebar state:
context.setTvNavigationEnabled(true); // Enable sidebar navigation
// In your content area:
context.setTvNavigationEnabled(false); // Disable content navigation
Controlling System Focus
The package includes functionality to exclude Flutter's system focus when using TV navigation. This prevents interference between the TV navigation system and Flutter's default focus system:
// Enable exclude focus (default is true)
context.disableFocus();
// Disable exclude focus
context.enableFocus();
When exclude focus is enabled (the default), Flutter's system focus will be prevented while TV navigation is active, ensuring a consistent navigation experience.
Focus Management with Lists
When working with dynamic lists, generate unique IDs and connections:
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final String itemId = 'item_$index';
return TVFocusable(
id: itemId,
upId: index > 0 ? 'item_${index - 1}' : null,
downId: index < items.length - 1 ? 'item_${index + 1}' : null,
child: ListTile(title: Text(items[index])),
);
},
)
License
This project is licensed under the MIT License - see the LICENSE file for details.