I'm looking to animate the scale of a container using the transform property of an AnimatedContainer; however, the scale is not being transitioned and jumps directly from start to end.
Code Snippet:
var container = new AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: 50.0,
height: 50.0,
// selected is a bool that will be toggled
transform: selected ? new Matrix4.identity() : new Matrix4.identity().scaled(0.2,0.2),
decoration: new BoxDecoration(
shape: BoxShape.circle,
backgroundColor: Colors.blue[500],
),
child: new Center(
child: new Icon(
Icons.check,
color: Colors.white,
),
)
);
Any insight on what's going on?
AnimatedContainer supports animting it's transform value, as follow:
/// scale to 95%, centerred
final width = 200.0;
final height = 300.0;
bool shouldScaleDown = true;// change value when needed
AnimatedContainer(
color: Colors.blueAccent,
width: width,
height: height,
duration: Duration(milliseconds: 100),
transform: (shouldScaleDown
? (Matrix4.identity()
..translate(0.025 * width, 0.025 * height)// translate towards right and down
..scale(0.95, 0.95))// scale with to 95% anchorred at topleft of the AnimatedContainer
: Matrix4.identity()),
child: Container(),
);
I'm afraid transform is one of the properties we don't animate (child is another). If you want to animate the scale, you can use ScaleTransition.
ScaleTransition: https://docs.flutter.io/flutter/widgets/ScaleTransition-class.html
Bug for Matrix lerp: https://github.com/flutter/flutter/issues/443
you can Animate using the Animated Builder,the below code will scale a Text from font Size 20-35 in 4 secs let me break this into steps to make you better understand
1.you need to implement your class from TickerProviderStateMixin.
2.You need an AnimationController and a Animation variables;
3.wrap your widget inside an AnimatedBuilder (note AnimatedBuilder must return a Widget at least a container) and add a controller to the animation as
animation: _controller,
and builder Which returns the AnimatedWidget
4.In the init method intialize the controller with vsync and the Duration of Animation.
and the animation with the Tweenit takes begin and end values which define the initial and final values of your Widget to be animated
for me, in this case, it was text widget so the begin and end Values will be corresponding to the fontSize.which receives variable values as animation.value
5.Finally How do you want the animation effect To be will be specified by the animate which takes in controller and the Curve effect
Here is a Working example
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<SplashScreen>
with TickerProviderStateMixin {
AnimationController _controller;
Animation _animation;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFF005CA9),
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
return Container(
child: Text(
'Hello World',
style: TextStyle(
color: Colors.white,
fontSize: _animation.value,
),
),
);
},
),
));
}
void initState() {
super.initState();
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 4));
_animation = Tween<double>(
begin: 20,
end: 35,
).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.ease,
),
);
_controller.forward();
}
}
the code above produces the following output
Related
I'm looking for a solution where i can hide a widget and only when some button is pressed the visual effect should be the container dropping from top to bottom.
Example: by pressing the action button a container is shown animated from top to bottom. This container is above everything.
Currently my Widget Build structure is the following:
Container
Stack
TheBlueContainerHere()
Flex
Flexible
ListView Builder
I'm not sure if this is the right structure to proceed, but i have not yet figure out a better one.
How can i build something like this?
Solved.
By following this example flutter notify from top of the screen i was able to create what i wanted.
When the action button is pressed:
Navigator.push(
context,
PageRouteBuilder(
opaque: false,
pageBuilder: (BuildContext context, _, __) {
return FunkyNotification();
},
),
);
And the respective code of FunkyNotification() is:
class FunkyNotification extends StatefulWidget {
#override
State<StatefulWidget> createState() => FunkyNotificationState();
}
class FunkyNotificationState extends State<FunkyNotification>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<Offset> position;
#override
void initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 750));
position = Tween<Offset>(begin: Offset(0.0, -4.0), end: Offset.zero)
.animate(CurvedAnimation(parent: controller, curve: Curves.decelerate));
controller.forward();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Material(
color: Colors.transparent,
child: Align(
alignment: Alignment.topCenter,
child: Padding(
padding: EdgeInsets.only(top: 56.0),
child: SlideTransition(
position: position,
child: Container(
height: 200,
width: double.infinity,
margin: EdgeInsets.only(left: 10, right: 10),
padding: EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(5),
bottomRight: Radius.circular(5),
),
),
),
),
),
),
),
);
}
}
I'm creating a text field like Text or RichText. And after that, I want to zoom in/out the size of text using pinching. For now, I tried implementing GestureDetector but it zooms in/out with one finger too. And it is really hard to aim pinching detection. Sometimes is freezing. I added a video that shows when after pinching it freezes and suddenly get bigger. The second video is with the case that image zoom in only when I tap on the text with one finger and move to up left corner. The ideal implementation is to detect pinch and zoom in/out all text area. And disable zooming when I use only one finger. Could you send me some hints, link or code how to solve or where to find the solution?
body: GestureDetector(
onScaleUpdate: (details) {
setState(() {
_textSize =
_initTextSize + (_initTextSize * (details.scale * .35));
});
},
onScaleEnd: (ScaleEndDetails details) {
setState(() {
_initTextSize = _textSize;
});
},
child: Center(
child: SizedBox(
height: _textSize,
child: FittedBox(
child: Text("Test"),
),
))),
In Stateful widget with these configuration
double _scaleFactor = 1.0;
double _baseScaleFactor = 1.0;
And use setState only on update, using the scaleFactor on textScaleFactor property of RichText.
Only one setState to rebuild widget and store the initial factor when scale starts
GestureDetector(
onScaleStart: (details) {
_baseScaleFactor = _scaleFactor;
},
onScaleUpdate: (details) {
setState(() {
_scaleFactor = _baseScaleFactor * details.scale;
});
},
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
color: Colors.red,
child: Center(
child: Text(
'Test',
textScaleFactor: _scaleFactor,
),
),
),
);
The height and width I put just to expand and simulate area of gesture detector.
Google software engineers Gary Qian and Chris Yang demonstrated this in their Google Developer Days talk. The video is viewable here:
Text in Flutter: Building a fancy chat bubble at GDD China
There code is similar to some of the other answers here, but they notably add a clamp so that it doesn't get too big or small.
Here is a summary of their scalable text bubble:
Because scaling still gets called even for a single finger touch, I added a check for scaleUpdateDetails.scale == 1.0. That means the UI won't be updated if there was no change in scale.
class Bubble extends StatefulWidget {
#override
_BubbleState createState() => _BubbleState();
}
class _BubbleState extends State<Bubble> {
double _fontSize = 20;
final double _baseFontSize = 20;
double _fontScale = 1;
double _baseFontScale = 1;
#override
Widget build(BuildContext context) {
return GestureDetector(
onScaleStart: (ScaleStartDetails scaleStartDetails) {
_baseFontScale = _fontScale;
},
onScaleUpdate: (ScaleUpdateDetails scaleUpdateDetails) {
// don't update the UI if the scale didn't change
if (scaleUpdateDetails.scale == 1.0) {
return;
}
setState(() {
_fontScale = (_baseFontScale * scaleUpdateDetails.scale).clamp(0.5, 5.0);
_fontSize = _fontScale * _baseFontSize;
});
},
child: ...
// descendant with a Text widget that uses the _fontSize
);
}
}
Notes:
Use a StatefulWidget so that you can store the current font size and scale at all times
Use two additional variables to remember the original font size and also the scale at the start of the pinch
Wrap the Text widget in a GestureDetector
Save the original scale in onScaleStart
Calculate the new font size onScaleUpdate
Use setState to rebuild the widget with the new size
Solution: Two finger zoom-in and zoom-out.
import 'package:flutter/material.dart';
import 'package:matrix_gesture_detector/matrix_gesture_detector.dart';
class TransformText extends StatefulWidget {
TransformText({Key key}) : super(key: key); // changed
#override
_TransformTextState createState() => _TransformTextState();
}
class _TransformTextState extends State<TransformText> {
double scale = 0.0;
#override
Widget build(BuildContext context) {
final ValueNotifier<Matrix4> notifier = ValueNotifier(Matrix4.identity());
return Scaffold(
appBar: AppBar(
title: Text('Single finger Rotate text'), // changed
),
body: Center(
child: MatrixGestureDetector(
onMatrixUpdate: (m, tm, sm, rm) {
notifier.value = m;
},
child: AnimatedBuilder(
animation: notifier,
builder: (ctx, child) {
return Transform(
transform: notifier.value,
child: Center(
child: Stack(
children: <Widget>[
Container(
color: Colors.red,
padding: EdgeInsets.all(10),
margin: EdgeInsets.only(top: 50),
child: Transform.scale(
scale:
1, // make this dynamic to change the scaling as in the basic demo
origin: Offset(0.0, 0.0),
child: Container(
height: 100,
child: Text(
"Two finger to zoom!!",
style:
TextStyle(fontSize: 26, color: Colors.white),
),
),
),
),
],
),
),
);
},
),
),
),
);
}
}
Full code. Hope it helps.
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final appTitle = 'Demo';
return MaterialApp(
title: appTitle,
home: MyHomePage(title: appTitle),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
MyHomePage({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: TransformText());
}
}
class TransformText extends StatefulWidget {
TransformText({Key key}) : super(key: key); // changed
#override
_TransformTextState createState() => _TransformTextState();
}
class _TransformTextState extends State<TransformText> {
double scale = 0.0;
double _scaleFactor = 1.0;
double _baseScaleFactor = 1.0;
double _savedVal = 1.0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GestureDetector Test'), // changed
),
body: Column(
children: <Widget>[
RaisedButton(
child: Text('get'),
onPressed: () {
_savedVal = _scaleFactor;
}),
RaisedButton(
child: Text('set'),
onPressed: () {
setState(() {
_scaleFactor = _savedVal;
});
}),
Expanded(
child: Center(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onScaleStart: (details) {
_baseScaleFactor = _scaleFactor;
},
onScaleUpdate: (details) {
setState(() {
_scaleFactor = _baseScaleFactor * details.scale;
});
},
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Center(
child: Text(
'Test',
textScaleFactor: _scaleFactor,
),
),
),
)),
),
],
),
);
}
}
Is there any way to display skew borders at the top and bottom?
I came up with the solution below by using two images (top_layout and bottom_layout.png). Is there any other way to make those color bars with shadows without using static images?
return Container(
color: const Color.fromARGB(255, 236, 0, 140),
child: Container(
padding: const EdgeInsets.all(8.0),
child: Container(
color: Colors.white,
margin:
EdgeInsets.only(top: 60.0, bottom: 20.0, left: 15.0, right: 15.0),
child: Stack(
children: <Widget>[
Positioned.fill(
child: Image.asset(
"assets/imgs/top_layout.png",
fit: BoxFit.fitWidth,
alignment: Alignment.topCenter,
),
),
Positioned.fill(
child: Image.asset(
"assets/imgs/xbottom_layout.png",
fit: BoxFit.fitWidth,
alignment: Alignment.bottomLeft,
),
),
],
),
),
),
);
}
How do draw lines in Flutter using the CustomPaint widget
To paint in Flutter you use the CustomPaint widget. The CustomPaint widget takes a CustomPainter object as a parameter. In that class you have to override the paint method, which gives you a canvas that you can paint on. Here is the code to draw the line in the image above.
#override
void paint(Canvas canvas, Size size) {
final p1 = Offset(50, 50);
final p2 = Offset(250, 150);
final paint = Paint()
..color = Colors.black
..strokeWidth = 4;
canvas.drawLine(p1, p2, paint);
}
Notes:
The drawLine method draws a line connecting the two points you give it.
An Offset is a pair of (dx, dy) doubles, offset from the top left corner of the CustomPaint widget.
Another option
You could do something similar with the drawPoints method using the PointMode.polygon option.
#override
void paint(Canvas canvas, Size size) {
final pointMode = ui.PointMode.polygon;
final points = [
Offset(50, 100),
Offset(150, 75),
Offset(250, 250),
Offset(130, 200),
Offset(270, 100),
];
final paint = Paint()
..color = Colors.black
..strokeWidth = 4
..strokeCap = StrokeCap.round;
canvas.drawPoints(pointMode, points, paint);
}
Context
Here is the main.dart code so that you can see it in context.
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: HomeWidget(),
),
);
}
}
class HomeWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: CustomPaint( // <-- CustomPaint widget
size: Size(300, 300),
painter: MyPainter(),
),
);
}
}
class MyPainter extends CustomPainter { // <-- CustomPainter class
#override
void paint(Canvas canvas, Size size) {
// <-- Insert your painting code here.
}
#override
bool shouldRepaint(CustomPainter old) {
return false;
}
}
See also
See this article for my fuller answer.
In this case, you would like to use Custom Painter widget instead. You can draw the shape based on coordinates.
Refer this tutorial for more info.
Drawing Custom Shapes in Flutter using CustomPainter
What I needed:
I want to scroll a list by some index, how can i do that.
What I know:
scrollToIndex should start from n index, but how can we scroll to any index?
Unfortunately, ListView has no built-in approach to a scrollToIndex() function. You’ll have to develop your own way to measure to that element’s offset for animateTo() or jumpTo(), or you can search through these suggested solutions/plugins or from other posts like Flutter: Scrolling to a widget in ListView
(the general scrollToIndex issue is discussed at flutter/issues/12319 since 2017, but still with no current plans)
But there is a different kind of ListView that does support scrollToIndex (as mentioned by Slashbin):
ScrollablePositionedList
dependency: scrollable_positioned_list
You set it up exactly like ListView and works the same, except you now have access to a ItemScrollController that does:
jumpTo({index, alignment})
scrollTo({index, alignment, duration, curve})
Simplified example:
ItemScrollController _scrollController = ItemScrollController();
ScrollablePositionedList.builder(
itemScrollController: _scrollController,
itemCount: _myList.length,
itemBuilder: (context, index) {
return _myList[index];
},
)
_scrollController.scrollTo(index: 150, duration: Duration(seconds: 1));
(note that this library is developed by Google but not by the core Flutter team.)
ScrollablePositionedList can be used for this.
https://github.com/google/flutter.widgets/tree/master/packages/scrollable_positioned_list
Pub link - https://pub.dev/packages/scrollable_positioned_list
final ItemScrollController itemScrollController = ItemScrollController();
final ItemPositionsListener itemPositionListener = ItemPositionsListener.create();
ScrollablePositionedList.builder(
itemCount: 500,
itemBuilder: (context, index) => Text('Item $index'),
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionListener,
);
One then can scroll to a particular item with:
itemScrollController.scrollTo(
index: 150,
duration: Duration(seconds: 2),
curve: Curves.easeInOutCubic);
Use scroll_to_index lib, here scroll will be always performed to sixth position as its hardcoded below
dependencies:
scroll_to_index: ^1.0.6
Code Snippet:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final scrollDirection = Axis.vertical;
AutoScrollController controller;
List<List<int>> randomList;
#override
void initState() {
super.initState();
controller = AutoScrollController(
viewportBoundaryGetter: () =>
Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom),
axis: scrollDirection);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView(
scrollDirection: scrollDirection,
controller: controller,
children: <Widget>[
...List.generate(20, (index) {
return AutoScrollTag(
key: ValueKey(index),
controller: controller,
index: index,
child: Container(
height: 100,
color: Colors.red,
margin: EdgeInsets.all(10),
child: Center(child: Text('index: $index')),
),
highlightColor: Colors.black.withOpacity(0.1),
);
}),
],
),
floatingActionButton: FloatingActionButton(
onPressed: _scrollToIndex,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
// Scroll listview to the sixth item of list, scrollling is dependent on this number
Future _scrollToIndex() async {
await controller.scrollToIndex(6, preferPosition: AutoScrollPosition.begin);
}
}
Output:
It's simple to scroll to preferred index if you know listview item size like:
var index = 15;
var widthOfItem =
176; //in dp. width needed for horizontallistView;
var heightOfItem =
200; //in dp. height need for vertical listView;
listViewScrollController.jumpTo(index * widthOfItem.toDouble());
or
listViewScrollController.animateTo(
index * widthOfItem.toDouble(),
duration: Duration(milliseconds: 500),
curve: Curves.ease);
Found a gist and its working gist url is https://gist.github.com/debuggerx01/b8ef756ee02b3eb82ec3702f14ba94e6
this gist is using a package which is calculation rect size.
https://pub.dartlang.org/packages/rect_getter
You can use the flutter_scrollview_observer lib to implement your desired functionality without invasivity
Create and use instance of ScrollController normally.
ScrollController scrollController = ScrollController();
ListView _buildListView() {
return ListView.separated(
controller: scrollController,
...
);
}
Create an instance of ListObserverController pass it to ListViewObserver
ListObserverController observerController = ListObserverController(controller: scrollController);
ListViewObserver(
controller: observerController,
child: _buildListView(),
...
)
Now you can scroll to the specified index position
// Jump to the specified index position without animation.
observerController.jumpTo(index: 1)
// Jump to the specified index position with animation.
observerController.animateTo(
index: 1,
duration: const Duration(milliseconds: 250),
curve: Curves.ease,
);
I'm creating a dashboard which contain Tween animation for two widgets, Text and two Container. But, I want to make the two Container's opacity changing slowly from invisible to visible...so I used AnimatedOpacity. But I don't know how to do it...
Any help would be appreciated..
class _IntroState extends State<Intro> with SingleTickerProviderStateMixin {
Animation animation;
AnimationController animationController;
#override
void initState() {
super.initState();
animationController = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
animation = Tween(begin: -1.0, end: 0.0).animate(CurvedAnimation(
parent: animationController, curve: Curves.fastOutSlowIn));
animationController.forward();
}
#override
Widget build(BuildContext context) {
bool _visible = false;
final double width = MediaQuery.of(context).size.width;
return AnimatedBuilder(
animation: animationController,
builder: (BuildContext context, Widget child) {
return Scaffold(
//BODDY
body: ListView(
hildren:<Widget>[
new Stack(
children: <Widget>[
new Transform(
//ANIMATED OPACITY
new AnimatedOpacity(
opacity: _visible ? 0.0 : 1.0,
duration: Duration(milliseconds: 500),
child: new Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12.0),
child: new Row(
children: <Widget>[
Expanded(
child: Row(
children: <Widget>[
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Container(
child: Column(
children: <Widget>[
//THIS THE CONTAINER
new Container(. . .),
new Container(. . .)
Instead of AnimatedOpacity, use a FadeTransition widget. This gives you manual control over the animation:
#override
Widget build(BuildContext context) {
return FadeTransition(
opacity: animationController.drive(CurveTween(curve: Curves.easeOut)),
child: ...,
);
}
To make a StatelessWidget or StatefulWidget fade in automatically on creation, TweenAnimationBuilder provides an even easier solution:
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
curve: Curves.ease,
duration: const Duration(seconds: 1),
builder: (BuildContext context, double opacity, Widget? child) {
return Opacity(
opacity: opacity,
child: Container(width: 20, height: 20, color: Colors.red)
);
});
}
}
See my Codepen for a complete example: https://codepen.io/atok/pen/BaZVRPr
Best regards
For anyone who'd like to fade a widget automatically as soon as the page is rendered and still wants to use AnimatedOpacity, you can put the call to change the state of the opacity in the WidgetsBinding's addPostFrameCallback callback.
Put this code below in your initState.
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_opacity = 1;
});
});
I totally recommend using #boformer 's answer above.
But, I played around with your code and wanted to show you how you can call setState to trigger the AnimatedOpacity, so you can see that it is working without onTap or GestureDetector as you were thinking in the comments above.
I got your code and played around with it. What I did is, simply added a status listener to your animation controller and when the controller is done. I triggered the visibility boolean in setState. Then it will change the visibility of the containers.
// When animation finished change the visibility.
animationController.addStatusListener((status){
if (status == AnimationStatus.completed) {
setState(() {
// This is opposite, because it's implemented opposite in your code.
_visible = false;
});
}
});