SButton

Pub Version License: MIT

A highly customizable Flutter button widget with rich animations, visual effects, and interaction modes. Perfect for creating engaging user interfaces with splash effects, bounce animations, haptic feedback, and more.

Demo

Demo

πŸ“‹ Features

  • Rich Animations

    • Bounce animation with configurable scale
    • Splash effects with custom colors and opacity
    • Smooth color transitions and overlays
    • Delayed initialization support
  • Multiple Interaction Modes

    • Single tap with offset detection
    • Double tap support
    • Long press with start/end callbacks
    • Customizable hit test behavior
  • Visual Customization

    • Circle or rectangle button shapes
    • Custom border radius
    • Splashcolor and selected state overlays
    • Loading state with customizable loading widget
    • Error state handling with error builder
  • Enhanced User Feedback

    • Bubble label tooltips (platform-aware)
    • Haptic feedback (multiple feedback types)
    • Tooltip messages
    • Active/inactive state management
  • Advanced Features

    • Comprehensive state management
    • Custom animation controls
    • Flexible child widget support
    • Error handling and recovery
    • Built with Material Design principles

πŸ“¦ Installation

Add this to your pubspec.yaml:

dependencies:
  s_button: ^1.1.0

Then run:

flutter pub get

πŸš€ Quick Start

Basic Usage

import 'package:s_button/s_button.dart';

SButton(
  onTap: (offset) {
    print('Button tapped at: $offset');
  },
  child: Container(
    padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
    decoration: BoxDecoration(
      color: Colors.blue,
      borderRadius: BorderRadius.circular(8),
    ),
    child: const Text(
      'Tap Me',
      style: TextStyle(color: Colors.white),
    ),
  ),
)

With Splash Effect

SButton(
  onTap: (offset) => print('Tapped'),
  splashColor: Colors.blue.withOpacity(0.3),
  splashOpacity: 0.5,
  shouldBounce: true,
  bounceScale: 0.95,
  child: const Padding(
    padding: EdgeInsets.all(16.0),
    child: Text('Click Me'),
  ),
)

Circle Button

SButton(
  isCircleButton: true,
  onTap: (offset) => print('Circular button tapped'),
  child: Container(
    width: 60,
    height: 60,
    decoration: const BoxDecoration(
      shape: BoxShape.circle,
      color: Colors.green,
    ),
    child: const Icon(Icons.add, color: Colors.white),
  ),
)

πŸ“š Comprehensive Usage

Advanced Interaction Handling

SButton(
  onTap: (offset) {
    print('Single tap at: ${offset.dx}, ${offset.dy}');
  },
  onDoubleTap: (offset) {
    print('Double tapped at: $offset');
  },
  onLongPressStart: (details) {
    print('Long press started');
  },
  onLongPressEnd: (details) {
    print('Long press ended');
  },
  child: const Padding(
    padding: EdgeInsets.all(16.0),
    child: Text('Multiple Interactions'),
  ),
)

With Bubble Label (Tooltip)

SButton(
  onTap: (offset) => print('Tapped'),
  bubbleLabelContent: BubbleLabelContent(
    child: const Text('This is a helpful tooltip!'),
    shouldActivateOnLongPressOnAllPlatforms: false,
  ),
  child: const Padding(
    padding: EdgeInsets.all(16.0),
    child: Icon(Icons.info),
  ),
)

Loading State

SButton(
  isLoading: isLoading,
  onTap: (offset) async {
    // Handle loading state
  },
  loadingWidget: const CircularProgressIndicator(),
  child: const Padding(
    padding: EdgeInsets.all(16.0),
    child: Text('Loading...'),
  ),
)

With Error Handling

SButton(
  onTap: (offset) {
    try {
      // Perform operation
    } catch (e) {
      widget.onError?.call(e);
    }
  },
  onError: (error) {
    print('Error occurred: $error');
  },
  errorBuilder: (context, error) {
    return Container(
      padding: const EdgeInsets.all(8.0),
      color: Colors.red,
      child: Text('Error: $error'),
    );
  },
  child: const Text('Operation'),
)

Haptic Feedback

SButton(
  onTap: (offset) => print('Tapped with feedback'),
  enableHapticFeedback: true,
  hapticFeedbackType: HapticFeedbackType.mediumImpact,
  child: const Padding(
    padding: EdgeInsets.all(16.0),
    child: Text('Haptic Button'),
  ),
)

Full Example with All Features

SButton(
  // Styling
  splashColor: Colors.blue,
  splashOpacity: 0.4,
  selectedColor: Colors.blue.withOpacity(0.2),
  borderRadius: BorderRadius.circular(12), // Clips child and applies to splash/overlay
  
  // Animations
  shouldBounce: true,
  bounceScale: 0.97,
  delay: const Duration(milliseconds: 200),
  
  // Interactions
  onTap: (offset) => _handleTap(offset),
  onDoubleTap: (offset) => _handleDoubleTap(offset),
  onLongPressStart: (details) => _handleLongPressStart(details),
  onLongPressEnd: (details) => _handleLongPressEnd(details),
  
  // States
  isActive: true,
  isLoading: isLoading,
  isCircleButton: false,
  
  // Feedback
  enableHapticFeedback: true,
  hapticFeedbackType: HapticFeedbackType.lightImpact,
  tooltipMessage: 'Press to perform action',
  bubbleLabelContent: BubbleLabelContent(
    child: const Text('Long press for more info'),
  ),
  
  // Error handling
  onError: (error) => _handleError(error),
  errorBuilder: (context, error) => _buildErrorWidget(context, error),
  
  // Custom loading widget
  loadingWidget: const SizedBox(
    width: 20,
    height: 20,
    child: CircularProgressIndicator(strokeWidth: 2),
  ),
  
  // Behavior
  hitTestBehavior: HitTestBehavior.opaque,
  
  child: Container(
    padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
    decoration: BoxDecoration(
      color: Colors.blue,
      borderRadius: BorderRadius.circular(12),
    ),
    child: const Text(
      'Advanced Button',
      style: TextStyle(
        color: Colors.white,
        fontWeight: FontWeight.w600,
      ),
    ),
  ),
)

🎨 Customization

All Available Properties

SButton(
  // Required
  required Widget child,
  
  // Shape & Border Radius
  BorderRadius? borderRadius, // Clips child and applies to splash/selected overlay
  bool isCircleButton = false,
  AlignmentGeometry? alignment,
  
  // Splash Effects
  Color? splashColor,
  double? splashOpacity,
  
  // Animation
  bool shouldBounce = true,
  double bounceScale = 0.98,
  Duration? delay,
  
  // Interactions
  void Function(Offset)? onTap,
  void Function(Offset)? onDoubleTap,
  void Function(LongPressStartDetails)? onLongPressStart,
  void Function(LongPressEndDetails)? onLongPressEnd,
  
  // State
  bool isActive = true,
  bool isLoading = false,
  Color? selectedColor,
  
  // Feedback
  bool enableHapticFeedback = true,
  HapticFeedbackType hapticFeedbackType = HapticFeedbackType.lightImpact,
  String? tooltipMessage,
  BubbleLabelContent? bubbleLabelContent,
  
  // Loading & Error
  Widget? loadingWidget,
  Function(Object)? onError,
  Widget Function(BuildContext, Object)? errorBuilder,
  
  // Behavior
  HitTestBehavior hitTestBehavior = HitTestBehavior.opaque,
  bool ignoreChildWidgetOnTap = false,
)

πŸ–ΌοΈ Examples

Check the example/ folder for a complete Flutter application demonstrating:

  • Basic button usage
  • Advanced interactions
  • Custom styling
  • Loading and error states
  • Multiple button variations

πŸ§ͺ Testing

The package includes comprehensive unit tests. Run tests with:

flutter test

πŸ“± Platform Support

  • βœ… iOS
  • βœ… Android
  • βœ… Web
  • βœ… macOS
  • βœ… Windows
  • βœ… Linux

πŸ”— Dependencies

  • flutter_animate - For advanced animations
  • soundsliced_tween_animation_builder - Custom tween animations
  • bubble_label - Bubble label tooltips
  • ticker_free_circular_progress_indicator - Loading indicators
  • s_ink_button - Underlying ink button effects
  • s_disabled - Disabled state management
  • soundsliced_dart_extensions - Utility extensions

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

πŸ“ž Support

For issues, questions, or suggestions:

🎯 Roadmap

  • Web-specific optimizations
  • Additional haptic feedback patterns
  • More bubble label customization options
  • Animation preset library
  • Accessibility improvements

Made with ❀️ by SoundSliced

Libraries

s_button