Skip to main content

面向 SwiftUI 开发人员的 Flutter

希望使用 Flutter 编写移动应用的 SwiftUI 开发人员应该阅读本指南。 它解释了如何将现有的 SwiftUI 知识应用于 Flutter。

Flutter 是一个用于构建跨平台应用程序的框架,它使用 Dart 编程语言。 要了解 Dart 编程和 Swift 编程之间的一些区别,请参阅作为 Swift 开发人员学习 Dart面向 Swift 开发人员的 Flutter 并发

在使用 Flutter 构建应用时,您的 SwiftUI 知识和经验非常宝贵。

Flutter 在 iOS 和 macOS 上运行时还会对应用程序行为进行一些调整。 要了解如何操作,请参阅平台适配

可以通过跳转并找到与您的需求最相关的疑问来使用本文档作为参考手册。 本指南嵌入示例代码。 通过使用悬停或聚焦时出现的“在 DartPad 中打开”按钮,您可以在 DartPad 上打开和运行一些示例。

概述

#

作为介绍,请观看以下视频。 它概述了 Flutter 在 iOS 上的工作原理以及如何使用 Flutter 构建 iOS 应用。


Flutter for iOS developers

Flutter 和 SwiftUI 代码描述了 UI 的外观和工作方式。 开发人员将这种类型的代码称为 声明式框架

视图与 Widget

#

SwiftUI 将 UI 组件表示为 视图 。 您可以使用 修饰符 配置视图。

swift
Text("Hello, World!") // <-- This is a View
  .padding(10)        // <-- This is a modifier of that View

Flutter 将 UI 组件表示为_Widget_。

视图和 Widget 只在需要更改时才存在。 这些语言称此属性为 不变性 。 SwiftUI 将 UI 组件属性表示为 View 修饰符。 相比之下,Flutter 使用 Widget 来表示 UI 组件及其属性。

dart
Padding(                         // <-- This is a Widget
  padding: EdgeInsets.all(10.0), // <-- So is this
  child: Text("Hello, World!"),  // <-- This, too
)));

要组合布局,SwiftUI 和 Flutter 都将 UI 组件相互嵌套。 SwiftUI 嵌套视图,而 Flutter 嵌套 Widget。

布局过程

#

SwiftUI 使用以下过程来布局视图:

  1. 父视图向其子视图建议一个大小。
  2. 所有后续的子视图:
    • 它们 的子视图建议一个大小
    • 向子视图询问它想要什么大小
  3. 每个父视图都以返回的大小呈现其子视图。

Flutter 的过程略有不同:

  1. 父 Widget 向其子 Widget 传递约束。 约束包括高度和宽度的最小值和最大值。

  2. 子 Widget 尝试确定其大小。它对自己的子 Widget 列表重复相同的过程:

    • 它告知其子 Widget 子 Widget 的约束。
    • 它询问其子 Widget 希望多大。
  3. 父 Widget 布局子 Widget。

    • 如果请求的大小适合约束,则父 Widget 使用该大小。
    • 如果请求的大小不适合约束,则父 Widget 将高度、宽度或两者限制在 其约束范围内。

Flutter 与 SwiftUI 的不同之处在于,父组件可以覆盖子组件的所需大小。Widget 不能具有任何它想要的大小。 它也不能知道或决定其在屏幕上的位置,因为其父组件会做出该决定。

要强制子 Widget 以特定大小呈现,父 Widget 必须设置严格的约束。 当约束的最小大小值等于其最大大小值时,约束变为严格约束。

SwiftUI中,视图可能会扩展到可用空间或将其大小限制为其内容的大小。 Flutter Widget 的行为类似。

但是,在 Flutter 中,父 Widget 可以提供无界约束。 无界约束将其最大值设置为无穷大。

dart
UnboundedBox(
  child: Container(
      width: double.infinity, height: double.infinity, color: red),
)

如果子 Widget 扩展并且具有无界约束,Flutter 将返回溢出警告:

dart
UnconstrainedBox(
  child: Container(color: red, width: 4000, height: 50),
)
当父级向子级传递无界约束,并且子级正在扩展时,则会出现溢出警告。

要了解 Flutter 中约束的工作原理,请参阅了解约束

设计系统

#

由于 Flutter 面向多个平台,因此您的应用不需要符合任何设计系统。 尽管本指南以Material Widget 为特色,但您的 Flutter 应用可以使用许多不同的设计系统:

  • 自定义 Material Widget
  • 社区构建的 Widget
  • 您自己的自定义 Widget
  • 遵循 Apple 人机界面指南的Cupertino Widget

Flutter's cupertino library for iOS developers

如果您正在寻找一个具有自定义设计系统的优秀参考应用,请查看Wonderous

UI 基础

#

本节介绍 Flutter 中 UI 开发的基础知识以及它与 SwiftUI 的比较。 这包括如何开始开发您的应用、显示静态文本、创建按钮、对按下事件做出反应、显示列表、网格等等。

开始

#

SwiftUI中,您可以使用App启动您的应用。

swift
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            HomePage()
        }
    }
}

另一种常见的 SwiftUI 做法是将应用程序主体放在符合View协议的struct中,如下所示:

swift
struct HomePage: View {
  var body: some View {
    Text("Hello, World!")
  }
}

要启动您的Flutter应用,请将您的应用实例传递给runApp函数。

dart
void main() {
  runApp(const MyApp());
}

App是一个Widget。build方法描述了它所代表的用户界面的一部分。 通常使用WidgetApp类(例如CupertinoApp)开始您的应用。

dart
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    // 返回一个CupertinoApp,默认情况下,
    // 具有 iOS 应用的外观和感觉。
    return const CupertinoApp(
      home: HomePage(),
    );
  }
}

HomePage中使用的 Widget 可能会以Scaffold类开头。 Scaffold为应用实现了一个基本的布局结构。

dart
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Text(
          'Hello, World!',
        ),
      ),
    );
  }
}

请注意 Flutter 如何使用Center Widget。 SwiftUI 默认情况下在其中心呈现视图的内容。 Flutter 并非总是如此。 Scaffold不会在其屏幕中心的渲染body Widget。 要居中显示文本,请将其包装在Center Widget 中。 要了解不同 Widget 及其默认行为,请查看[Widget 目录][]。

添加按钮

#

SwiftUI中,您可以使用Button结构体来创建按钮。

swift
Button("Do something") {
  // 当您的按钮被点击时,此闭包将被调用
}

要在Flutter中实现相同的结果,请使用CupertinoButton类:

dart
CupertinoButton(
  onPressed: () {
    // 当您的按钮被点击时,此闭包将被调用。
  },
  const Text('Do something'),
),

Flutter让您可以访问各种具有预定义样式的按钮。 CupertinoButton类来自Cupertino库。 Cupertino库中的 Widget 使用 Apple 的设计系统。

水平对齐组件

#

SwiftUI中,堆叠视图在设计布局方面发挥着重要作用。 两个单独的结构允许您创建堆叠视图:

  1. 用于水平堆叠视图的HStack

  2. 用于垂直堆叠视图的VStack

以下 SwiftUI 视图将地球仪图像和文本添加到水平堆叠视图:

swift
HStack {
  Image(systemName: "globe")
  Text("Hello, world!")
}

Flutter使用Row而不是HStack

dart
Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    Icon(CupertinoIcons.globe),
    Text('Hello, world!'),
  ],
),

Row Widget 在children参数中需要一个List<Widget>mainAxisAlignment属性告诉 Flutter 如何定位具有额外空间的子 Widget。MainAxisAlignment.center将子 Widget 定位在主轴的中心。对于Row,主轴是水平轴。

垂直对齐组件

#

以下示例基于上一节中的示例。

SwiftUI中,您可以使用VStack将组件排列成垂直列。

swift
VStack {
  Image(systemName: "globe")
  Text("Hello, world!")
}

Flutter使用与上一示例相同的 Dart 代码, 只是它用Column替换了Row

dart
Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    Icon(CupertinoIcons.globe),
    Text('Hello, world!'),
  ],
),

显示列表视图

#

SwiftUI中,您可以使用List基本组件来显示项目序列。 要显示模型对象的序列,请确保用户可以识别您的模型对象。 要使对象可识别,请使用Identifiable协议。

swift
struct Person: Identifiable {
  var name: String
}

var persons = [
  Person(name: "Person 1"),
  Person(name: "Person 2"),
  Person(name: "Person 3"),
]

struct ListWithPersons: View {
  let persons: [Person]
  var body: some View {
    List {
      ForEach(persons) { person in
        Text(person.name)
      }
    }
  }
}

这类似于Flutter构建其列表 Widget 的方式。 Flutter 不需要列表项可识别。 您可以设置要显示的项目数,然后为每个项目构建一个 Widget。

dart
class Person {
  String name;
  Person(this.name);
}

final List<Person> items = [
  Person('Person 1'),
  Person('Person 2'),
  Person('Person 3'),
];

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView.builder(
        itemCount: items.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(items[index].name),
          );
        },
      ),
    );
  }
}

Flutter 列表有一些注意事项:

  • ListView Widget 有一个 builder 方法。 这类似于 SwiftUI 的 List 结构体中的 ForEach

  • ListViewitemCount 参数设置 ListView 显示的项目数量。

  • itemBuilder 有一个索引参数,该参数介于 0 和 itemCount 减 1 之间。

前面的示例为每个项目返回了一个 ListTile Widget。 ListTile Widget 包含 heightfont-size 等属性。 这些属性有助于构建列表。但是,Flutter 允许您返回几乎任何代表数据的 Widget。

显示网格

#

SwiftUI中构建非条件网格时,您可以使用带有 GridRowGrid

swift
Grid {
  GridRow {
    Text("Row 1")
    Image(systemName: "square.and.arrow.down")
    Image(systemName: "square.and.arrow.up")
  }
  GridRow {
    Text("Row 2")
    Image(systemName: "square.and.arrow.down")
    Image(systemName: "square.and.arrow.up")
  }
}

要在Flutter中显示网格,请使用GridView Widget。 此 Widget 有各种构造函数。每个构造函数的目标类似,但使用不同的输入参数。 以下示例使用.builder()初始化器:

dart
const widgets = [
  Text('Row 1'),
  Icon(CupertinoIcons.arrow_down_square),
  Icon(CupertinoIcons.arrow_up_square),
  Text('Row 2'),
  Icon(CupertinoIcons.arrow_down_square),
  Icon(CupertinoIcons.arrow_up_square),
];

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GridView.builder(
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          mainAxisExtent: 40,
        ),
        itemCount: widgets.length,
        itemBuilder: (context, index) => widgets[index],
      ),
    );
  }
}

SliverGridDelegateWithFixedCrossAxisCount委托确定网格用于布置其组件的各种参数。 这包括确定每行显示的项目数量的crossAxisCount

SwiftUI 的 Grid 和 Flutter 的 GridView 的区别在于 Grid 需要 GridRowGridView 使用委托来决定网格应如何布置其组件。

创建滚动视图

#

SwiftUI中,您可以使用ScrollView创建自定义滚动组件。 以下示例以可滚动的方式显示一系列PersonView实例。

swift
ScrollView {
  VStack(alignment: .leading) {
    ForEach(persons) { person in
      PersonView(person: person)
    }
  }
}

要创建滚动视图,Flutter 使用 SingleChildScrollView。 在以下示例中,函数 mockPerson 模拟 Person 类的实例以创建自定义 PersonView Widget。

dart
SingleChildScrollView(
  child: Column(
    children: mockPersons
        .map(
          (person) => PersonView(
            person: person,
          ),
        )
        .toList(),
  ),
),

响应式和自适应设计

#

SwiftUI中,您可以使用GeometryReader创建相对视图大小。

例如,您可以:

  • geometry.size.width乘以某个因子来设置 宽度
  • 使用GeometryReader作为断点来更改应用的设计。

您还可以使用horizontalSizeClass查看大小类是否具有.regular.compact

要在Flutter中创建相对视图,您可以使用以下两个选项之一:

要了解更多信息,请查看创建响应式和自适应应用

状态管理

#

SwiftUI中,您可以使用@State属性包装器来表示 SwiftUI 视图的内部状态。

swift
struct ContentView: View {
  @State private var counter = 0;
  var body: some View {
    VStack{
      Button("+") { counter+=1 }
      Text(String(counter))
    }
  }}

SwiftUI还包括用于更复杂状态管理的几个选项,例如ObservableObject协议。

Flutter 使用 StatefulWidget 管理局部状态。 使用以下两个类实现有状态 Widget:

  • StatefulWidget的子类
  • State的子类

State对象存储 Widget 的状态。 要更改 Widget 的状态,请从State子类调用setState()以告知框架重新绘制 Widget。

以下示例显示了计数器应用程序的一部分:

dart
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('$_counter'),
            TextButton(
              onPressed: () => setState(() {
                _counter++;
              }),
              child: const Text('+'),
            ),
          ],
        ),
      ),
    );
  }
}

要了解管理状态的更多方法,请查看状态管理

动画

#

存在两种主要的 UI 动画类型。

  • 隐式动画,从当前值动画到新的目标值。
  • 显式动画,在被请求时执行动画。

隐式动画

#

SwiftUI 和 Flutter 对动画采取了类似的方法。 在这两个框架中,您都指定了诸如durationcurve之类的参数。

SwiftUI中,您可以使用animate()修饰符来处理隐式动画。

swift
Button("Tap me!"){
   angle += 45
}
.rotationEffect(.degrees(angle))
.animation(.easeIn(duration: 1))

Flutter包含用于隐式动画的 Widget。 这简化了常用 Widget 的动画制作。 Flutter 使用以下格式命名这些 Widget:AnimatedFoo

例如:要旋转按钮,请使用AnimatedRotation类。 这将为Transform.rotate Widget 制作动画。

dart
AnimatedRotation(
  duration: const Duration(seconds: 1),
  turns: turns,
  curve: Curves.easeIn,
  TextButton(
      onPressed: () {
        setState(() {
          turns += .125;
        });
      },
      const Text('Tap me!')),
),

Flutter 允许您创建自定义隐式动画。 要组合一个新的动画 Widget,请使用TweenAnimationBuilder

显式动画

#

对于显式动画,SwiftUI使用withAnimation()函数。

Flutter包含名称格式为FooTransition的显式动画 Widget。 一个例子是RotationTransition类。

Flutter 还允许您使用AnimatedWidgetAnimatedBuilder创建自定义显式动画。

要了解有关 Flutter 中动画的更多信息,请参阅动画概述

在屏幕上绘图

#

SwiftUI中,您可以使用CoreGraphics将线条和形状绘制到屏幕上。

Flutter有一个基于Canvas类的 API,有两个类可以帮助您绘图:

  1. CustomPaint,它需要一个 painter:

    dart
    CustomPaint(
      painter: SignaturePainter(_points),
      size: Size.infinite,
    ),
  2. CustomPainter,它实现您的算法以绘制到画布上。

    dart
    class SignaturePainter extends CustomPainter {
      SignaturePainter(this.points);
    
      final List<Offset?> points;
    
      @override
      void paint(Canvas canvas, Size size) {
        final Paint paint = Paint()
          ..color = Colors.black
          ..strokeCap = StrokeCap.round
          ..strokeWidth = 5;
        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);
          }
        }
      }
    
      @override
      bool shouldRepaint(SignaturePainter oldDelegate) =>
          oldDelegate.points != points;
    }

导航

#

本节解释如何在应用的页面之间导航,推送和弹出机制等等。

页面之间导航

#

开发人员使用称为 导航路由 的不同页面构建 iOS 和 macOS 应用。

SwiftUI中,NavigationStack表示此页面堆栈。

以下示例创建一个显示人员列表的应用。 要在新导航链接中显示某人的详细信息,请点击该人员。

swift
NavigationStack(path: $path) {
      List {
        ForEach(persons) { person in
          NavigationLink(
            person.name,
            value: person
          )
        }
      }
      .navigationDestination(for: Person.self) { person in
        PersonView(person: person)
      }
    }

如果您有一个没有复杂链接的小型Flutter应用,请使用带有命名路由的Navigator。 定义导航路由后,使用其名称调用导航路由。

  1. 在传递给runApp()函数的类中命名每个路由。 以下示例使用App

    dart
    // 将路由名称定义为常量
    // 以便可以重复使用。
    const detailsPageRouteName = '/details';
    
    class App extends StatelessWidget {
      const App({
        super.key,
      });
    
      @override
      Widget build(BuildContext context) {
        return CupertinoApp(
          home: const HomePage(),
          // [routes] 属性定义了可用的命名路由
          // 以及在导航到这些路由时要构建的 Widget。
          routes: {
            detailsPageRouteName: (context) => const DetailsPage(),
          },
        );
      }
    }

    以下示例使用mockPersons()生成人员列表。点击某人会使用pushNamed()将该人的详细信息页面推送到Navigator

    dart
    ListView.builder(
      itemCount: mockPersons.length,
      itemBuilder: (context, index) {
        final person = mockPersons.elementAt(index);
        final age = '${person.age} years old';
        return ListTile(
          title: Text(person.name),
          subtitle: Text(age),
          trailing: const Icon(
            Icons.arrow_forward_ios,
          ),
          onTap: () {
            // 当点击代表一个人的 [ListTile] 时,
            // 将 detailsPageRouteName 路由推送到 Navigator,并将该人的实例
            // 传递到路由。
            Navigator.of(context).pushNamed(
              detailsPageRouteName,
              arguments: person,
            );
          },
        );
      },
    ),
  2. 定义 DetailsPage Widget,用于显示每个人的详细信息。在 Flutter 中,您可以在导航到新路由时将参数传递到 Widget 中。使用 ModalRoute.of()提取参数:

    dart
    class DetailsPage extends StatelessWidget {
      const DetailsPage({super.key});
    
      @override
      Widget build(BuildContext context) {
        // 从参数中读取 Person 实例。
        final Person person = ModalRoute.of(
          context,
        )?.settings.arguments as Person;
        // 提取年龄。
        final age = '${person.age} years old';
        return Scaffold(
          // 显示姓名和年龄。
          body: Column(children: [Text(person.name), Text(age)]),
        );
      }
    }

要创建更高级的导航和路由需求,请使用路由包,例如go_router

要了解更多信息,请查看导航和路由

手动返回

#

SwiftUI中,您可以使用dismiss环境值返回到上一屏幕。

swift
Button("Pop back") {
        dismiss()
      }

Flutter中,使用Navigator类的pop()函数:

dart
TextButton(
  onPressed: () {
    // 此代码允许
    // 视图返回到其演示者。
    Navigator.of(context).pop();
  },
  child: const Text('Pop back'),
),

导航到另一个应用

#

SwiftUI中,您可以使用openURL环境变量打开另一个应用程序的 URL。

swift
@Environment(\.openURL) private var openUrl

// View 代码在此处

 Button("Open website") {
      openUrl(
        URL(
          string: "https://google.com"
        )!
      )
    }

Flutter中,请使用url_launcher插件。

dart
CupertinoButton(
  onPressed: () async {
    await launchUrl(
      Uri.parse('https://google.com'),
    );
  },
  const Text(
    'Open website',
  ),
),

主题、样式和媒体

#

您可以轻松地为 Flutter 应用设置样式。 样式包括在浅色和深色主题之间切换,更改文本和 UI 组件的设计等等。本节介绍如何设置应用的样式。

使用深色模式

#

SwiftUI中,您可以在View上调用preferredColorScheme()函数来使用深色模式。

Flutter中,您可以应用级别控制浅色和深色模式。要控制亮度模式,请使用App类的theme属性:

dart
const CupertinoApp(
  theme: CupertinoThemeData(
    brightness: Brightness.dark,
  ),
  home: HomePage(),
);

设置文本样式

#

SwiftUI中,您可以使用修饰符函数来设置文本样式。例如,要更改Text字符串的字体,请使用font()修饰符:

swift
Text("Hello, world!")
  .font(.system(size: 30, weight: .heavy))
  .foregroundColor(.yellow)

要在Flutter中设置文本样式,请将TextStyle Widget 添加为Text Widget 的style参数的值。

dart
Text(
  'Hello, world!',
  style: TextStyle(
    fontSize: 30,
    fontWeight: FontWeight.bold,
    color: CupertinoColors.systemYellow,
  ),
),

设置按钮样式

#

SwiftUI中,您可以使用修饰符函数来设置按钮样式。

swift
Button("Do something") {
    // 点击按钮时执行操作
  }
  .font(.system(size: 30, weight: .bold))
  .background(Color.yellow)
  .foregroundColor(Color.blue)
}

要在Flutter中设置按钮 Widget 的样式,请设置其子 Widget 的样式,或修改按钮本身的属性。

在以下示例中:

  • CupertinoButtoncolor属性设置其颜色。
  • Text Widget 的color属性设置按钮文本颜色。
dart
child: CupertinoButton(
  color: CupertinoColors.systemYellow,
  onPressed: () {},
  padding: const EdgeInsets.all(16),
  child: const Text(
    'Do something',
    style: TextStyle(
      color: CupertinoColors.systemBlue,
      fontSize: 30,
      fontWeight: FontWeight.bold,
    ),
  ),
),

使用自定义字体

#

SwiftUI中,您可以分两步在应用中使用自定义字体。首先,将字体文件添加到您的 SwiftUI 项目中。添加文件后,使用.font()修饰符将其应用于您的 UI 组件。

swift
Text("Hello")
  .font(
    Font.custom(
      "BungeeSpice-Regular",
      size: 40
    )
  )

Flutter中,您可以使用名为pubspec.yaml的文件来控制您的资源。此文件与平台无关。要将自定义字体添加到您的项目中,请按照以下步骤操作:

  1. 在项目的根目录中创建一个名为fonts的文件夹。此可选步骤有助于组织您的字体。

  2. 将您的.ttf.otf.ttc字体文件添加到fonts文件夹中。

  3. 打开项目中的pubspec.yaml文件。

  4. 找到flutter部分。

  5. fonts部分下添加您的自定义字体。

    yaml
    flutter:
      fonts:
        - family: BungeeSpice
          fonts:
            - asset: fonts/BungeeSpice-Regular.ttf

将字体添加到项目后,您可以像以下示例中那样使用它:

dart
Text(
  'Cupertino',
  style: TextStyle(
    fontSize: 40,
    fontFamily: 'BungeeSpice',
  ),
),

在应用中捆绑图像

#

SwiftUI中,您首先将图像文件添加到Assets.xcassets中,然后使用Image视图显示图像。

要在Flutter中添加图像,请按照添加自定义字体类似的方法进行操作。

  1. 将一个images文件夹添加到根目录。

  2. 将此资源添加到pubspec.yaml文件中。

    yaml
    flutter:
      assets:
        - images/Blueberries.jpg

添加图像后,使用Image Widget 的.asset()构造函数显示它。此构造函数:

  1. 使用提供的路径实例化给定的图像。
  2. 从与您的应用捆绑在一起的资源中读取图像。
  3. 在屏幕上显示图像。

要查看完整的示例,请查看Image文档。

在应用中捆绑视频

#

SwiftUI中,您可以分两步将本地视频文件与您的应用捆绑在一起。首先,您导入AVKit框架,然后实例化VideoPlayer视图。

Flutter中,将video_player插件添加到您的项目中。此插件允许您创建一个可在 Android、iOS 和 Web 上使用相同代码库工作的视频播放器。

  1. 将插件添加到您的应用中,并将视频文件添加到您的项目中。
  2. 将资源添加到您的pubspec.yaml文件中。
  3. 使用VideoPlayerController类加载和播放您的视频文件。

要查看完整的演练,请查看video_player 示例