I'd like to create a stateful widget that embed other widget(s) to apply some form of custom rendering operations (such as rotation for example)
So I'd like to use my widget from a parent widget as follows:
#override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: _buildView(),
floatingActionButton: StreamBuilder<bool>(
stream: FlutterBlue.instance.isScanning,
initialData: false,
builder: (c, snapshot) {
if (snapshot.data) {
return FloatingActionButton(
child: Stack(alignment: Alignment.center, children: [
RotatingWidget(
child: ArcText(
radius: 34,
text: 'Radar scanning… | Radar scanning…',
textStyle: TextStyle(fontSize: 12, color: Colors.white),
startAngle: -3 * math.pi / 4,
),
),
Icon(Icons.stop),
]),
onPressed: () => FlutterBlue.instance.stopScan(),
backgroundColor: Colors.red,
);
}
else
// Etc.
I came up with such a widget as follows, but I wonder how could I get the child to render it?
// Rotating button for the radar scanning
class RotatingWidget extends StatefulWidget {
final Widget child;
const RotatingWidget({Key key, #required this.child}) : super(key: key);
#override
_RotatingWidgetState createState() => _RotatingWidgetState();
}
class _RotatingWidgetState extends State<RotatingWidget>
with SingleTickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
super.initState();
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 2))
..repeat();
}
#override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: _controller,
builder: (_, child) {
return Transform.rotate(
angle: _controller.value * 2 * math.pi,
child: child,
);
},
// ===== HOW TO GET THE CHILD PASSED TO THE CURRENT WIDGET ? ====
child: this.state.child//FlutterLogo(size: 200),
),
);
}
}
Merits go to #pskink for his quick answer:
You access to the child by writing:
child: widget.child
instead of my wrong initial child: this.child
Related
I'm trying to get full control on routes using Navigator 2.0 api. I want some pages to have transparent background (e.g. dialogs and bottom sheets) but still be represented as pages in Navigator. Naive way - just add a MaterialPage with partially transparent widget - doesn't work, lower page becomes black after transition animation.
Minimal code for reproduction is below. I expect to see red square (UpperPage) on green background (RootPage) but background becomes black. Navigator 1.0 api, like showGeneralDialog, works fine with this case, but I don't want to mix declarative and imperative way since it's hard to control from single source of truth like bloc or Provider. Is there any way to achieve this behaviour with pages api only?
class RootPage extends StatelessWidget {
const RootPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return SizedBox.expand(
child: Container(color: Colors.green),
);
}
}
class UpperPage extends StatelessWidget {
const UpperPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
);
}
}
class AppRouterDelegate extends RouterDelegate<String> with ChangeNotifier {
#override
Widget build(BuildContext context) {
return Navigator(
pages: const [
MaterialPage(child: RootPage()),
MaterialPage(child: UpperPage()),
],
onPopPage: (route, result) {
return route.didPop(result);
},
);
}
...
}
import 'package:flutter/material.dart';
class TutorialOverlay extends ModalRoute<void> {
#override
Duration get transitionDuration => Duration(milliseconds: 500);
#override
bool get opaque => false;
#override
bool get barrierDismissible => false;
#override
Color get barrierColor => Colors.black.withOpacity(0.5);
#override
String get barrierLabel => null;
#override
bool get maintainState => true;
#override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
// This makes sure that text and other content follows the material style
return Material(
type: MaterialType.transparency,
// make sure that the overlay content is not cut off
child: SafeArea(
child: _buildOverlayContent(context),
),
);
}
Widget _buildOverlayContent(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'This is a nice overlay',
style: TextStyle(color: Colors.white, fontSize: 30.0),
),
RaisedButton(
onPressed: () => Navigator.pop(context),
child: Text('Dismiss'),
)
],
),
);
}
#override
Widget buildTransitions(
BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
// You can add your own animations for the overlay content
return FadeTransition(
opacity: animation,
child: ScaleTransition(
scale: animation,
child: child,
),
);
}
}
// Example application:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Playground',
home: TestPage(),
);
}
}
class TestPage extends StatelessWidget {
void _showOverlay(BuildContext context) {
Navigator.of(context).push(TutorialOverlay());
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Test')),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Center(
child: RaisedButton(
onPressed: () => _showOverlay(context),
child: Text('Show Overlay'),
),
),
),
);
}
}
I have a MainScreen and SecondScreen. When the drawer item in the MainScreen clicked. It should move to SecondScreen Container widget. But how to do that?
I have set ScrollController for SecondScreen SingleChildScrollView. but how to move to a certain widget?
Create a method in SecondScreen which scroll to the widget?
What if I have 3rd screen which need same functionality.
SecondScreen.dart
import 'package:flutter/material.dart';
ScrollController scrollController = ScrollController();
var containerKey = GlobalKey();
class SecondScreen extends StatefulWidget {
final Key widgetKey;
const SecondScreen({Key key, this.widgetKey}) : super(key: key);
#override
State<SecondScreen> createState() => _SecondScreenState();
}
class _SecondScreenState extends State<SecondScreen> {
#override
void initState() {
// TODO: implement initState
super.initState();
Scrollable.ensureVisible(
widget.widgetKey,
duration: const Duration(milliseconds: 400),
curve: Curves.easeInOut,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
controller: scrollController,
child: Column(
children: [
Text('hi'),
Container(
color: Colors.red,
height: 1000,
),
Container(
color: Colors.green,
height: 1000,
),
Container(
key: containerKey,
color: Colors.green,
height: 1000,
),
],
),
),
);
}
}
mainscreen.dart
import 'package:flutter/material.dart';
import 'package:stackoverflow_check/scrollcheck/second_screen.dart';
class MainScreen extends StatelessWidget {
const MainScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
drawer: Drawer(
child: ListView(
children: [
TextButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SecondScreen(widgetKey: containerKey),
),
);
//scrollController.an
},
child: Text('click'),
)
],
),
),
);
}
}
Main screen
class MainScreen extends StatelessWidget {
const MainScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
drawer: Drawer(
child: ListView(
children: [
TextButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SecondScreen(widgetNum: 2),
),
);
//scrollController.an
},
child: Text('click'),
)
],
),
),
);
}
}
SecondScreen
class SecondScreen extends StatefulWidget {
final int widgetNum;
const SecondScreen({Key? key, required this.widgetNum}) : super(key: key);
#override
State<SecondScreen> createState() => _SecondScreenState();
}
class _SecondScreenState extends State<SecondScreen> {
ScrollController scrollController = ScrollController();
var containerKey = GlobalKey();
var container2Key = GlobalKey();
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
void didChangeDependencies() {
// TODO: implement didChangeDependencies
super.didChangeDependencies();
Future.delayed(Duration(milliseconds: 100), () {
if (widget.widgetNum == 1) {
Scrollable.ensureVisible(
containerKey.currentContext!,
duration: const Duration(milliseconds: 400),
curve: Curves.easeInOut,
);
} else {
Scrollable.ensureVisible(
container2Key.currentContext!,
duration: const Duration(milliseconds: 400),
curve: Curves.easeInOut,
);
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
controller: scrollController,
child: Column(
children: [
Text('hi'),
Container(
color: Colors.red,
height: 1000,
),
Container(
color: Colors.green,
height: 1000,
),
Container(
key: containerKey,
color: Colors.green,
height: 1000,
),
Container(
key: container2Key,
color: Colors.blue,
height: 1000,
),
],
),
),
);
}
}
Description:
Hello, I am trying to animate the rotation of an object.
For this I use Matrix4 to control the point of rotation of my object.
I have a strange behavior during the animation transition.
Problem :
Why does my green square not maintain its rotation around its center during the animation?
The code :
class NodeV3View extends StatefulWidget {
const NodeV3View({
Key? key,
}) : super(key: key);
#override
State<NodeV3View> createState() => _NodeV3ViewState();
}
class _NodeV3ViewState extends State<NodeV3View> {
bool isExpand = false;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
var controller = Provider.of<CompteurProvider2>(context);
return Scaffold(
body: LayoutBuilder(
builder: (context, constraints){
return Consumer<CompteurProvider2>(builder :(ctx , provider , child){
return GestureDetector(
onTap: () => setState(() {}),
child: Container(
color: Colors.yellow,
width: 300,
height: 300,
child: Stack(
children: [
Positioned(
left: 150 - 50,// => Yellow Square / 2 - Green Square / 2
top : 150 - 50,
child: InkWell(
onTap: (){
setState(() {
isExpand = !isExpand;
});
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 500),
width: 100,
height: 100,
decoration: BoxDecoration(
color: Colors.green,
),
transform: Matrix4Transform()
.rotateDegrees(
isExpand == true
? 180
: 0,
origin: Offset(50, 50)
)
.matrix4,
),
),
)
],
),
)
);
});
},
)
);
}
}
Any guidance on the best way to accomplish this would be appreciated.
Add transformAlignment: FractionalOffset.center to the AnimatedContainer.
i hope this one may be help you
this code for only one time rotate the box, but you will add conditions for your desire things
import 'dart:math';
import 'package:flutter/material.dart';
class TransformExample extends StatefulWidget {
const TransformExample({Key? key}) : super(key: key);
#override
State<TransformExample> createState() => _TransformExampleState();
}
class _TransformExampleState extends State<TransformExample>
with TickerProviderStateMixin {
late AnimationController animationController =
AnimationController(vsync: this, duration: const Duration(seconds: 2));
#override
void dispose() {
super.dispose();
animationController.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Center(
child: InkWell(
onTap: () => setState(() {
animationController.forward();
}),
child: AnimatedBuilder(
animation: animationController,
child: Container(
height: 150.0,
width: 150.0,
color: Colors.blueGrey,
),
builder: (BuildContext context, Widget? child) {
return Transform.rotate(
angle: animationController.value * 2.0 * pi,
child: child,
);
}),
),
),
);
}
}
If you only want to rotate it along the z-axis, a much easier way is to use the RotationTransition widget, check out this existing answer here.
If you want to rotate along different axes, for example, making a 3D cube by rotating along x-axis and y-axis, you can check out this video tutorial here.
Is there any way to animate rotation of a widget? I tried RotatedBox but it didn't work.
Use AnimatedBuilder
AnimatedBuilder(
animation: _animation, // pass AnimationController to it
child: YourContainer(),
builder: (_, child) {
return Transform.rotate(
angle: _animation.value * play_around_with_values,
child: child,
);
},
)
Screenshot:
Full code:
class _MainPageState extends State<MainPage> with SingleTickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: Duration(seconds: 2))..repeat();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (_, child) {
return Transform.rotate(
angle: _controller.value * 2 * math.pi,
child: child,
);
},
child: FlutterLogo(size: 200),
),
),
);
}
}
I want to achieve something like below (animation style doesn't matter, I'm looking for the way to do this)
However, all resources and question only explain how to create
item addition or removal animations.
My current code (I use BLoC pattern)
class _MembersPageState extends State<MembersPage> {
#override
Widget build(BuildContext context) {
return BlocProvider<MembersPageBloc>(
create: (context) =>
MembersPageBloc(userRepository: UserRepository.instance)..add(MembersPageShowed()),
child: BlocBuilder<MembersPageBloc, MembersPageState>(
builder: (context, state) {
if (state is MembersPageSuccess) {
return ListView.builder(
itemCount: state.users.length,
itemBuilder: (context, index) {
User user = state.users[index];
return ListTile(
isThreeLine: true,
leading: Icon(Icons.person, size: 36),
title: Text(user.name),
subtitle: Text(user.username),
onTap: () => null,
);
},
);
} else
return Text("I don't care");
},
),
);
}
}
Widgets like AnimatedOpacity and AnimatedPositioned can be used to achieve this. However, lifecycle of the children widgets in a ListView is a bit complex. They get destroyed and recreated according to the scroll position. If the child widget has an animation that starts on initialization, it will reanimate whenever the child gets visible to the UI.
Here is my hacky solution. I used a static boolean to indicate whether it's the first time or recreation state and simply ignore the recration. You can copy and try it in Dartpad.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.brown,
body: ListView(
children: List.generate(
25,
(i) => AnimatedListItem(i, key: ValueKey<int>(i)),
),
),
),
);
}
}
class AnimatedListItem extends StatefulWidget {
final int index;
const AnimatedListItem(this.index, {Key? key}) : super(key: key);
#override
State<AnimatedListItem> createState() => _AnimatedListItemState();
}
class _AnimatedListItemState extends State<AnimatedListItem> {
bool _animate = false;
static bool _isStart = true;
#override
void initState() {
super.initState();
if (_isStart) {
Future.delayed(Duration(milliseconds: widget.index * 100), () {
setState(() {
_animate = true;
_isStart = false;
});
});
} else {
_animate = true;
}
}
#override
Widget build(BuildContext context) {
return AnimatedOpacity(
duration: const Duration(milliseconds: 1000),
opacity: _animate ? 1 : 0,
curve: Curves.easeInOutQuart,
child: AnimatedPadding(
duration: const Duration(milliseconds: 1000),
padding: _animate
? const EdgeInsets.all(4.0)
: const EdgeInsets.only(top: 10),
child: Container(
constraints: const BoxConstraints.expand(height: 100),
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
widget.index.toString(),
style: const TextStyle(fontSize: 24),
),
),
),
),
),
);
}
}
I created a gist on dartpad shows how to add initial animation for ListView
The key point is to create an AnimationController for each list item, cache it, and clean up when animation is complete.