flutter_hand_gesture 2.1.1 copy "flutter_hand_gesture: ^2.1.1" to clipboard
flutter_hand_gesture: ^2.1.1 copied to clipboard

A Flutter package for real-time hand gesture recognition using on-device ML. Supports custom gestures, landmark detection, and stream-based API. No internet required.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_hand_gesture/flutter_hand_gesture.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Hand Gesture Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData.dark(useMaterial3: true),
      home: const HomeShell(),
    );
  }
}

/// Hosts the three v2 demos behind a [BottomNavigationBar].
class HomeShell extends StatefulWidget {
  const HomeShell({super.key});

  @override
  State<HomeShell> createState() => _HomeShellState();
}

class _HomeShellState extends State<HomeShell> {
  int _index = 0;

  static const _tabs = [
    TamilSignScreen(),
    SequenceDemoScreen(),
    PresentationScreen(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(index: _index, children: _tabs),
      bottomNavigationBar: NavigationBar(
        selectedIndex: _index,
        onDestinationSelected: (i) => setState(() => _index = i),
        destinations: const [
          NavigationDestination(icon: Icon(Icons.sign_language), label: 'Tamil'),
          NavigationDestination(icon: Icon(Icons.timeline), label: 'Sequence'),
          NavigationDestination(icon: Icon(Icons.slideshow), label: 'Present'),
        ],
      ),
    );
  }
}

// ─────────────────────────────────────────────────────────────────────────────
// Tab 1 — Tamil signs
// ─────────────────────────────────────────────────────────────────────────────

/// Recognizes Tamil/ISL signs from live landmarks using a [SignRecognizer],
/// which combines finger shape, hand orientation, and motion — so thumbs-up
/// (*nalla*) vs thumbs-down (*ketta*) and the wagging index (*illai*) are told
/// apart.
class TamilSignScreen extends StatefulWidget {
  const TamilSignScreen({super.key});

  @override
  State<TamilSignScreen> createState() => _TamilSignScreenState();
}

class _TamilSignScreenState extends State<TamilSignScreen> {
  final _recognizer = SignRecognizer<TamilSign>(TamilSignPreset.signs);
  TamilSign? _sign;

  void _onResult(GestureResult result) {
    final hand = result.primaryHand;
    if (hand == null) {
      _recognizer.reset();
      return;
    }
    final sign = _recognizer.update(hand.landmarks, result.timestamp);
    if (sign?.name != _sign?.name) {
      setState(() => _sign = sign);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        GestureCameraView(
          // Zero debounce so every frame reaches the recognizer — motion
          // (the wagging index of *illai*) needs a continuous stream.
          config: const GestureConfig(
            maxHands: 1,
            showLandmarks: true,
            debounceDuration: Duration.zero,
          ),
          showGestureLabel: false,
          onResult: _onResult,
        ),
        const Positioned(
          top: 0,
          left: 0,
          right: 0,
          child: SafeArea(
            child: Padding(
              padding: EdgeInsets.all(16),
              child: Text(
                'தமிழ் சைகை — Tamil Signs',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 18,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          ),
        ),
        if (_sign != null)
          Positioned(
            bottom: 32,
            left: 16,
            right: 16,
            child: _SignCard(sign: _sign!),
          ),
      ],
    );
  }
}

class _SignCard extends StatelessWidget {
  final TamilSign sign;
  const _SignCard({required this.sign});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(20),
      decoration: BoxDecoration(
        color: Colors.black.withValues(alpha: 0.7),
        borderRadius: BorderRadius.circular(20),
        border: Border.all(color: Colors.cyanAccent.withValues(alpha: 0.5)),
      ),
      child: Row(
        children: [
          Text(sign.emoji, style: const TextStyle(fontSize: 44)),
          const SizedBox(width: 16),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  sign.tamilScript,
                  style: const TextStyle(
                    color: Colors.white,
                    fontSize: 32,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                Text(
                  sign.name,
                  style: const TextStyle(
                    color: Colors.cyanAccent,
                    fontSize: 16,
                  ),
                ),
                Text(
                  sign.meaning,
                  style: const TextStyle(color: Colors.white70, fontSize: 13),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// ─────────────────────────────────────────────────────────────────────────────
// Tab 2 — Sequence demo
// ─────────────────────────────────────────────────────────────────────────────

/// Registers a couple of gesture sequences and shows live progress dots, firing
/// a banner when one completes.
class SequenceDemoScreen extends StatefulWidget {
  const SequenceDemoScreen({super.key});

  @override
  State<SequenceDemoScreen> createState() => _SequenceDemoScreenState();
}

class _SequenceDemoScreenState extends State<SequenceDemoScreen> {
  final _recognizer = GestureSequenceRecognizer()
    ..registerAll([
      const GestureSequence(
        name: 'unlock',
        label: 'Unlock',
        steps: [
          GestureType.thumbsUp,
          GestureType.peace,
          GestureType.openPalm,
        ],
      ),
      const GestureSequence(
        name: 'dismiss',
        label: 'Dismiss',
        steps: [GestureType.openPalm, GestureType.fist],
      ),
    ]);

  Map<String, int> _progress = {};
  String? _fired;

  void _onGesture(HandGesture gesture) {
    final matched = _recognizer.update(gesture.type, gesture.timestamp);
    setState(() {
      _progress = _recognizer.progress;
      if (matched != null) _fired = matched.label;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        GestureCameraView(
          config: const GestureConfig(maxHands: 1),
          onGestureDetected: _onGesture,
        ),
        Positioned(
          top: 0,
          left: 0,
          right: 0,
          child: SafeArea(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Sequences',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 4),
                  Text(
                    'unlock: 👍 → ✌️ → 🖐   ·   dismiss: 🖐 → ✊',
                    style: TextStyle(
                      color: Colors.white.withValues(alpha: 0.6),
                      fontSize: 12,
                    ),
                  ),
                  const SizedBox(height: 16),
                  _ProgressRows(
                    recognizer: _recognizer,
                    progress: _progress,
                  ),
                ],
              ),
            ),
          ),
        ),
        if (_fired != null)
          Positioned(
            bottom: 40,
            left: 0,
            right: 0,
            child: Center(
              child: Container(
                padding:
                    const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
                decoration: BoxDecoration(
                  color: Colors.greenAccent.withValues(alpha: 0.85),
                  borderRadius: BorderRadius.circular(24),
                ),
                child: Text(
                  '✅ ${_fired!}',
                  style: const TextStyle(
                    color: Colors.black,
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            ),
          ),
      ],
    );
  }
}

/// Renders a labeled progress-dot row per sequence using
/// [SequenceProgressPainter].
class _ProgressRows extends StatelessWidget {
  final GestureSequenceRecognizer recognizer;
  final Map<String, int> progress;

  const _ProgressRows({required this.recognizer, required this.progress});

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        for (final seq in recognizer.sequences)
          Padding(
            padding: const EdgeInsets.only(bottom: 8),
            child: Row(
              children: [
                SizedBox(
                  width: 90,
                  child: Text(
                    seq.label,
                    style: const TextStyle(color: Colors.white, fontSize: 14),
                  ),
                ),
                CustomPaint(
                  size: Size(seq.steps.length * 20.0, 16),
                  painter: SequenceProgressPainter(
                    progress: {seq.name: progress[seq.name] ?? 0},
                    totalSteps: {seq.name: seq.steps.length},
                  ),
                ),
              ],
            ),
          ),
      ],
    );
  }
}

// ─────────────────────────────────────────────────────────────────────────────
// Tab 3 — Presentation mode
// ─────────────────────────────────────────────────────────────────────────────

/// Maps gestures to slide actions via [GestureActionMapper.presentation].
class PresentationScreen extends StatefulWidget {
  const PresentationScreen({super.key});

  @override
  State<PresentationScreen> createState() => _PresentationScreenState();
}

class _PresentationScreenState extends State<PresentationScreen> {
  final _mapper = GestureActionMapper.presentation();
  int _slide = 1;
  static const _totalSlides = 10;
  String? _lastAction;
  bool _blackScreen = false;

  void _onGesture(HandGesture gesture) {
    final action = _mapper.resolve(gesture.type);
    if (action == null) return;
    setState(() {
      _lastAction = action;
      switch (action) {
        case 'next_slide':
          _slide = (_slide + 1).clamp(1, _totalSlides);
          _blackScreen = false;
        case 'prev_slide':
          _slide = (_slide - 1).clamp(1, _totalSlides);
          _blackScreen = false;
        case 'black_screen':
          _blackScreen = !_blackScreen;
        case 'pause_timer':
          break;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        // Slide canvas
        Positioned.fill(
          child: ColoredBox(
            color: _blackScreen ? Colors.black : const Color(0xFF1A237E),
            child: Center(
              child: _blackScreen
                  ? const Text(
                      'Black screen',
                      style: TextStyle(color: Colors.white24, fontSize: 18),
                    )
                  : Text(
                      'Slide $_slide / $_totalSlides',
                      style: const TextStyle(
                        color: Colors.white,
                        fontSize: 40,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
            ),
          ),
        ),
        // Small camera preview corner
        Positioned(
          bottom: 24,
          right: 16,
          width: 130,
          height: 180,
          child: ClipRRect(
            borderRadius: BorderRadius.circular(16),
            child: GestureCameraView(
              config: const GestureConfig(maxHands: 1),
              showGestureLabel: false,
              showLandmarks: false,
              onGestureDetected: _onGesture,
            ),
          ),
        ),
        Positioned(
          top: 0,
          left: 0,
          right: 0,
          child: SafeArea(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Presentation Mode',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 4),
                  Text(
                    '👆 next · 👎 prev · ✊ black · 🖐 pause',
                    style: TextStyle(
                      color: Colors.white.withValues(alpha: 0.7),
                      fontSize: 12,
                    ),
                  ),
                  if (_lastAction != null)
                    Padding(
                      padding: const EdgeInsets.only(top: 8),
                      child: Text(
                        'action: $_lastAction',
                        style: const TextStyle(
                          color: Colors.cyanAccent,
                          fontSize: 13,
                        ),
                      ),
                    ),
                ],
              ),
            ),
          ),
        ),
      ],
    );
  }
}
0
likes
150
points
76
downloads

Documentation

API reference

Publisher

verified publisherkalainovainfotech.com

Weekly Downloads

A Flutter package for real-time hand gesture recognition using on-device ML. Supports custom gestures, landmark detection, and stream-based API. No internet required.

Repository (GitHub)
View/report issues

Topics

#gesture #hand-tracking #mediapipe #camera #machine-learning

License

MIT (license)

Dependencies

camera, flutter, hand_detection, path_provider

More

Packages that depend on flutter_hand_gesture