android button 添加事件_给 Android 开发者的 Flutter 指南(下)

这篇文档旨在帮助 Android 开发者通过 Flutter 开发移动应用。如果你了解 Android 框架的基本知识,你就可以使用这篇文档作为 Flutter 开发的快速入门。

这篇文档可以用作随时查阅以及答疑解惑的专题手册。

本系列上部分:给 Android 开发者的 Flutter 指南(上)

本文结构如下:

1.视图(上)

2.如何在布局中添加或删除一个组件?(上)

3.Intents(上)

4.异步 UI(上)

5.工程结构和资源文件(上)

6.Activity 和 Fragment(上)

7.布局(下)

8.手势监听和触摸事件处理(下)

9.Listviews 和 adapters(下)

10.文字处理(下)

11.表单输入(下)

12.Flutter 插件(下)

13.主题(下)

14.数据库和本地存储(下)

15.通知(下)


七、布局

7.1 LinearLayout 的对应概念是什么?

在 Android 中,LinearLayout 用于线性布局你的控件—水平或者垂直。在 Flutter 中,使用 Row 或者 Column Widget 来实现相同的效果。

如果你注意看的话,会发现下面的两段代码除了 Row 和 Column Widget 以外是一模一样的。它们的孩子是一样的,而这个特性可以被充分利用来开发包含有相同的孩子但是会随时间改变的复杂布局。

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

如果想学习更多的构建线性布局的内容,请阅读社区贡献的 Medium 文章给 Android 开发者的 Flutter 指南:如何在 Flutter 中设计线性布局?

7.2 RelativeLayout 的对应概念是什么?

RelativeLayout 通过 Widget 的相互位置对它们进行布局。在 Flutter 中,有几种实现相同效果的方法。

你可以通过组合使用 Column、Row 和 Stack Widget 实现 RelativeLayout 的效果。你还可以在 Widget 构造器内声明孩子相对父亲的布局规则。

Collin 在 StackOverflow 上的回答是一个在 Flutter 中构建相对布局的好例子。

7.3 ScrollView 的对应概念是什么?

在 Android 中,使用 ScrollView 布局控件—如果用户的设备屏幕比应用的内容区域小,用户可以滑动内容。

在 Flutter 中,实现这个功能的最简单的方法是使用 ListView Widget。从 Android 的角度看,这样做可能是杀鸡用牛刀了,但是 Flutter 中 ListView Widget 既是一个 ScrollView,也是一个 Android 中的 ListView。

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

7.4 在 Flutter 中如何处理屏幕旋转?

FlutterView 会处理配置的变化,前提条件是在 AndroidManifest.xml 文件中声明了:

android:configChanges="orientation|screenSize"

八、手势监听和触摸事件处理

8.1 Flutter 中如何为一个 Widget 添加点击监听器?

在 Android 中,你可以通过调用 setOnClickListener 方法在按钮这样的 View 上添加点击监听器。

在 Flutter 中有两种添加触摸监听器的方法:

1.如果 Widget 支持事件监听,那么向它传入一个方法并在方法中处理事件。例如,RaisedButton 有一个 onPressed 参数:

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

2.如果 Widget 不支持事件监听,将 Widget 包装进一个 GestureDetector 中并向 onTap 参数传入一个方法。

class SampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
      child: GestureDetector(
        child: FlutterLogo(
          size: 200.0,
        ),
        onTap: () {
          print("tap");
        },
      ),
    ));
  }
}

8.2 如何处理 Widget 上的其它手势?

使用 GestureDetector 可以监听非常多的手势,例如:

  • Tap

  • onTapDown - 一个可能产生点击事件的指针触摸到屏幕的特定位置。

  • onTapUp - 一个产生了点击事件的指针停止触摸屏幕的特定位置。

  • onTap - A tap has occurred.

  • onTap - 一个点击事件已经发生。

  • onTapCancel - 之前触发了 onTapDown 事件的指针不会产生点击事件。

  • Double tap

  • onDoubleTap - 用户在屏幕同一位置连续快速地点击两次。

  • Long press

  • onLongPress - 指针在屏幕的同一位置保持了一段较长时间的触摸状态。

  • Vertical drag

  • onVerticalDragStart - 指针已经触摸屏幕并可能开始垂直移动。

  • onVerticalDragUpdate - 触摸屏幕的指针在垂直方向移动了更多的距离。

  • onVerticalDragEnd - 之前和屏幕接触并垂直移动的指针不再继续和屏幕接触,并且在和屏幕停止接触的时候以一定的速度移动。

  • Horizontal drag

  • onHorizontalDragStart - 指针已经触摸屏幕并可能开始水平移动。

  • onHorizontalDragUpdate - 触摸屏幕的指针在水平方向移动了更多的距离。

  • onHorizontalDragEnd - 之前和屏幕接触并水平移动的指针不再继续和屏幕接触,并且在和屏幕停止接触的时候以一定的速度移动。

下面的例子展示了一个实现了双击旋转 Flutter 标志的 GestureDetector:

AnimationController controller;
CurvedAnimation curve;

@override
void initState() {
  controller = AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
  curve = CurvedAnimation(parent: controller, curve: Curves.easeIn);
}

class SampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
          child: GestureDetector(
            child: RotationTransition(
                turns: curve,
                child: FlutterLogo(
                  size: 200.0,
                )),
            onDoubleTap: () {
              if (controller.isCompleted) {
                controller.reverse();
              } else {
                controller.forward();
              }
            },
        ),
    ));
  }
}

九、Listviews 和 adapters

9.1 ListView 在 Flutter 中的对应概念是什么?

Flutter 中 ListView 的对应概念仍然是…ListView!

使用 Android 的 ListView 时,创建一个 adapter 并将其传给 ListView,ListView 渲染 adapter 返回的每一行内容。然后,你需要确保回收了每一行视图,否则,你会遇到各种奇怪的 界面和内存问题。

因为 Flutter Widget 不可变的特点,你需要向 ListView 传入一组 Widget, Flutter 会保证滑动的快速顺畅。

import 'package:flutter/material.dart';

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

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Sample App"),
      ),
      body: ListView(children: _getListData()),
    );
  }

  _getListData() {
    List widgets = [];for (int i = 0; i 100; i++) {
      widgets.add(Padding(padding: EdgeInsets.all(10.0), child: Text("Row $i")));
    }return widgets;
  }
}

9.2 如何知道点击了哪个列表项?

在 Android 中,ListView 有一个可以帮助你定位哪个列表项被点击了的方法 onItemClickListener。在 Flutter 中,则使用传入 Widget 的触摸监听。

import 'package:flutter/material.dart';

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

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Sample App"),
      ),
      body: ListView(children: _getListData()),
    );
  }

  _getListData() {
    List widgets = [];for (int i = 0; i 100; i++) {
      widgets.add(GestureDetector(
        child: Padding(
            padding: EdgeInsets.all(10.0),
            child: Text("Row $i")),
        onTap: () {
          print('row tapped');
        },
      ));
    }return widgets;
  }
}

9.3 如何动态更新 ListView?

在 Android 中,你需要更新 adapter 并调用 notifyDataSetChanged。

在 Flutter 中,如果你准备在 setState() 里更新一组 Widget,你很快会发现你的数据并没有 更新到界面上。这是因为当 setState() 被调用的时候,Flutter 渲染引擎会查看 Widget 树是否有任何更改。当引擎检查到 ListView,他会执行 == 检查,并判断两个 ListView 是一样的。没有任何更改,所以也就不需要更新。

更新 ListView 的一个简单方法是,在 setState() 里创建一个新的 List,并将数据从旧列表拷贝到新列表。虽然这个方法很简单,就如下面例子所示,但是并不推荐在大数据集的时候使用。

import 'package:flutter/material.dart';

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

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  List widgets = [];@overridevoid initState() {super.initState();for (int i = 0; i 100; i++) {
      widgets.add(getRow(i));
    }
  }@overrideWidget build(BuildContext context) {return Scaffold(
      appBar: AppBar(
        title: Text("Sample App"),
      ),
      body: ListView(children: widgets),
    );
  }Widget getRow(int i) {return GestureDetector(
      child: Padding(
          padding: EdgeInsets.all(10.0),
          child: Text("Row $i")),
      onTap: () {
        setState(() {
          widgets = List.from(widgets);
          widgets.add(getRow(widgets.length + 1));
          print('row $i');
        });
      },
    );
  }
}

推荐的高效且有效的创建一个列表的方法是使用 ListView.Builder。这个方法非常适用于动态列表或者拥有大量数据的列表。这基本上就是 Android 里的 RecyclerView,会为你自动回收列表项:

import 'package:flutter/material.dart';

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

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  List widgets = [];@overridevoid initState() {super.initState();for (int i = 0; i 100; i++) {
      widgets.add(getRow(i));
    }
  }@overrideWidget build(BuildContext context) {return Scaffold(
        appBar: AppBar(
          title: Text("Sample App"),
        ),
        body: ListView.builder(
            itemCount: widgets.length,
            itemBuilder: (BuildContext context, int position) {return getRow(position);
            }));
  }Widget getRow(int i) {return GestureDetector(
      child: Padding(
          padding: EdgeInsets.all(10.0),
          child: Text("Row $i")),
      onTap: () {
        setState(() {
          widgets.add(getRow(widgets.length + 1));
          print('row $i');
        });
      },
    );
  }
}

不用创建一个“ListView”,而是创建接收两个参数的 ListView.Builder,两个参数分别是列表的初始长度和一个 ItemBuilder 方法。

ItemBuilder 方法和 Android adapter 里的 getView 方法类似;它通过位置返回你期望在这个位置渲染的列表项。

最后也是最重要的一条,需要注意 onTap() 方法不再重建列表项,但是会执行 .add 操作。

十、文字处理

10.1 如何为 Text Widget 设置自定义字体?

在 Android SDK 中(从 Android O 开始),你可以创建一个字体资源文件并将其传给 TextView 的 FontFamily 参数。

在 Flutter 中,将字体文件放入一个文件夹,并在 pubspec.yaml 文件中引用它,就和导入图片一样。

fonts:
   - family: MyCustomFont
     fonts:
       - asset: fonts/MyCustomFont.ttf
       - style: italic

然后将字体赋值给你的 Text Widget:

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text("Sample App"),
    ),
    body: Center(
      child: Text(
        'This is a custom font text',
        style: TextStyle(fontFamily: 'MyCustomFont'),
      ),
    ),
  );
}

10.2 如何更改 Text Widget 的样式?

除了字体,你还可以自定义 Text Widget 的其它样式元素。Text Widget 的样式参数接收一个 TextStyle 对象,你可以在这个对象里自定义很多参数,例如:

  • color

  • decoration

  • decorationColor

  • decorationStyle

  • fontFamily

  • fontSize

  • fontStyle

  • fontWeight

  • hashCode

  • height

  • inherit

  • letterSpacing

  • textBaseline

  • wordSpacing

十一、表单输入

如果需要更多使用表单的信息,请查看 Flutter Cookbook 中的检索一个文本字段的值。

11.1 Input 的“提示”(“hint”)的对应概念是什么?

在 Flutter 中,你可以简单地通过向 Text Widget 构造器的 decoration 参数传入一个 InputDecoration 对象来为输入框展示一个“提示”或占位文本。

body: Center(
  child: TextField(
    decoration: InputDecoration(hintText: "This is a hint"),
  )
)

11.2 如何显示验证错误的信息?

就像上面实现“提示”功能一样,像 Text Widget 构造方法的 decoration 参数传入一个 InputDecoration 对象。

然而,你并不想一开始就显示错误信息。相反,当用户输入了无效的信息后,更新状态并传入一个新的 InputDecoration 对象。

import 'package:flutter/material.dart';

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

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  String _errorText;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Sample App"),
      ),
      body: Center(
        child: TextField(
          onSubmitted: (String text) {
            setState(() {
              if (!isEmail(text)) {
                _errorText = 'Error: This is not an email';
              } else {
                _errorText = null;
              }
            });
          },
          decoration: InputDecoration(hintText: "This is a hint", errorText: _getErrorText()),
        ),
      ),
    );
  }

  _getErrorText() {
    return _errorText;
  }

  bool isEmail(String em) {
    String emailRegexp =
        r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';

    RegExp regExp = RegExp(emailRegexp);

    return regExp.hasMatch(em);
  }
}

十二、Flutter 插件

12.1 如何使用 GPS 传感器?

使用 geolocator 社区插件。

12.2 如何使用相机?

image_picker 插件被常用于相机功能的使用。

12.3 如何使用 Facebook 登录?

使用 flutter_facebook_login 社区插件实现 Facebook 登录功能。

12.4 如何使用 Firebase 的功能?

官方插件提供了 Firebase 的大多数功能。这些插件都是由 Flutter 团队维护的官方集成插件:

  • firebase_admob 提供 Firebase AdMob 功能

  • firebase_analytics 提供 Firebase Analytics 功能

  • firebase_auth 提供 Firebase Auth 功能

  • firebase_database 提供 Firebase RTDB 功能

  • firebase_storage 提供 Firebase Cloud Storage 功能

  • firebase_messaging 提供 Firebase Messaging (FCM) 功能

  • flutter_firebase_ui 提供快速的 Firebase Auth 集成功能 (Facebook, Google, Twitter 和 email)

  • cloud_firestore 提供 Firebase Cloud Firestore 功能

你可以在 Pub(https://pub.dev/flutter) 网站上查找一些官方插件没有直接支持的功能的第三方 Firebase 插件。

12.5 如何创建自己的自定义原生集成插件?

如果有 Flutter 官方或社区第三方插件没有涵盖的平台特定的功能,你可以根据开发包和插件页面创建自己的插件。

Flutter 的插件架构,简而言之,和 Android 中的事件总线的使用非常相似:你发送一个消息,并让接受者处理并返回一个结果给你。在这种情况下,接受者是运行在 Android 或 iOS 原生端的代码。

12.6 如何在 Flutter 应用中使用 NDK?

如果你在现有的 Android 应用中使用 NDK,并且希望你的 Flutter 应用可以利用你的 native 库,这可以通过创建一个自定义插件实现。

你的自定义插件首先和你的 Android 应用通信,Android 应用会通过 JNI 调用 native 方法。一旦有返回值,就可以向 Flutter 发送回一个消息并渲染结果。

暂时还不支持从 Flutter 中直接调用 native 代码。

十三、主题

13.1 如何对应用使用主题?

Flutter 提供开箱即用的优美的 Material Design 实现,可以满足你通常需要的各种样式和主题的需求。不同于 Android 中你在 XML 文件中定义主题并在 AndroidManifest.xml 中将其赋值给你的应用, Flutter 中是在顶层 Widget 上声明主题。

为了在应用中利用好 Material 组件,你可以在应用中声明一个顶层 Widget MeterialApp 作为入口。 MaterialApp 是一个包装了一系列 Widget 的为你给予便利的 Widget,而这些 Widget 通常是实现 Material Design 的应用所必须的。它基于 WidgetsApp 并添加了 Material 相关的功能。

你也可以使用 WidgetApp 作为应用的 Widget,它会提供一些相同的功能,但是不如 MaterialApp 提供的功能丰富。

如果要自定义任意子组件的颜色或者样式,给 MaterialApp Widget 传入一个 ThemeData 对象即可。例如,在下面的代码中,主色调设置为蓝色,文本选中颜色设置为红色。

class SampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        textSelectionColor: Colors.red
      ),
      home: SampleAppPage(),
    );
  }
}

十四、数据库和本地存储

14.1 如何使用 Shared Preferences?

在 Android 中,你可以使用 SharedPreferences API 来存储少量的键值对。

在 Flutter 中,使用 Shared_Preferences 插件实现此功能。这个插件同时包装了 Shared Preferences 和 NSUserDefaults(iOS 平台对应 API)的功能。

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        body: Center(
          child: RaisedButton(
            onPressed: _incrementCounter,
            child: Text('Increment Counter'),
          ),
        ),
      ),
    ),
  );
}

_incrementCounter() async {
  SharedPreferences prefs = await SharedPreferences.getInstance();
  int counter = (prefs.getInt('counter') ?? 0) + 1;
  print('Pressed $counter times.');
  prefs.setInt('counter', counter);
}

14.2 在 Flutter 中如何使用 SQLite?

在 Android 中,你会使用 SQLite 来存储可以通过 SQL 进行查询的结构化数据。

在 Flutter 中,使用 SQFlite 插件实现此功能。

十五、通知

15.1 如何设置推送通知?

在 Android 中,你会使用 Firebase Cloud Messaging 来为应用设置推送通知。

在 Flutter 中,则使用 Firebase_Messaging 插件实现此功能。想要获得更多关于使用 Firebase Cloud Messaging API 的信息,请查阅 firebase_messaging 插件文档。

(完)

▼往期精彩回顾▼
给 Android 开发者的 Flutter 指南(上) 给 iOS 开发者的 Flutter 指南(下)给 iOS 开发者的 Flutter 指南(上) df5bc326e49e6501ad1f79e91e54a581.gif
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值