motion_core 0.2.0 copy "motion_core: ^0.2.0" to clipboard
motion_core: ^0.2.0 copied to clipboard

A high-performance Flutter plugin that provides a simple, unified stream of fused device motion data, abstracting native APIs like iOS Core Motion and Android's Rotation Vector sensor.

example/lib/main.dart

import 'dart:async';
import 'dart:math' show pi;

import 'package:cubixd/cubixd.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show PlatformException;
import 'package:motion_core/motion_core.dart';
import 'package:vector_math/vector_math_64.dart' show Vector2, Vector3;

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Motion Visualizer',
      theme: ThemeData.dark(),
      home: const MotionDemoScreen(),
    );
  }
}

class MotionDemoScreen extends StatefulWidget {
  const MotionDemoScreen({super.key});

  @override
  State<MotionDemoScreen> createState() => _MotionDemoScreenState();
}

class _MotionDemoScreenState extends State<MotionDemoScreen> {
  static const _rates = [15, 30, 60, 100];

  MotionData? _motionData;
  StreamSubscription<MotionData>? _subscription;

  /// `null` while availability is still being checked.
  bool? _isAvailable;
  List<AttitudeReferenceFrame> _availableFrames = const [];
  AttitudeReferenceFrame _frame = AttitudeReferenceFrame.magneticNorthZVertical;
  int _rateHz = 60;
  String? _error;

  @override
  void initState() {
    super.initState();
    _init();
  }

  Future<void> _init() async {
    final available = await MotionCore.isAvailable();
    final frames = await MotionCore.availableReferenceFrames();
    if (!mounted) return;
    setState(() {
      _isAvailable = available;
      _availableFrames = frames;
    });
    if (available) {
      await _applyConfiguration();
      _startListening();
    }
  }

  Future<void> _applyConfiguration() {
    return MotionCore.configure(
      referenceFrame: _frame,
      updateInterval: Duration(microseconds: 1000000 ~/ _rateHz),
    );
  }

  void _startListening() {
    _subscription = MotionCore.motionStream.listen(
      (data) {
        if (!mounted) return;
        setState(() {
          _motionData = data;
          _error = null;
        });
      },
      onError: (Object error) {
        if (!mounted) return;
        setState(() {
          _error = error is PlatformException
              ? '${error.code}: ${error.message}'
              : '$error';
        });
      },
    );
  }

  @override
  void dispose() {
    _subscription?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Motion Visualizer')),
      backgroundColor: Colors.black,
      body: switch (_isAvailable) {
        null => const Center(child: CircularProgressIndicator()),
        false => const Center(child: Text('Motion sensors not available')),
        true => _buildContent(),
      },
    );
  }

  Widget _buildContent() {
    final data = _motionData;
    return ListView(
      padding: const EdgeInsets.symmetric(vertical: 16),
      children: [
        _buildControls(),
        if (_error != null)
          Padding(
            padding: const EdgeInsets.all(16),
            child: Text(_error!, style: const TextStyle(color: Colors.redAccent)),
          ),
        if (data == null)
          const Padding(
            padding: EdgeInsets.all(48),
            child: Center(child: CircularProgressIndicator()),
          )
        else ...[
          const SizedBox(height: 16),
          SizedBox(height: 220, child: _buildCube(data)),
          const SizedBox(height: 16),
          Row(
            children: [
              Expanded(child: _buildGravityCompass(data)),
              Expanded(child: _buildHeadingCompass(data)),
              Expanded(child: _buildAccelerationIndicator(data)),
            ],
          ),
          const SizedBox(height: 16),
          _buildDataTable(data),
        ],
      ],
    );
  }

  Widget _buildControls() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Row(
        children: [
          Expanded(
            child: DropdownButton<AttitudeReferenceFrame>(
              isExpanded: true,
              value: _frame,
              items: [
                for (final frame in AttitudeReferenceFrame.values)
                  DropdownMenuItem(
                    value: frame,
                    child: Text(
                      _availableFrames.contains(frame)
                          ? frame.name
                          : '${frame.name} (fallback)',
                      overflow: TextOverflow.ellipsis,
                    ),
                  ),
              ],
              onChanged: (frame) async {
                if (frame == null) return;
                setState(() => _frame = frame);
                await _applyConfiguration();
              },
            ),
          ),
          const SizedBox(width: 16),
          DropdownButton<int>(
            value: _rateHz,
            items: [
              for (final hz in _rates)
                DropdownMenuItem(value: hz, child: Text('$hz Hz')),
            ],
            onChanged: (hz) async {
              if (hz == null) return;
              setState(() => _rateHz = hz);
              await _applyConfiguration();
            },
          ),
        ],
      ),
    );
  }

  Widget _buildCube(MotionData data) {
    final Vector3 euler = data.eulerAngles;
    return Center(
      child: CubixD(
        size: 180,
        delta: Vector2(-euler.x, -euler.y),
        onSelected: (_, __) {},
        front: _face(Colors.yellow),
        back: _face(Colors.pink),
        left: _face(Colors.blue),
        right: _face(Colors.green),
        top: _face(Colors.red),
        bottom: _face(Colors.orange),
      ),
    );
  }

  Widget _face(Color color) => Container(
        margin: const EdgeInsets.all(1.5),
        child: Container(color: color.withValues(alpha: .7)),
      );

  Widget _buildGravityCompass(MotionData data) {
    final Vector3 g = data.gravity.normalized();
    return SizedBox(
      height: 100,
      child: CustomPaint(
        painter: _VectorArrowPainter(vector: g, color: Colors.purple),
        child: const Center(child: Text('Gravity')),
      ),
    );
  }

  Widget _buildHeadingCompass(MotionData data) {
    final heading = data.heading;
    return SizedBox(
      height: 100,
      child: heading == null
          ? const Center(child: Text('No heading'))
          : Transform.rotate(
              // Rotate the needle so it keeps pointing north on screen.
              angle: -heading * pi / 180,
              child: const Icon(Icons.navigation, size: 64, color: Colors.redAccent),
            ),
    );
  }

  Widget _buildAccelerationIndicator(MotionData data) {
    // userAcceleration is in m/s²; treat 1 g as "full" intensity.
    final double intensity = (data.userAcceleration.length / 9.81).clamp(0.0, 1.0);
    return Center(
      child: Container(
        width: 50 + intensity * 50,
        height: 50 + intensity * 50,
        decoration: BoxDecoration(
          shape: BoxShape.circle,
          color: Colors.teal.withValues(alpha: 0.4 + 0.6 * intensity),
        ),
        child: const Center(child: Text('ACC')),
      ),
    );
  }

  Widget _buildDataTable(MotionData d) {
    String deg(double r) => '${(r * 180 / pi).toStringAsFixed(1)}°';
    String optional(double? v, String Function(double) format) =>
        v == null ? 'N/A' : format(v);
    final magnetic = d.magneticField;

    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Table(
        columnWidths: const {0: IntrinsicColumnWidth()},
        children: [
          _row('Frame', d.referenceFrame.name),
          _row('Pitch', deg(d.pitch)),
          _row('Roll', deg(d.roll)),
          _row('Yaw', deg(d.yaw)),
          _row('Heading', optional(d.heading, (h) => '${h.toStringAsFixed(1)}°')),
          _row('Heading acc.', optional(d.headingAccuracy, deg)),
          _row('Gravity (m/s²)', _vec(d.gravity)),
          _row('User acc. (m/s²)', _vec(d.userAcceleration)),
          _row('Rotation (rad/s)',
              d.rotationRate == null ? 'N/A' : _vec(d.rotationRate!)),
          _row(
            'Magnetic (µT)',
            magnetic == null
                ? 'N/A'
                : '${_vec(magnetic.field)} (${magnetic.accuracy.name})',
          ),
          _row('Timestamp', '${d.timestamp.toStringAsFixed(3)} s'),
        ],
      ),
    );
  }

  TableRow _row(String label, String value) => TableRow(children: [
        Padding(
          padding: const EdgeInsets.all(6),
          child: Text(label, style: const TextStyle(fontWeight: FontWeight.bold)),
        ),
        Padding(padding: const EdgeInsets.all(6), child: Text(value)),
      ]);

  String _vec(Vector3 v) =>
      'X:${v.x.toStringAsFixed(2)}, Y:${v.y.toStringAsFixed(2)}, Z:${v.z.toStringAsFixed(2)}';
}

class _VectorArrowPainter extends CustomPainter {
  _VectorArrowPainter({required this.vector, required this.color});

  final Vector3 vector;
  final Color color;

  @override
  void paint(Canvas canvas, Size size) {
    final Paint paint = Paint()
      ..color = color
      ..strokeWidth = 4
      ..style = PaintingStyle.stroke;

    final center = Offset(size.width / 2, size.height / 2);
    final dx = vector.x * size.width / 2;
    final dy = -vector.y * size.height / 2; // invert y for screen coords
    final end = Offset(center.dx + dx, center.dy + dy);

    canvas.drawLine(center, end, paint);
    canvas.drawCircle(end, 6, paint);
  }

  @override
  bool shouldRepaint(covariant _VectorArrowPainter oldDelegate) =>
      oldDelegate.vector != vector || oldDelegate.color != color;
}
3
likes
150
points
281
downloads

Documentation

API reference

Publisher

verified publisherfendomoney.com

Weekly Downloads

A high-performance Flutter plugin that provides a simple, unified stream of fused device motion data, abstracting native APIs like iOS Core Motion and Android's Rotation Vector sensor.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, vector_math

More

Packages that depend on motion_core

Packages that implement motion_core