Skip to content

Instantly share code, notes, and snippets.

@ScottS2017
Created June 16, 2020 13:09
Show Gist options
  • Save ScottS2017/95defe55161ce376dba3b260b51f8d0f to your computer and use it in GitHub Desktop.
Save ScottS2017/95defe55161ce376dba3b260b51f8d0f to your computer and use it in GitHub Desktop.
Code to smoothly handle rotations in Flutter
// MIT License
//
// Copyright (c) 2020 Scott Stoll
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
// KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:aeyrium_sensor/aeyrium_sensor.dart';
//TODO add "aeyrium_sensor: (current version)" to pubspec.yaml
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
static RotationController rotationControllerOf(BuildContext context) {
final _MyAppState state = context.findAncestorStateOfType<_MyAppState>();
return state._rotationController;
}
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
RotationController _rotationController;
@override
void initState() {
super.initState();
_rotationController = RotationController();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'AnagramScapes',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Home(),
);
}
}
class Home extends StatefulWidget {
const Home({Key key, this.level}) : super(key: key);
final int level;
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
@override
void initState() {
super.initState();
}
double _currentRoll = 0;
int _currentQuadrant = 0;
@override
Widget build(BuildContext context) {
final RotationController _rotationController = MyApp.rotationControllerOf(context);
AeyriumSensor.sensorEvents.listen((SensorEvent event) {
_currentRoll = double.parse(event.roll.toStringAsFixed(1));
if (_currentRoll < 0.9 && _currentRoll > -0.9) {
/// Up
if (_currentQuadrant != 0) {
_currentQuadrant = 0;
_rotationController.updateDisplay(currentQuadrant: 0);
}
} else if (_currentRoll >= 0.9 && _currentRoll <= 2.4) {
if (_currentQuadrant != 1) {
/// Rotated Right
_currentQuadrant = 1;
_rotationController.updateDisplay(currentQuadrant: 1);
}
} else if (_currentRoll <= -0.9 && _currentRoll >= -2.4) {
if (_currentQuadrant != 3) {
/// Rotated Left
_currentQuadrant = 3;
_rotationController.updateDisplay(currentQuadrant: 3);
}
} else {
if (_currentQuadrant != 2) {
/// Rotated Down
_currentQuadrant = 2;
_rotationController.updateDisplay(currentQuadrant: 2);
}
}
});
return Material(
child: Center(
child: ValueListenableBuilder<double>(
valueListenable: _rotationController.orientationInRadians,
builder: (BuildContext context, double value, _) {
return TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0, end: value),
duration: const Duration(milliseconds: 500),
builder: (context, anim, child) {
return Transform.rotate(
angle: anim,
child: child,
);
},
child: const ThingBeingRotated(),
);
},
),
),
);
}
}
class RotationController {
final ValueNotifier<double> _orientationInRadians = ValueNotifier<double>(0);
ValueNotifier<double> get orientationInRadians => _orientationInRadians;
int previousQuadrant = 0;
void updateDisplay({int currentQuadrant}) {
/// This tracks the device's actual orientation, not the orientation
/// of the item being rotated. This is because sometimes people rotated
/// a device rapidly, and you want to ensure the rotation doesn't jump.
/// By adding 1/4 rotation per change to the total value, the animation
/// will just keep moving until it reaches the value. This way we can add
/// or subtract much more quickly than the rotated item can rotate and
/// it will still keep rotating smoothly until it catches up.
switch (currentQuadrant) {
case 0:
if (previousQuadrant == 1) {
_orientationInRadians.value -= pi * .5;
} else if (previousQuadrant == 3) {
_orientationInRadians.value += pi * .5;
}
previousQuadrant = currentQuadrant;
break;
case 1:
if (previousQuadrant == 2) {
_orientationInRadians.value -= pi * .5;
} else if (previousQuadrant == 0) {
_orientationInRadians.value += pi * .5;
}
previousQuadrant = currentQuadrant;
break;
case 2:
if (previousQuadrant == 3) {
_orientationInRadians.value -= pi * .5;
} else if (previousQuadrant == 1) {
_orientationInRadians.value += pi * .5;
}
previousQuadrant = currentQuadrant;
break;
case 3:
if (previousQuadrant == 0) {
_orientationInRadians.value -= pi * .5;
} else if (previousQuadrant == 2) {
_orientationInRadians.value += pi * .5;
}
previousQuadrant = currentQuadrant;
break;
}
}
}
class ThingBeingRotated extends StatelessWidget {
const ThingBeingRotated({
Key key,
}) : super(key:key);
@override
Widget build(BuildContext context) {
return Container(
height: 200,
width: 200,
color: Colors.red,
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment