Skip to content

Instantly share code, notes, and snippets.

@kouseralamin
Last active January 11, 2022 10:58
Show Gist options
  • Save kouseralamin/333d08328d0d7349b4bd53d755bc9ae9 to your computer and use it in GitHub Desktop.
Save kouseralamin/333d08328d0d7349b4bd53d755bc9ae9 to your computer and use it in GitHub Desktop.
Animatics
import 'dart:convert';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Ken Burn Instruction Widget',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: [
TextButton(
onPressed: () {
double aspectRatio = 0.0;
showDialog(
context: context,
builder: (context) {
return Dialog(
child: Column(
children: [
const Text('Aspect Ratio'),
TextField(
keyboardType: TextInputType.number,
onChanged: (value) {
aspectRatio = double.parse(value);
},
),
TextButton(
onPressed: () {
Navigator.pop(context, aspectRatio.toString());
},
child: const Text('OK'),),
],
),
);
},
).then((value) {
if (value != null) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => KenBurnIWC(aspectRatio)),
);
}
});
},
child: const Text('Create Screen'),
),
TextButton(
onPressed: () {
String json = '';
showDialog(
context: context,
builder: (context) {
return Dialog(
child: Column(
children: [
const Text('JSON'),
TextField(
onChanged: (value) {
json = value;
},
),
TextButton(
onPressed: () {
Navigator.pop(context, json);
},
child: const Text('OK'),),
],
),
);
},
).then((value) {
if (value != null) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => CreateViewer(json)),
);
}
});
},
child: const Text('View Shared'),
),
],
),
);
}
}
class CreateViewer extends StatefulWidget {
const CreateViewer(this.instruction, {Key? key}) : super(key: key);
final String instruction;
@override
State<CreateViewer> createState() => _CreateViewerState();
}
class _CreateViewerState extends State<CreateViewer> {
Map i = {};
@override
void initState() {
super.initState();
i = jsonDecode(widget.instruction);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.copy),
onPressed: () {
print("--------------------------------------------");
print(widget.instruction);
print("--------------------------------------------");
},
),
),
body: Row(
children: [
Expanded(
child: Align(
alignment: Alignment.center,
child: i.isNotEmpty ? AnimaticsIW(i) : const Text('MAKING')),
),
],
),
);
}
}
class KenBurnIWC extends StatefulWidget {
const KenBurnIWC(this.aspectRatio, {Key? key}) : super(key: key);
final double aspectRatio;
@override
_KenBurnIWCState createState() => _KenBurnIWCState();
}
class _KenBurnIWCState extends State<KenBurnIWC> {
void _go() {
instructionList['il'] = il;
Navigator.push(
context,
MaterialPageRoute(builder: (context) => CreateViewer(jsonEncode(instructionList))),
);
}
dynamic il = [];
Map instructionList = {'meta': {'aspectRatio': 0.0, 'audioUrl': ''}, 'il': []};
Map currentInstruction = {};
String _state = '';
String imageURL = '';
int duration = 0;
double tx = 0.25;
double y0 = 0.75;
double x1 = 0.25;
double x2 = 0.75;
String controller = '';
@override
void initState() {
super.initState();
instructionList['meta']['aspectRatio'] = widget.aspectRatio;
}
Future<void> _setImage() async {
setState(() {
_state = 'setImage';
});
await showDialog(
context: context,
builder: (context) {
return Dialog(
child: Column(
children: [
const Text('Image Direct Url'),
TextField(
keyboardType: TextInputType.url,
onChanged: (value) {
setState(() {
imageURL = value;
});
},
),
TextButton(
onPressed: () {
_getCoordinates();
Navigator.pop(context, imageURL.toString());
},
child: const Text('OK'),
),
],
),
);
},
);
}
void _setImageOK() async {
await showDialog(
context: context,
builder: (context) {
return Dialog(
child: Column(
children: [
const Text('Set Duration'),
TextField(
keyboardType: TextInputType.number,
onChanged: (value) {
setState(() {
duration = int.parse(value);
});
},
),
TextButton(
onPressed: () {
List _clipArea = _getCoordinates();
currentInstruction = {
'instruction': 'setImage',
'imageUrl': imageURL,
'clipArea': _clipArea,
'duration': duration
};
il.add(currentInstruction);
Navigator.pop(context, imageURL.toString());
setState(() {
_state = '';
});
},
child: const Text('OK'),
),
],
),
);
},
);
}
void _clipArea() async {
await showDialog(
context: context,
builder: (context) {
return Dialog(
child: Column(
children: [
const Text('Duration'),
TextField(
keyboardType: TextInputType.number,
onChanged: (value) {
setState(() {
duration = int.parse(value);
});
},
),
TextButton(
onPressed: () {
List _clipArea = _getCoordinates();
currentInstruction = {
'instruction': 'clip',
'clipArea': _clipArea,
'duration': duration
};
il.add(currentInstruction);
Navigator.pop(context, imageURL.toString());
setState(() {
_state = '';
});
},
child: const Text('OK'),
),
],
),
);
},
);
}
final canvas = GlobalKey();
final clipArea = GlobalKey();
List _getCoordinates() {
RenderBox? _canvas = canvas.currentContext!.findRenderObject()! as RenderBox;
final _canvasBox = _canvas.localToGlobal(Offset.zero);
double _canvasBoxPY = _canvasBox.dy;
double _canvasBoxPX = _canvasBox.dx;
double _canvasBoxH = _canvas.size.height;
double _canvasBoxW = _canvas.size.width;
RenderBox? _clipArea = clipArea.currentContext!.findRenderObject()! as RenderBox;
final _clipAreaBox = _clipArea.localToGlobal(Offset.zero);
double _clipAreaPY = _clipAreaBox.dy;
double _clipAreaPX = _clipAreaBox.dx;
double _clipAreaH = _clipArea.size.height;
double _cliaAreaW = _clipArea.size.width;
double _y0 = (_clipAreaPY + _clipAreaH - _canvasBoxPY) / _canvasBoxH;
double _x1 = (_clipAreaPX - _canvasBoxPX) / _canvasBoxW;
double _x2 = (_clipAreaPX + _cliaAreaW - _canvasBoxPX) / _canvasBoxW;
return [_y0, _x1, _x2];
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: [
const Text('SPACE'),
Row(
children: [
const Text('SPACE'),
Container(
child: _state == ''
? Column(
children: [
TextButton(
onPressed: () {
_setImage();
},
child: const Text('setImage'),
),
TextButton(
onPressed: () {
_clipArea();
},
child: const Text('clip'),
),
TextButton(
onPressed: () {
_go();
},
child: const Text('go'),
)
],
)
: _state == 'setImage'
? TextButton(
onPressed: () {
_setImageOK();
},
child: const Text('OK'),
)
: null),
],
),
Expanded(
child: Align(
alignment: Alignment.center,
child: AspectRatio(
key: canvas,
aspectRatio: widget.aspectRatio,
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Container(
color: Colors.black,
child: Stack(
alignment: Alignment.center,
children: [
Container(
color: Colors.black,
child: imageURL != ''
? SizedBox(
width: constraints.maxWidth,
height: constraints.maxHeight,
child: Image.network(imageURL, fit: BoxFit.contain,),
)
: Container()
),
Positioned(
top: tx * constraints.maxHeight,
left: x1 * constraints.maxWidth,
child: Container(
color: Colors.white.withOpacity(0.5),
width: (x2 - x1) * constraints.maxWidth,
height: (y0 - tx) * constraints.maxHeight,
child: Align(
alignment: Alignment.center,
child: AspectRatio(
aspectRatio: widget.aspectRatio,
child: Container(
key: clipArea,
color: Colors.green.withOpacity(0.5),
),
),
),
),
),
Positioned(
top: tx * constraints.maxHeight - 10,
left: x1 * constraints.maxWidth - 10,
child: Container(
color: Colors.red,
width: 20,
height: 20,
),
),
Positioned(
top: y0 * constraints.maxHeight - 10,
left: x2 * constraints.maxWidth - 10,
child: Container(
color: Colors.blue,
width: 20,
height: 20,
),
),
Listener(
onPointerDown: (PointerEvent details) {
if((tx * constraints.maxHeight + 10) > details.localPosition.dy
&& (tx * constraints.maxHeight - 10) < details.localPosition.dy
&& (x1 * constraints.maxWidth + 10) > details.localPosition.dx
&& (x1 * constraints.maxWidth - 10) < details.localPosition.dx
) {
controller = 'red';
}
if((y0 * constraints.maxHeight + 10) > details.localPosition.dy
&& (y0 * constraints.maxHeight - 10) < details.localPosition.dy
&& (x2 * constraints.maxWidth + 10) > details.localPosition.dx
&& (x2 * constraints.maxWidth - 10) < details.localPosition.dx
) {
controller = 'blue';
}
},
onPointerMove: (PointerEvent details) {
final double dy;
final double dx;
if(details.localPosition.dy > constraints.maxHeight) {
dy = constraints.maxHeight;
} else if(details.localPosition.dy < 0) {
dy = 0;
} else {
dy = details.localPosition.dy;
}
if(details.localPosition.dx > constraints.maxWidth) {
dx = constraints.maxWidth;
} else if(details.localPosition.dx < 0) {
dx = 0;
} else {
dx = details.localPosition.dx;
}
if(controller == 'red'
&& (x1 * constraints.maxWidth + 10) <= x2 * constraints.maxWidth) {
setState(() {
tx = dy / constraints.maxHeight;
x1 = dx / constraints.maxWidth;
});
}
if(controller == 'blue') {
setState(() {
y0 = dy / constraints.maxHeight;
x2 = dx / constraints.maxWidth;
});
}
},
onPointerUp: (PointerEvent details) {
controller = '';
},
child: Container(
color: Colors.transparent,
),
),
],
),
);
}
),
),
),
),
Text('tx: $tx'),
Text('y0: $y0'),
Text('x1: $x1'),
Text('x2: $x2'),
],
),
);
}
}
class AnimaticsIW extends StatefulWidget {
const AnimaticsIW(this.instruction, {Key? key}) : super(key: key);
final Map instruction;
@override
_AnimaticsIWState createState() => _AnimaticsIWState();
}
class _AnimaticsIWState extends State<AnimaticsIW> with TickerProviderStateMixin {
late Map i;
late AnimationController controller;
String imageURL = '';
//old clip position
double y0old = 1.0;
double x1old = 0.0;
double x2old = 1.0;
double y0 = 1.0;
double x1 = 0.0;
double x2 = 1.0;
double opacity = 0.0;
//background image
String bimageURL = '';
double by0 = 1.0;
double bx1 = 1.0;
double bx2 = 1.0;
@override
void initState() {
super.initState();
i = widget.instruction;
debugPrint(i.toString());
_kenburnDO();
}
void _kenburnDO() async {
debugPrint('doKenBurn');
await Future.forEach(i['il'], (dynamic instruction) async {
switch (instruction['instruction']) {
case 'setImage':
debugPrint('setImage');
setState(() {
bimageURL = instruction['imageUrl'].toString();
by0 = instruction['clipArea'][0];
bx1 = instruction['clipArea'][1];
bx2 = instruction['clipArea'][2];
});
controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: instruction['duration']),
);
controller.addListener(() {
setState(() {
opacity = 1 - controller.value;
});
});
await controller.forward();
setState(() {
imageURL = bimageURL;
bimageURL = '';
opacity = 1.0;
y0old = by0;
x1old = bx1;
x2old = bx2;
y0 = by0;
x1 = bx1;
x2 = bx2;
});
break;
case 'clip':
debugPrint('clip');
controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: instruction['duration']),
);
controller.addListener(() {
setState(() {
y0 = (1 - controller.value) * y0old + controller.value * instruction['clipArea'][0];
x1 = (1 - controller.value) * x1old + controller.value * instruction['clipArea'][1];
x2 = (1 - controller.value) * x2old + controller.value * instruction['clipArea'][2];
});
});
await controller.forward();
y0old = y0;
x1old = x1;
x2old = x2;
break;
}
});
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: i['meta']['aspectRatio'].toDouble(),
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Container(
color: Colors.black,
child: ClipRect(
child: Stack(
alignment: Alignment.center,
children: [
//background
Opacity(
opacity: 1 - opacity,
child: Transform.translate(
// width height
offset: Offset(
constraints.maxWidth/(bx2 - bx1)/2 - constraints.maxWidth/2 - constraints.maxWidth/(bx2 - bx1) * bx1,
constraints.maxHeight/(bx2 - bx1)/2 - constraints.maxHeight/2 + constraints.maxHeight - constraints.maxHeight/(bx2 - bx1) * by0,
),
child: Transform.scale(
scale: 1 / (bx2 - bx1),
child: SizedBox(
width: constraints.maxWidth,
height: constraints.maxHeight,
child: bimageURL != ''
? Image.network(bimageURL, fit: BoxFit.contain,)
: Container()
),
),
),
),
//foreground
Opacity(
opacity: opacity,
child: Transform.translate(
// width height
offset: Offset(
constraints.maxWidth/(x2 - x1)/2 - constraints.maxWidth/2 - constraints.maxWidth/(x2 -x1) * x1,
constraints.maxHeight/(x2 - x1)/2 - constraints.maxHeight/2 + constraints.maxHeight - constraints.maxHeight/(x2 -x1) * y0,
),
child: Transform.scale(
scale: 1 / (x2 - x1),
child: SizedBox(
width: constraints.maxWidth,
height: constraints.maxHeight,
child: imageURL != ''
? Image.network(imageURL, fit: BoxFit.contain,)
: Container(color: Colors.black,),
),
),
),
),
],
),
),
);
},
)
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment