返回介绍

Flutter for Xamarin.Forms developers

发布于 2019-12-09 21:31:21 字数 58249 浏览 1018 评论 0 收藏 0

This document is meant for Xamarin.Forms developers looking to apply their existing knowledge to build mobile apps with Flutter. If you understand the fundamentals of the Xamarin.Forms framework then you can use this document as a jump start to Flutter development.

Your Android and iOS knowledge and skill set are valuable when building with Flutter, because Flutter relies on the native operating system configurations, similar to how you would configure your native Xamarin.Forms projects. The Flutter Frameworks is also similar to how you create a single UI, that is used on multiple platforms.

This document can be used as a cookbook by jumping around and finding questions that are most relevant to your needs.

Project Setup

How does the app start?

For each platform in Xamarin.Forms, you call the LoadApplication method, which creates a new Application and starts your app.

LoadApplication(new App());

In Flutter, the default main entry point is main where you load your Flutter app.

void main() {
  runApp(new MyApp());
}

In Xamarin.Forms, you assign a Page to the MainPage property in the Application class.

public class App: Application
{
    public App()
    {
      MainPage = new ContentPage()
                 {
                   new Label()
                   {
                     Text="Hello World",
                     HorizontalOptions = LayoutOptions.Center,
                     VerticalOptions = LayoutOptions.Center
                   }
                 };
    }
}

In Flutter, “everything is a widget”, even the application itself. The following example shows MyApp, a simple application Widget.

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new Center(
        child: Text("Hello World!", textDirection: TextDirection.ltr));
  }
}

How do you create a Page?

Xamarin.Forms has many different types of pages; ContentPage is the most common.

In Flutter, you specify an application widget that holds your root page. You can use a MaterialApp widget, which supports Material Design, or you can use the lower level WidgetsApp, which you can customize in any way you want.

The following code defines the home page, a stateful widget. In Flutter, all widgets are immutable, but two types of widgets are supported: stateful and stateless. Examples of a stateless widget are titles, icons, or images.

The following example uses MaterialApp, which holds its root page in the home property.

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

From here, your actual first page is another Widget, in which you create your state.

A stateful widget, such as MyHomePage below, consists of two parts. The first part, which is itself immutable, creates a State object that holds the state of the object. The State object persists over the life of the widget.

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

The state object implements the build method for the stateful widget.

When the state of the widget tree changes, call setState(), which triggers a build of that portion of the UI. Make sure to call setState() only when necessary, and only on the part of the widget tree that has changed, or it can result in poor UI performance.

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: new Text(widget.title),
      ),
      body: new Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ),
    );
  }
}

The UI, also known as widget tree, in Flutter is immutable, meaning you can not change its state once it is built. You change fields in your State class, then call setState to rebuild the entire widget tree again.

This way of generating UI is different than Xamarin.Forms, but there are many benefits to this approach.

Views

What is the equivalent of a Page or Element in Flutter?

A ContentPage, TabbedPage, MasterDetailPage are all types of pages you could use in a Xamarin.Forms application. These pages would then hold Elements 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, 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, Widgets are immutable and you can not 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.

For 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 a 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, if you had to remove or add an Element, you had to do so in code. This would involve 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 Animations 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 any 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 VisualElements 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 VisualElements 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"),
  );
}

Navigation

How do I navigate between pages?

In Xamarin.Forms, you navigate between pages normally through a, you can use a NavigationPage that manages the stack of pages to display.

Flutter has a similar implementation, using a Navigator and Routes. A Route is an abstraction for a Page of an app, and a Navigator is a widget that manages routes.

A route roughly maps to a Page. The navigator works in a similar way to the Xamarin.Forms NavigationPage, in that it can push() and pop() routes depending on whether you want to navigate to, or back from, a view.

To navigate between pages, you have a couple options:

  • Specify a Map of route names. (MaterialApp)
  • Directly navigate to a route. (WidgetApp)

The following example builds a Map.

void main() {
  runApp(new MaterialApp(
    home: new MyAppHome(), // becomes the route named '/'
    routes: <String, WidgetBuilder> {
      '/a': (BuildContext context) => new MyPage(title: 'page A'),
      '/b': (BuildContext context) => new MyPage(title: 'page B'),
      '/c': (BuildContext context) => new MyPage(title: 'page C'),
    },
  ));
}

Navigate to a route by pushing its name to the Navigator.

Navigator.of(context).pushNamed('/b');

The Navigator is a stack that manages your app’s routes. Pushing a route to the stack moves to that route. Popping a route from the stack, returns to the previous route. This is done by awaiting on the Future returned by push().

Async/await is very similar to the .NET implementation and is explained in more detail in Async UI.

For example, to start a location route that lets the user select their location, you might do the following:

Map coordinates = await Navigator.of(context).pushNamed('/location');

And then, inside your ‘location’ route, once the user has selected their location, pop() the stack with the result:

Navigator.of(context).pop({"lat":43.821757,"long":-79.226392});

How do I navigate to another app?

In Xamarin.Forms, to send the user to another application, you use a specific URI scheme, using Device.OpenUrl("mailto://")

To implement this functionality in Flutter, create a native platform integration, or use an existing plugin, such as url_launcher, available with many other packages on the Pub site.

Async UI

What is the equivalent of Device.BeginOnMainThread() in Flutter?

Dart has a single-threaded execution model, with support for Isolates (a way to run Dart code on another thread), an event loop, and asynchronous programming. Unless you spawn an Isolate, your Dart code runs in the main UI thread and is driven by an event loop.

Dart’s single-threaded model doesn’t mean you need to run everything as a blocking operation that causes the UI to freeze. Much like Xamarin.Forms, you need to keep the UI thread free. You would use async/await to perform tasks, where you must wait for the response.

In Flutter, use the asynchronous facilities that the Dart language provides, also named async/await, to perform asynchronous work. This is very similar to C# and should be very easy to use for any Xamarin.Forms developer.

For example, you can run network code without causing the UI to hang by using async/await and letting Dart do the heavy lifting:

loadData() async {
  String dataURL = "https://jsonplaceholder.typicode.com/posts";
  http.Response response = await http.get(dataURL);
  setState(() {
    widgets = json.decode(response.body);
  });
}

Once the awaited network call is done, update the UI by calling setState(), which triggers a rebuild of the widget sub-tree and updates the data.

The following example loads data asynchronously and displays it in a ListView:

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(new SampleApp());
}

class SampleApp extends StatelessWidget {
  @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> {
  List widgets = [];

  @override
  void initState() {
    super.initState();

    loadData();
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Sample App"),
      ),
      body: new ListView.builder(
          itemCount: widgets.length,
          itemBuilder: (BuildContext context, int position) {
            return getRow(position);
          }));
  }

  Widget getRow(int i) {
    return new Padding(
      padding: new EdgeInsets.all(10.0),
      child: new Text("Row ${widgets[i]["title"]}")
    );
  }

  loadData() async {
    String dataURL = "https://jsonplaceholder.typicode.com/posts";
    http.Response response = await http.get(dataURL);
    setState(() {
      widgets = json.decode(response.body);
    });
  }
}

Refer to the next section for more information on doing work in the background, and how Flutter differs from Android.

How do you move work to a background thread?

Since Flutter is single threaded and runs an event loop, you don’t have to worry about thread management or spawning background threads. This is very similar to Xamarin.Forms. If you’re doing I/O-bound work, such as disk access or a network call, then you can safely use async/await and you’re all set.

If, on the other hand, you need to do computationally intensive work that keeps the CPU busy, you want to move it to an Isolate to avoid blocking the event loop, like you would keep any sort of work out of the main thread. This is similar to when you move things to a different thread via Task.Run() in Xamarin.Forms.

For I/O-bound work, declare the function as an async function, and await on long-running tasks inside the function:

loadData() async {
  String dataURL = "https://jsonplaceholder.typicode.com/posts";
  http.Response response = await http.get(dataURL);
  setState(() {
    widgets = json.decode(response.body);
  });
}

This is how you would typically do network or database calls, which are both I/O operations.

However, there are times when you might be processing a large amount of data and your UI hangs. In Flutter, use Isolates to take advantage of multiple CPU cores to do long-running or computationally intensive tasks.

Isolates are separate execution threads that do not share any memory with the main execution memory heap. This is a difference between Task.Run(). This means you can’t access variables from the main thread, or update your UI by calling setState().

The following example shows, in a simple isolate, how to share data back to the main thread to update the UI.

loadData() async {
  ReceivePort receivePort = new ReceivePort();
  await Isolate.spawn(dataLoader, receivePort.sendPort);

  // The 'echo' isolate sends its SendPort as the first message
  SendPort sendPort = await receivePort.first;

  List msg = await sendReceive(sendPort, "https://jsonplaceholder.typicode.com/posts");

  setState(() {
    widgets = msg;
  });
}

// The entry point for the isolate
static dataLoader(SendPort sendPort) async {
  // Open the ReceivePort for incoming messages.
  ReceivePort port = new ReceivePort();

  // Notify any other isolates what port this isolate listens to.
  sendPort.send(port.sendPort);

  await for (var msg in port) {
    String data = msg[0];
    SendPort replyTo = msg[1];

    String dataURL = data;
    http.Response response = await http.get(dataURL);
    // Lots of JSON to parse
    replyTo.send(json.decode(response.body));
  }
}

Future sendReceive(SendPort port, msg) {
  ReceivePort response = new ReceivePort();
  port.send([msg, response.sendPort]);
  return response.first;
}

Here, dataLoader() is the Isolate that runs in its own separate execution thread. In the isolate you can perform more CPU intensive processing (parsing a big JSON, for example), or perform computationally intensive math, such as encryption or signal processing.

You can run the full example below:

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:isolate';

void main() {
  runApp(new SampleApp());
}

class SampleApp extends StatelessWidget {
  @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> {
  List widgets = [];

  @override
  void initState() {
    super.initState();
    loadData();
  }

  showLoadingDialog() {
    if (widgets.length == 0) {
      return true;
    }

    return false;
  }

  getBody() {
    if (showLoadingDialog()) {
      return getProgressDialog();
    } else {
      return getListView();
    }
  }

  getProgressDialog() {
    return new Center(child: new CircularProgressIndicator());
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
          title: new Text("Sample App"),
        ),
        body: getBody());
  }

  ListView getListView() => new ListView.builder(
      itemCount: widgets.length,
      itemBuilder: (BuildContext context, int position) {
        return getRow(position);
      });

  Widget getRow(int i) {
    return new Padding(padding: new EdgeInsets.all(10.0), child: new Text("Row ${widgets[i]["title"]}"));
  }

  loadData() async {
    ReceivePort receivePort = new ReceivePort();
    await Isolate.spawn(dataLoader, receivePort.sendPort);

    // The 'echo' isolate sends its SendPort as the first message
    SendPort sendPort = await receivePort.first;

    List msg = await sendReceive(sendPort, "https://jsonplaceholder.typicode.com/posts");

    setState(() {
      widgets = msg;
    });
  }

  // the entry point for the isolate
  static dataLoader(SendPort sendPort) async {
    // Open the ReceivePort for incoming messages.
    ReceivePort port = new ReceivePort();

    // Notify any other isolates what port this isolate listens to.
    sendPort.send(port.sendPort);

    await for (var msg in port) {
      String data = msg[0];
      SendPort replyTo = msg[1];

      String dataURL = data;
      http.Response response = await http.get(dataURL);
      // Lots of JSON to parse
      replyTo.send(json.decode(response.body));
    }
  }

  Future sendReceive(SendPort port, msg) {
    ReceivePort response = new ReceivePort();
    port.send([msg, response.sendPort]);
    return response.first;
  }
}

How do I make network requests?

In Xamarin.Forms you would use HttpClient. Making a network call in Flutter is easy when you use the popular http package. This abstracts away a lot of the networking that you might normally implement yourself, making it simple to make network calls.

To use the http package, add it to your dependencies in pubspec.yaml:

dependencies:
  ...
  http: ^0.11.3+16

To make a network request, call await on the async function http.get():

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
[...]
  loadData() async {
    String dataURL = "https://jsonplaceholder.typicode.com/posts";
    http.Response response = await http.get(dataURL);
    setState(() {
      widgets = json.decode(response.body);
    });
  }
}

How do I show the progress for a long-running task?

In Xamarin.Forms you would typically create a loading indicator, either directly in XAML or through a 3rd party plugin such as AcrDialogs.

In Flutter, use a ProgressIndicator widget. Show the progress programmatically by controlling when it’s rendered through a boolean flag. Tell Flutter to update its state before your long-running task starts, and hide it after it ends.

In the following example, the build function is separated into three different functions. If showLoadingDialog() is true (when widgets.length == 0), then render the ProgressIndicator. Otherwise, render the ListView with the data returned from a network call.

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(new SampleApp());
}

class SampleApp extends StatelessWidget {
  @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> {
  List widgets = [];

  @override
  void initState() {
    super.initState();
    loadData();
  }

  showLoadingDialog() {
    return widgets.length == 0;
  }

  getBody() {
    if (showLoadingDialog()) {
      return getProgressDialog();
    } else {
      return getListView();
    }
  }

  getProgressDialog() {
    return new Center(child: new CircularProgressIndicator());
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
          title: new Text("Sample App"),
        ),
        body: getBody());
  }

  ListView getListView() => new ListView.builder(
      itemCount: widgets.length,
      itemBuilder: (BuildContext context, int position) {
        return getRow(position);
      });

  Widget getRow(int i) {
    return new Padding(padding: new EdgeInsets.all(10.0), child: new Text("Row ${widgets[i]["title"]}"));
  }

  loadData() async {
    String dataURL = "https://jsonplaceholder.typicode.com/posts";
    http.Response response = await http.get(dataURL);
    setState(() {
      widgets = json.decode(response.body);
    });
  }
}

Project structure & resources

Where do I store my image files?

Xamarin.Forms has no platform independent way of storing images, you had to place images in the iOS xcasset folder or on Android, in the various drawable folders.

While Android and iOS treat resources and assets as distinct items, Flutter apps have only assets. All resources that would live in the Resources/drawable-* folders on Android, are placed in an assets folder for Flutter.

Flutter follows a simple density-based format like iOS. Assets might be 1.0x, 2.0x, 3.0x, or any other multiplier. Flutter doesn’t have dps but there are logical pixels, which are basically the same as device-independent pixels. The so-called devicePixelRatio expresses the ratio of physical pixels in a single logical pixel.

The equivalent to Android’s density buckets are:

Android density qualifierFlutter pixel ratio
ldpi0.75x
mdpi1.0x
hdpi1.5x
xhdpi2.0x
xxhdpi3.0x
xxxhdpi4.0x

Assets are located in any arbitrary folder—Flutter has no predefined folder structure. You declare the assets (with location) in the pubspec.yaml file, and Flutter picks them up.

Note that before Flutter 1.0 beta 2, assets defined in Flutter were not accessible from the native side, and vice versa, native assets and resources weren’t available to Flutter, as they lived in separate folders.

As of Flutter beta 2, assets are stored in the native asset folder, and are accessed on the native side using Android’s AssetManager:

As of Flutter beta 2, Flutter still cannot access native resources, nor it can access native assets.

To add a new image asset called my_icon.png to our Flutter project, for example, and deciding that it should live in a folder we arbitrarily called images, you would put the base image (1.0x) in the images folder, and all the other variants in sub-folders called with the appropriate ratio multiplier:

images/my_icon.png       // Base: 1.0x image
images/2.0x/my_icon.png  // 2.0x image
images/3.0x/my_icon.png  // 3.0x image

Next, you’ll need to declare these images in your pubspec.yaml file:

assets:
 - images/my_icon.jpeg

You can then access your images using AssetImage:

return new AssetImage("images/a_dot_burr.jpeg");

or directly in an Image widget:

@override
Widget build(BuildContext context) {
  return new Image.asset("images/my_image.png");
}

More detailed information can be found in Adding Assets and Images in Flutter.

Where do I store strings? How do I handle localization?

Unlike .NET which has resx files, Flutter currently doesn’t have a dedicated resources-like system for strings. At the moment, the best practice is to hold your copy text in a class as static fields and accessing them from there. For example:

class Strings {
  static String welcomeMessage = "Welcome To Flutter";
}

Then in your code, you can access your strings as such:

new Text(Strings.welcomeMessage)

By default, Flutter only supports US English for its strings. If you need to add support for other languages, include the flutter_localizations package. You might also need to add Dart’s intl package to use i10n machinery, such as date/time formatting.

dependencies:
  # ...
  flutter_localizations:
    sdk: flutter
  intl: "^0.15.6"

To use the flutter_localizations package, specify the localizationsDelegates and supportedLocales on the app widget:

import 'package:flutter_localizations/flutter_localizations.dart';

new MaterialApp(
 localizationsDelegates: [
   // Add app-specific localization delegate[s] here
   GlobalMaterialLocalizations.delegate,
   GlobalWidgetsLocalizations.delegate,
 ],
 supportedLocales: [
    const Locale('en', 'US'), // English
    const Locale('he', 'IL'), // Hebrew
    // ... other locales the app supports
  ],
  // ...
)

The delegates contain the actual localized values, while the supportedLocales defines which locales the app supports. The above example uses a MaterialApp, so it has both a GlobalWidgetsLocalizations for the base widgets localized values, and a MaterialWidgetsLocalizations for the Material widgets localizations. If you use WidgetsApp for your app, you don’t need the latter. Note that these two delegates contain “default” values, but you’ll need to provide one or more delegates for your own app’s localizable copy, if you want those to be localized too.

When initialized, the WidgetsApp (or MaterialApp) creates a Localizations widget for you, with the delegates you specify. The current locale for the device is always accessible from the Localizations widget from the current context (in the form of a Locale object), or using the Window.locale.

To access localized resources, use the Localizations.of() method to access a specific localizations class that is provided by a given delegate. Use the intl_translation package to extract translatable copy to arb files for translating, and importing them back into the app for using them with intl.

For further details on internationalization and localization in Flutter, see the internationalization guide, which has sample code with and without the intl package.

Where is my project file?

In Xamarin.Forms you will have a csproj file. The closest equivalent in Flutter is pubspec.yaml, which contains package dependencies and various project details. Similar to .NET Standard, files within the same directory are considered part of the project.

What is the equivalent of Nuget? How do I add dependencies?

In the .NET eco-system, native Xamarin projects and Xamarin.Forms projects had access to Nuget and the inbuilt package management system. Flutter apps contain a native Android app, native iOS app and Flutter app.

In Android, you add dependencies by adding to your Gradle build script. In iOS, you add dependencies by adding to your Podfile.

Flutter uses Dart’s own build system, and the Pub package manager. The tools delegate the building of the native Android and iOS wrapper apps to the respective build systems.

In general, use pubspec.yaml to declare external dependencies to use in Flutter. A good place to find Flutter packages is Pub.

Application Lifecycle

How do I listen to application lifecycle events?

In Xamarin.Forms, you have an Application that contains OnStart, OnResume and OnSleep. In Flutter you can instead listen to similar lifecycle events by hooking into the WidgetsBinding observer and listening to the didChangeAppLifecycleState() change event.

The observable lifecycle events are:

  • inactive — The application is in an inactive state and is not receiving user input. This event is iOS only.
  • paused — The application is not currently visible to the user, is not responding to user input, but is running in the background.
  • resumed — The application is visible and responding to user input.
  • suspending — The application is suspended momentarily. This event is Android only.

For more details on the meaning of these states, see AppLifecycleStatus documentation.

Layouts

What is the equivalent of a StackLayout?

In Xamarin.Forms you can create a StackLayout with an Orientation of Horizontal or Vertical. Flutter has a similar approach, however you would use the Row or Column widgets.

If you notice the two code samples are identical with the exception of the “Row” and “Column” widget. The children are the same and this feature can be exploited to develop rich layouts that can change overtime with the same children.

@override
Widget build(BuildContext context) {
  return new Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      new Text('Row One'),
      new Text('Row Two'),
      new Text('Row Three'),
      new Text('Row Four'),
    ],
  );
}
@override
Widget build(BuildContext context) {
  return new Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      new Text('Column One'),
      new Text('Column Two'),
      new Text('Column Three'),
      new Text('Column Four'),
    ],
  );
}

What is the equivalent of a Grid?

The closest equivalent of a Grid would be to use a GridView. This is much more powerful than what you are used to in Xamarin.Forms. A GridView provides automatic scrolling when the content exceeds the its viewable space.

GridView.count(
  // Create a grid with 2 columns. If you change the scrollDirection to
  // horizontal, this would produce 2 rows.
  crossAxisCount: 2,
  // Generate 100 Widgets that display their index in the List
  children: List.generate(100, (index) {
    return Center(
      child: Text(
        'Item $index',
        style: Theme.of(context).textTheme.headline,
      ),
    );
  }),
);

You may have used a Grid in Xamarin.Forms to implement widgets that overlay other widgets. In Flutter, you accomplish this with the Stack widget.

This sample creates two icons that overlap each other.

child: new Stack(
  children: <Widget>[
    new Icon(Icons.add_box, size: 24.0, color: const Color.fromRGBO(0,0,0,1.0)),
    new Positioned(
      left: 10.0,
      child: new Icon(Icons.add_circle, size: 24.0, color: const Color.fromRGBO(0,0,0,1.0)),
    ),
  ],
),

What is the equivalent of a ScrollView?

In Xamarin.Forms, a ScrollView wraps around a VisualElement and, if the content is larger than the device screen, it scrolls.

In Flutter, the closest match is the SingleChildScrollView widget. You simply fill the Widget with the content that you want to be scrollable.

@override
Widget build(BuildContext context) {
  return new SingleChildScrollView(
    child: new Text('Long Content'),
  );
}

If you have many items you want to wrap in a scroll, even of different Widget types, you might want to use a ListView. This might seem like overkill, but in Flutter this is far more optimized and less intensive than a Xamarin.Forms ListView which is backing on to platform specific controls.

@override
Widget build(BuildContext context) {
  return new ListView(
    children: <Widget>[
      new Text('Row One'),
      new Text('Row Two'),
      new Text('Row Three'),
      new Text('Row Four'),
    ],
  );
}

How do I handle landscape transitions in Flutter?

Landscape transitions can be handled automatically by setting the configChanges property in the AndroidManifest.xml:

android:configChanges="orientation|screenSize"

Gesture detection and touch event handling

How do I add GestureRecognizers to a widget in Flutter?

In Xamarin.Forms, Elements may contain a Click event you can attach to. Many elements also contain a Command that is tied to this event. Alternatively you would use the TapGestureRecognizer. In Flutter there are two very similar ways:

  1. If the Widget supports event detection, pass a function to it and handle it in the function. For example, the RaisedButton has an onPressed parameter:

    @override
    Widget build(BuildContext context) {
      return new RaisedButton(
          onPressed: () {
            print("click");
          },
          child: new Text("Button"));
    }
    </pr
                      
                      
                    

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文