Views
What is the equivalent of a Page or Element in Flutter?
ContentPage
, TabbedPage
, MasterDetailPage
are all types of pages you might in a Xamarin.Forms application. These pages would then hold Element
s to display the various controls. In Xamarin.Forms an Entry
or Button
are examples of an Element
.
In Flutter, almost everything is a widget. A Page
, called a Route
in Flutter, is a widget. Buttons, progress bars, and animation controllers are all widgets. When building a route, you create a widget tree.
Flutter includes the Material Components library. These are widgets that implement the Material Design guidelines. Material Design is a flexible design system optimized for all platforms, including iOS.
But Flutter is flexible and expressive enough to implement any design language. For example, on iOS, you can use the Cupertino widgets to produce an interface that looks like Apple’s iOS design language.
How do I update widgets?
In Xamarin.Forms, each Page
or Element
is a stateful class, that has properties and methods. You update your Element
by updating a property, and this is propagated down to the native control.
In Flutter, Widget
s are immutable and you can’t directly update them by changing a property, instead you have to work with the widget’s state.
This is where the concept of Stateful vs Stateless widgets comes from. A StatelessWidget
is just what it sounds like—a widget with no state information.
StatelessWidgets
are useful when the part of the user interface you are describing does not depend on anything other than the configuration information in the object.
For example, in Xamarin.Forms, this is similar to placing an Image
with your logo. The logo is not going to change during runtime, so use a StatelessWidget
in Flutter.
If you want to dynamically change the UI based on data received after making an HTTP call or user interaction then you have to work with StatefulWidget
and tell the Flutter framework that the widget’s State
has been updated so it can update that widget.
The important thing to note here is at the core both stateless and stateful widgets behave the same. They rebuild every frame, the difference is the StatefulWidget
has a State
object that stores state data across frames and restores it.
If you are in doubt, then always remember this rule: if a widget changes (because of user interactions, for example) it’s stateful. However, if a widget reacts to change, the containing parent widget can still be stateless if it doesn’t itself react to change.
The following example shows how to use a StatelessWidget
. A common StatelessWidget
is the Text
widget. If you look at the implementation of the Text
widget you’ll find it subclasses StatelessWidget
.
new Text(
'I like Flutter!',
style: new TextStyle(fontWeight: FontWeight.bold),
);
As you can see, the Text
widget has no state information associated with it, it renders what is passed in its constructors and nothing more.
But, what if you want to make “I Like Flutter” change dynamically, for example when clicking a FloatingActionButton
?
To achieve this, wrap the Text
widget in a StatefulWidget
and update it when the user clicks the button, as shown in the following example:
import 'package:flutter/material.dart';
void main() {
runApp(new SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Sample App',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => new _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
// Default placeholder text
String textToShow = "I Like Flutter";
void _updateText() {
setState(() {
// Update the text
textToShow = "Flutter is Awesome!";
});
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Sample App"),
),
body: new Center(child: new Text(textToShow)),
floatingActionButton: new FloatingActionButton(
onPressed: _updateText,
tooltip: 'Update Text',
child: new Icon(Icons.update),
),
);
}
}
How do I lay out my widgets? What is the equivalent of an XAML file?
In Xamarin.Forms, most developers write layouts in XAML, though sometimes in C#. In Flutter, you write your layouts with a widget tree in code.
The following example shows how to display a simple widget with padding:
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Sample App"),
),
body: new Center(
child: new MaterialButton(
onPressed: () {},
child: new Text('Hello'),
padding: new EdgeInsets.only(left: 10.0, right: 10.0),
),
),
);
}
You can view the layouts that Flutter has to offer in the widget catalog.
How do I add or remove an Element from my layout?
In Xamarin.Forms, you had to remove or add an Element
in code. This involved either setting the Content
property or calling Add()
or Remove()
if it was a list.
In Flutter, because widgets are immutable there is no direct equivalent. Instead, you can pass a function to the parent that returns a widget, and control that child’s creation with a boolean flag.
The following example shows how to toggle between two widgets when the user clicks the FloatingActionButton
:
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Sample App',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => new _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
// Default value for toggle
bool toggle = true;
void _toggle() {
setState(() {
toggle = !toggle;
});
}
_getToggleChild() {
if (toggle) {
return new Text('Toggle One');
} else {
return new CupertinoButton(
onPressed: () {},
child: new Text('Toggle Two'),
);
}
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Sample App"),
),
body: new Center(
child: _getToggleChild(),
),
floatingActionButton: new FloatingActionButton(
onPressed: _toggle,
tooltip: 'Update Text',
child: new Icon(Icons.update),
),
);
}
}
How do I animate a widget?
In Xamarin.Forms, you create simple animations using ViewExtensions that include methods such as FadeTo
and TranslateTo
. You would use these methods on a view to perform the required animations.
<Image Source="{Binding MyImage}" x:Name="myImage" />
Then in code behind, or a behavior, this would fade in the image, over a 1 second period.
myImage.FadeTo(0, 1000);
In Flutter, you animate widgets using the animation library by wrapping widgets inside an animated widget. Use an AnimationController
, which is an Animation<double>
that can pause, seek, stop and reverse the animation. It requires a Ticker
that signals when vsync happens, and produces a linear interpolation between 0 and 1 on each frame while it’s running. You then create one or more Animation
s and attach them to the controller.
For example, you might use CurvedAnimation
to implement an animation along an interpolated curve. In this sense, the controller is the “master” source of the animation progress and the CurvedAnimation
computes the curve that replaces the controller’s default linear motion. Like widgets, animations in Flutter work with composition.
When building the widget tree, you assign the Animation
to an animated property of a widget, such as the opacity of a FadeTransition
, and tell the controller to start the animation.
The following example shows how to write a FadeTransition
that fades the widget into a logo when you press the FloatingActionButton
:
import 'package:flutter/material.dart';
void main() {
runApp(new FadeAppTest());
}
class FadeAppTest extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Fade Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyFadeTest(title: 'Fade Demo'),
);
}
}
class MyFadeTest extends StatefulWidget {
MyFadeTest({Key key, this.title}) : super(key: key);
final String title;
@override
_MyFadeTest createState() => new _MyFadeTest();
}
class _MyFadeTest extends State<MyFadeTest> with TickerProviderStateMixin {
AnimationController controller;
CurvedAnimation curve;
@override
void initState() {
controller = new AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
curve = new CurvedAnimation(parent: controller, curve: Curves.easeIn);
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Container(
child: new FadeTransition(
opacity: curve,
child: new FlutterLogo(
size: 100.0,
)))),
floatingActionButton: new FloatingActionButton(
tooltip: 'Fade',
child: new Icon(Icons.brush),
onPressed: () {
controller.forward();
},
),
);
}
}
For more information, see Animation & Motion widgets, the Animations tutorial, and the Animations overview.
How do I draw/paint on the screen?
Xamarin.Forms never had a built in way to draw directly on the screen. Many would use SkiaSharp, if they needed a custom image drawn. In Flutter, you have direct access to the Skia Canvas and can easily draw on screen.
Flutter has two classes that help you draw to the canvas: CustomPaint
and CustomPainter
, the latter of which implements your algorithm to draw to the canvas.
To learn how to implement a signature painter in Flutter, see Collin’s answer on StackOverflow.
import 'package:flutter/material.dart';
void main() => runApp(new MaterialApp(home: new DemoApp()));
class DemoApp extends StatelessWidget {
Widget build(BuildContext context) => new Scaffold(body: new Signature());
}
class Signature extends StatefulWidget {
SignatureState createState() => new SignatureState();
}
class SignatureState extends State<Signature> {
List<Offset> _points = <Offset>[];
Widget build(BuildContext context) {
return new GestureDetector(
onPanUpdate: (DragUpdateDetails details) {
setState(() {
RenderBox referenceBox = context.findRenderObject();
Offset localPosition =
referenceBox.globalToLocal(details.globalPosition);
_points = new List.from(_points)..add(localPosition);
});
},
onPanEnd: (DragEndDetails details) => _points.add(null),
child: new CustomPaint(painter: new SignaturePainter(_points), size: Size.infinite),
);
}
}
class SignaturePainter extends CustomPainter {
SignaturePainter(this.points);
final List<Offset> points;
void paint(Canvas canvas, Size size) {
var paint = new Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null)
canvas.drawLine(points[i], points[i + 1], paint);
}
}
bool shouldRepaint(SignaturePainter other) => other.points != points;
}
Where is the widget’s opacity?
On Xamarin.Forms, all VisualElement
s have an Opacity. In Flutter, you need to wrap a widget in an Opacity widget to accomplish this.
How do I build custom widgets?
In Xamarin.Forms, you typically subclass VisualElement
, or use a pre-existing VisualElement
, to override and implement methods that achieve the desired behavior.
In Flutter, build a custom widget by composing smaller widgets (instead of extending them). It is somewhat similar to implementing a custom control based off a Grid
with numerous VisualElement
s added in, while extending with custom logic.
For example, how do you build a CustomButton
that takes a label in the constructor? Create a CustomButton that composes a RaisedButton
with a label, rather than by extending RaisedButton
:
class CustomButton extends StatelessWidget {
final String label;
CustomButton(this.label);
@override
Widget build(BuildContext context) {
return new RaisedButton(onPressed: () {}, child: new Text(label));
}
}
Then use CustomButton
, just as you’d use any other Flutter widget:
@override
Widget build(BuildContext context) {
return new Center(
child: new CustomButton("Hello"),
);
}