flutter_body_part_selector 1.1.5
flutter_body_part_selector: ^1.1.5 copied to clipboard
Interactive body selector for Flutter. Tap muscles on SVG body diagram or select programmatically with visual highlighting. Includes built-in assets.
Flutter Body Part Selector #
An interactive body selector package for Flutter that allows users to select muscles on a body diagram. Users can tap on muscles in the SVG body diagram or select them programmatically, with visual highlighting of selected muscles.
⚠️ IMPORTANT: This package includes mandatory SVG assets that must be used. Custom SVG files are not supported.
https://github.com/user-attachments/assets/8ba0b47b-fa72-4055-bee8-26f50427437c
Features #
- 🎯 Interactive Muscle Selection: Tap on any muscle in the body diagram to select it
- 🎨 Visual Highlighting: Selected muscles are automatically highlighted with customizable colors
- 🔄 Front/Back Views: Toggle between front and back body views
- 📱 Programmatic Control: Select muscles programmatically using the controller
- 🎛️ Customizable: Customize highlight colors and base colors
- 📦 Easy to Use: Simple API with minimal setup required - includes all required assets
- 🎨 Built-in Assets: Package includes mandatory SVG body diagrams (front and back views)
Installation #
Add this to your package's pubspec.yaml file:
dependencies:
flutter_body_part_selector: ^1.1.5
Then run:
flutter pub get
Usage #
Quick Start (Recommended) #
The easiest way to use this package is with the InteractiveBodyWidget:
import 'package:flutter/material.dart';
import 'package:flutter_body_part_selector/flutter_body_part_selector.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: InteractiveBodyWidget(
// Asset paths are optional - package includes default assets
onMuscleSelected: (muscle) {
print('Selected muscle: $muscle');
},
),
);
}
}
Advanced Usage #
For more control, use InteractiveBodySvg with BodyMapController:
import 'package:flutter/material.dart';
import 'package:flutter_body_part_selector/flutter_body_part_selector.dart';
class BodySelectorExample extends StatefulWidget {
@override
State<BodySelectorExample> createState() => _BodySelectorExampleState();
}
class _BodySelectorExampleState extends State<BodySelectorExample> {
final controller = BodyMapController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Interactive Body Selector'),
actions: [
IconButton(
icon: const Icon(Icons.flip),
onPressed: controller.toggleView,
tooltip: 'Flip view',
),
],
),
body: AnimatedBuilder(
animation: controller,
builder: (context, _) {
return Column(
children: [
if (controller.selectedMuscles.isNotEmpty)
Container(
padding: const EdgeInsets.all(16),
color: Colors.blue.shade900,
width: double.infinity,
child: Text(
'Selected: ${controller.selectedMuscles.length} muscles',
style: const TextStyle(color: Colors.white),
textAlign: TextAlign.center,
),
),
Expanded(
child: InteractiveBodySvg(
isFront: controller.isFront,
selectedMuscles: controller.selectedMuscles,
onMuscleTap: controller.selectMuscle,
),
),
],
);
},
),
);
}
}
Using the Controller #
The BodyMapController manages the state of the body selector:
Basic Usage
final controller = BodyMapController();
// Select a muscle programmatically (toggles if already selected)
controller.selectMuscle(Muscle.bicepsLeft);
// Clear all selections
controller.clearSelection();
// Toggle between front and back view
controller.toggleView();
// Set specific view
controller.setFrontView();
controller.setBackView();
// Access current state
final selected = controller.selectedMuscles; // Returns Set<Muscle> (read-only getter)
final isFront = controller.isFront; // Writable: can be set directly
// Set entire selection using setter (convenience)
controller.selectedMuscles = {Muscle.bicepsLeft, Muscle.tricepsRight};
Initialization with Pre-selected Muscles
// Create controller with initial selection
final controller = BodyMapController(
initialSelectedMuscles: {Muscle.bicepsLeft, Muscle.tricepsRight},
initialDisabledMuscles: {Muscle.chestLeft}, // Optional: pre-disable muscles
initialIsFront: true, // Optional: start with back view
);
Programmatic Selection Management
final controller = BodyMapController();
// Toggle a muscle's selection state
controller.toggleMuscle(Muscle.bicepsLeft);
// Deselect a specific muscle
controller.deselectMuscle(Muscle.bicepsLeft);
// Set entire selection (replaces current selection)
controller.setSelectedMuscles({
Muscle.bicepsLeft,
Muscle.tricepsRight,
Muscle.chestLeft,
});
// Add multiple muscles to current selection (without clearing)
controller.selectMultiple({
Muscle.bicepsLeft,
Muscle.tricepsRight,
});
// Check if a muscle is selected
if (controller.isSelected(Muscle.bicepsLeft)) {
print('Biceps left is selected');
}
// Get all selected muscles (read-only Set)
final selected = controller.selectedMuscles;
print('Selected ${selected.length} muscles');
Managing Disabled Muscles
final controller = BodyMapController();
// Disable a muscle (locks it, removes from selection)
controller.disableMuscle(Muscle.chestLeft);
// Enable a muscle (unlocks it)
controller.enableMuscle(Muscle.chestLeft);
// Set multiple disabled muscles at once
controller.setDisabledMuscles({
Muscle.chestLeft,
Muscle.chestRight,
});
// Check if a muscle is disabled
if (controller.isDisabled(Muscle.chestLeft)) {
print('Chest left is disabled');
}
Complete Example: Programmatic Selection Management
import 'package:flutter/material.dart';
import 'package:flutter_body_part_selector/flutter_body_part_selector.dart';
class BodySelectorPage extends StatefulWidget {
@override
State<BodySelectorPage> createState() => _BodySelectorPageState();
}
class _BodySelectorPageState extends State<BodySelectorPage> {
late BodyMapController controller;
@override
void initState() {
super.initState();
// Initialize with some pre-selected muscles
controller = BodyMapController(
initialSelectedMuscles: {Muscle.bicepsLeft, Muscle.tricepsRight},
);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Body Selector')),
body: Column(
children: [
// Control buttons
Padding(
padding: const EdgeInsets.all(16.0),
child: Wrap(
spacing: 8.0,
children: [
ElevatedButton(
onPressed: () {
// Select multiple muscles programmatically
controller.selectMultiple({
Muscle.bicepsLeft,
Muscle.bicepsRight,
});
},
child: const Text('Select Both Biceps'),
),
ElevatedButton(
onPressed: () {
// Set entire selection
controller.setSelectedMuscles({
Muscle.chestLeft,
Muscle.chestRight,
});
},
child: const Text('Select Chest Only'),
),
ElevatedButton(
onPressed: () {
// Clear all selections
controller.clearSelection();
},
child: const Text('Clear All'),
),
],
),
),
// Display selected muscles
AnimatedBuilder(
animation: controller,
builder: (context, _) {
return Container(
padding: const EdgeInsets.all(16),
color: Colors.blue.shade900,
width: double.infinity,
child: Text(
'Selected: ${controller.selectedMuscles.length} muscles',
style: const TextStyle(color: Colors.white),
textAlign: TextAlign.center,
),
);
},
),
// Body diagram
Expanded(
child: AnimatedBuilder(
animation: controller,
builder: (context, _) {
return InteractiveBodySvg(
isFront: controller.isFront,
selectedMuscles: controller.selectedMuscles,
onMuscleTap: controller.selectMuscle,
);
},
),
),
],
),
);
}
}
Customization Options #
Colors and Styling
InteractiveBodySvg(
isFront: true, // Use package's front body asset automatically
selectedMuscles: controller.selectedMuscles,
onMuscleTap: controller.selectMuscle,
highlightColor: Colors.red.withOpacity(0.7), // Custom highlight color
baseColor: Colors.white, // Custom base color for unselected muscles
selectedStrokeWidth: 3.0, // Stroke width for selected muscles
unselectedStrokeWidth: 1.0, // Stroke width for unselected muscles
)
Size and Layout
InteractiveBodySvg(
isFront: true, // Use package's front body asset automatically
selectedMuscles: controller.selectedMuscles,
onMuscleTap: controller.selectMuscle,
width: 300, // Fixed width
height: 600, // Fixed height
fit: BoxFit.cover, // How to fit the SVG
alignment: Alignment.center, // Alignment within the widget
)
Selection Behavior
InteractiveBodySvg(
isFront: true, // Use package's front body asset automatically
selectedMuscles: controller.selectedMuscles,
onMuscleTap: controller.selectMuscle,
enableSelection: true, // Enable/disable tap selection
hitTestPadding: 15.0, // Padding for hit-testing (makes taps more forgiving)
onMuscleTapDisabled: (muscle) {
// Called when a muscle is tapped but selection is disabled
print('Tapped $muscle but selection is disabled');
},
)
Using InteractiveBodyWidget
The InteractiveBodyWidget provides a complete solution with built-in UI:
InteractiveBodyWidget(
frontAsset: 'packages/flutter_body_part_selector/assets/svg/body_front.svg',
backAsset: 'packages/flutter_body_part_selector/assets/svg/body_back.svg',
onMuscleSelected: (muscle) {
print('Selected: $muscle');
},
highlightColor: Colors.blue,
baseColor: Colors.white,
showFlipButton: true, // Show flip button in app bar
showClearButton: true, // Show clear button in app bar
backgroundColor: Colors.black, // Background color
selectedMuscleHeader: (muscle) {
// Custom header widget
return Container(
padding: EdgeInsets.all(16),
child: Text('Selected: $muscle'),
);
},
)
Available Muscles #
The package supports the following muscles:
Front View #
- Traps (Left/Right)
- Delts (Left/Right)
- Chest (Left/Right)
- Abs
- Lats Front (Left/Right)
- Triceps (Left/Right)
- Biceps (Left/Right)
- Biceps Brachialis (Left/Right)
- Forearms (Left/Right)
- Quads (Left/Right)
- Calves (Left/Right)
Back View #
- Lats Back (Left/Right)
- Lower Lats Back (Left/Right)
- Glutes (Left/Right)
- Hamstrings (Left/Right)
- Triceps (Left/Right)
- Delts (Left/Right)
- Traps (Left/Right)
Assets #
IMPORTANT: This package includes the required SVG body diagrams (front and back views) that are mandatory for the package to work correctly. You must use the package assets - custom SVG files are not supported.
The package assets are pre-configured with the correct muscle IDs and mappings. Using custom assets will result in incorrect behavior.
Using Package Assets #
The package includes default SVG assets that are automatically used by InteractiveBodyWidget. You don't need to specify asset paths:
// Simplest usage - assets are included automatically
InteractiveBodyWidget(
onMuscleSelected: (muscle) {
print('Selected: $muscle');
},
)
For InteractiveBodySvg, you can use the simplified syntax without specifying asset paths:
InteractiveBodySvg(
isFront: true, // Automatically uses package's front body asset
selectedMuscles: controller.selectedMuscles,
onMuscleTap: controller.selectMuscle,
)
Or if you need to specify asset paths explicitly:
InteractiveBodySvg(
asset: 'packages/flutter_body_part_selector/assets/svg/body_front.svg',
// ...
)
Note: The package assets are automatically included when you add this package to your pubspec.yaml. No additional asset configuration is required in your app's pubspec.yaml.
API Reference #
InteractiveBodyWidget #
A complete widget with built-in controller and UI. Perfect for quick integration.
Properties:
frontAsset(String?, optional): Path to the front body SVG. Defaults to'packages/flutter_body_part_selector/assets/svg/body_front.svg'if not specified. Custom SVG files are not supported.backAsset(String?, optional): Path to the back body SVG. Defaults to'packages/flutter_body_part_selector/assets/svg/body_back.svg'if not specified. Custom SVG files are not supported.onMuscleSelected(Function(Muscle)?, optional): Callback when a muscle is selectedonSelectionCleared(VoidCallback?, optional): Callback when selection is clearedselectedMuscles(SetinitialIsFront(bool, default: true): Initial view (front or back)highlightColor(Color?, optional): Color for highlighting selected musclesbaseColor(Color?, optional): Base color for unselected musclesselectedStrokeWidth(double, default: 2.0): Stroke width for selected musclesunselectedStrokeWidth(double, default: 1.0): Stroke width for unselected musclesenableSelection(bool, default: true): Enable/disable selectionfit(BoxFit, default: BoxFit.contain): How to fit the SVGhitTestPadding(double, default: 10.0): Padding for hit-testingwidth(double?, optional): Fixed widthheight(double?, optional): Fixed heightalignment(Alignment, default: Alignment.center): Alignment of SVGshowFlipButton(bool, default: true): Show flip button in app barshowClearButton(bool, default: true): Show clear button in app barappBar(PreferredSizeWidget?, optional): Custom app barbackgroundColor(Color?, optional): Background colorselectedMuscleHeader(Widget Function(Muscle)?, optional): Custom header widget
InteractiveBodySvg #
The core widget for displaying the interactive body diagram.
Properties:
asset(String?, optional): Custom asset path. If not provided, automatically uses package assets based on [isFront]. Note: Custom SVG files are not supported - only package assets work correctly.isFront(bool, default: true): Whether to show front view. Used when [asset] is not provided to automatically select the correct package asset.selectedMuscles(SetonMuscleTap(Function(Muscle)?, optional): Callback when a muscle is tappedhighlightColor(Color?, optional): Color for highlighting selected muscles (default: Colors.blue with opacity)baseColor(Color?, optional): Base color for unselected muscles (default: Colors.white)selectedStrokeWidth(double, default: 2.0): Stroke width for selected musclesunselectedStrokeWidth(double, default: 1.0): Stroke width for unselected musclesenableSelection(bool, default: true): Enable/disable tap selectionfit(BoxFit, default: BoxFit.contain): How to fit the SVGhitTestPadding(double, default: 10.0): Padding for hit-testingwidth(double?, optional): Fixed widthheight(double?, optional): Fixed heightalignment(Alignment, default: Alignment.center): Alignment of SVGonMuscleTapDisabled(Function(Muscle)?, optional): Callback when muscle is tapped but selection is disabled
BodyMapController #
Controller for managing the body selector state.
Constructor:
BodyMapController({Set<Muscle>? initialSelectedMuscles, Set<Muscle>? initialDisabledMuscles, bool initialIsFront = true}): Create a controller with optional initial state
Selection Methods (Writable):
selectMuscle(Muscle): Select or toggle a muscle (if already selected, deselects it)toggleMuscle(Muscle): Explicitly toggle a muscle's selection statedeselectMuscle(Muscle): Deselect a specific musclesetSelectedMuscles(Set<Muscle>): Set the entire selection (replaces current selection)selectMultiple(Set<Muscle>): Add multiple muscles to current selection (without clearing)clearSelection(): Clear all selections
View Methods (Writable):
toggleView(): Toggle between front and back viewsetFrontView(): Set view to frontsetBackView(): Set view to back
Disabled Muscle Methods (Writable):
disableMuscle(Muscle): Disable a muscle (locks it, removes from selection)enableMuscle(Muscle): Enable a muscle (unlocks it)setDisabledMuscles(Set<Muscle>): Set multiple disabled muscles at once
Properties:
selectedMuscles(SetdisabledMuscles(SetisFront(bool, writable): Whether showing front view. Can be set directly or use view methods.isSelected(Muscle)(bool, read-only): Check if a muscle is selectedisDisabled(Muscle)(bool, read-only): Check if a muscle is disabled
Muscle #
Enum representing all available muscles. See the "Available Muscles" section above for the complete list.
Common Pitfalls #
❌ Don't: Try to modify selectedMuscles Set directly #
// ❌ WRONG - This won't work because the Set is unmodifiable
controller.selectedMuscles.add(Muscle.bicepsLeft); // Error!
controller.selectedMuscles.clear(); // Error!
✅ Do: Use the setter or provided methods #
// ✅ CORRECT - Use the setter (replaces entire selection)
controller.selectedMuscles = {Muscle.bicepsLeft, Muscle.tricepsRight};
// ✅ CORRECT - Or use the controller methods
controller.selectMuscle(Muscle.bicepsLeft);
controller.setSelectedMuscles({Muscle.bicepsLeft, Muscle.tricepsRight});
controller.clearSelection();
❌ Don't: Forget to listen to controller changes #
// ❌ WRONG - UI won't update when selection changes
final controller = BodyMapController();
controller.selectMuscle(Muscle.bicepsLeft);
// Widget won't rebuild automatically
✅ Do: Use AnimatedBuilder or listen to changes #
// ✅ CORRECT - Wrap with AnimatedBuilder
AnimatedBuilder(
animation: controller,
builder: (context, _) {
return Text('Selected: ${controller.selectedMuscles.length}');
},
)
❌ Don't: Create controller in build method #
// ❌ WRONG - Creates new controller on every rebuild
Widget build(BuildContext context) {
final controller = BodyMapController(); // Don't do this!
return InteractiveBodySvg(...);
}
✅ Do: Create controller in initState or use late initialization #
// ✅ CORRECT - Create once in initState
class _MyWidgetState extends State<MyWidget> {
late BodyMapController controller;
@override
void initState() {
super.initState();
controller = BodyMapController();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
❌ Don't: Forget to dispose the controller #
// ❌ WRONG - Memory leak!
class _MyWidgetState extends State<MyWidget> {
final controller = BodyMapController();
// Missing dispose() call
}
✅ Do: Always dispose controllers #
// ✅ CORRECT - Always dispose
@override
void dispose() {
controller.dispose();
super.dispose();
}
❌ Don't: Try to use custom SVG assets #
// ❌ WRONG - Custom SVG files are not supported
InteractiveBodySvg(
asset: 'assets/my_custom_body.svg', // Won't work correctly!
)
✅ Do: Use the package's included assets #
// ✅ CORRECT - Simplest way (automatically uses package assets)
InteractiveBodySvg(
isFront: true, // Automatically uses package's front body asset
)
// ✅ CORRECT - Or specify asset path explicitly
InteractiveBodySvg(
asset: 'packages/flutter_body_part_selector/assets/svg/body_front.svg',
)
// ✅ CORRECT - Or use InteractiveBodyWidget which uses defaults automatically
InteractiveBodyWidget(...)
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
License #
This project is licensed under the MIT License.
Support #
If you encounter any issues or have questions, please file an issue on the GitHub repository.