序言
认识flutter已经到了第三节。我们了解到了flutter的启动入口,weight类别和常见的类型,页面之间的跳转,资源文件的配置,如何实现weight的排列、以及如何和Android互动,下面我们继续已Android视角认识flutter中的细节。
手势检测和触摸事件处理
如何在 Flutter 中给一个组件添加 onClick 监听器?
在安卓系统中,你可以通过调用 "setOnClickListener" 方法,将 onClick(点击事件)附加到按钮等视图上。
在 Flutter 中,有两种添加触摸监听器的方法:
1、如果微件(Widget)支持事件检测,可以向其传递一个函数,并在该函数中进行处理。例如,ElevatedButton 具有 onPressed 参数:
kotlin
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
developer.log('click');
},
child: const Text('Button'),
);
}
2、如果 Widget 不支持事件检测,请将该 widget 包裹在 GestureDetector 中,并向 onTap 参数传递一个函数。
kotlin
class SampleTapApp extends StatelessWidget {
const SampleTapApp({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GestureDetector(
onTap: () {
developer.log('tap');
},
child: const FlutterLogo(size: 200),
),
),
);
}
}
如何处理小部件上的其他手势?
使用 GestureDetector,你可以监听各种各样的手势,例如:
Tap:
onTapDown - 一个可能触发点击操作的指针已在屏幕特定位置接触屏幕。
onTapUp - 一个触发点击操作的指针已在屏幕特定位置停止接触屏幕。
onTap - 点击操作已发生。
onTapCancel - 先前触发onTapDown的指针将不会导致点击操作。


Double tap
onDoubleTap 用户快速连续两次点击了屏幕上的同一位置
Long press
onLongPress 指针长时间与屏幕上的同一位置保持接触。
Vertical drag
onVerticalDragStart - 指针已接触屏幕并可能开始垂直移动。
onVerticalDragUpdate - 与屏幕接触的指针在垂直方向上进一步移动。
onVerticalDragEnd - 先前与屏幕接触并垂直移动的指针已不再接触屏幕,且在停止接触时具有特定速度。
Horizontal drag
onHorizontalDragStart - 指针已接触屏幕,并可能开始水平移动。
onHorizontalDragUpdate - 与屏幕接触的指针在水平方向上已进一步移动。
onHorizontalDragEnd - 先前与屏幕接触并水平移动的指针已不再接触屏幕,且在停止接触时具有特定速度。
下面是一个双击和旋转动画结合的例子
kotlin
class SampleApp extends StatefulWidget {
const SampleApp({super.key});
@override
State<SampleApp> createState() => _SampleAppState();
}
class _SampleAppState extends State<SampleApp>
with SingleTickerProviderStateMixin {
late AnimationController controller;
late CurvedAnimation curve;
@override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 2000),
);
curve = CurvedAnimation(parent: controller, curve: Curves.easeIn);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GestureDetector(
onDoubleTap: () {
if (controller.isCompleted) {
controller.reverse();
} else {
controller.forward();
}
},
child: RotationTransition(
turns: curve,
child: const FlutterLogo(size: 200),
),
),
),
);
}
}
ListView
Flutter 中 ListView 的替代方案是什么?
Flutter 中与 ListView 等效的组件依然是ListView!
在 Android 的 ListView 中,你需要创建一个适配器并将其传入 ListView,ListView 会根据适配器返回的内容来渲染每一行。不过,你必须确保对行进行回收,否则会出现各种奇怪的视觉故障和内存问题。
由于 Flutter 的不可变组件模式,你可以将一个组件列表传递给 ListView,而 Flutter 会负责确保滚动既快速又流畅。
kotlin
import 'package:flutter/material.dart';
void main() {
runApp(const SampleApp());
}
class SampleApp extends StatelessWidget {
const SampleApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
const SampleAppPage({super.key});
@override
State<SampleAppPage> createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Sample App')),
body: ListView(children: _getListData()),
);
}
List<Widget> _getListData() {
List<Widget> widgets = [];
for (int i = 0; i < 100; i++) {
widgets.add(
Padding(padding: const EdgeInsets.all(10), child: Text('Row $i')),
);
}
return widgets;
}
}
如何给item设置点击
在 Android 中,ListView 有一个用于查明哪个条目被点击的方法,即 "onItemClickListener"。在 Flutter 中,使用传入的小部件所提供的触摸处理。
kotlin
import 'dart:developer' as developer;
import 'package:flutter/material.dart';
void main() {
runApp(const SampleApp());
}
class SampleApp extends StatelessWidget {
const SampleApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
const SampleAppPage({super.key});
@override
State<SampleAppPage> createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Sample App')),
body: ListView(children: _getListData()),
);
}
List<Widget> _getListData() {
List<Widget> widgets = [];
for (int i = 0; i < 100; i++) {
widgets.add(
GestureDetector(
onTap: () {
developer.log('row tapped');
},
child: Padding(
padding: const EdgeInsets.all(10),
child: Text('Row $i'),
),
),
);
}
return widgets;
}
}
如何动态更新 ListView?
在安卓系统上,你需要更新适配器并调用 notifyDataSetChanged 方法。
在 Flutter 中,如果你要在 setState () 里更新 widget 列表,你会很快发现数据在视觉上并没有发生变化。这是因为当调用 setState () 时,Flutter 渲染引擎会查看 widget 树,判断是否有内容发生了改变。当它检查到你的 ListView 时,会执行一个 == 检查,然后判定这两个 ListView 是相同的。既然没有变化,也就不需要进行更新了。
要以简单的方式更新你的 ListView,请在 setState () 中创建一个新的 List,并将数据从旧列表复制到新列表。虽然这种方法很简单,但不建议用于大型数据集,如下一个示例所示。
kotlin
import 'dart:developer' as developer;
import 'package:flutter/material.dart';
void main() {
runApp(const SampleApp());
}
class SampleApp extends StatelessWidget {
const SampleApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
const SampleAppPage({super.key});
@override
State<SampleAppPage> createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List<Widget> widgets = [];
@override
void initState() {
super.initState();
for (int i = 0; i < 100; i++) {
widgets.add(getRow(i));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Sample App')),
body: ListView(children: widgets),
);
}
Widget getRow(int i) {
return GestureDetector(
onTap: () {
setState(() {
widgets = List.from(widgets);
widgets.add(getRow(widgets.length));
developer.log('row $i');
});
},
child: Padding(padding: const EdgeInsets.all(10), child: Text('Row $i')),
);
}
}
推荐的、高效且有效的构建列表的方法是使用 ListView.Builder。当你有一个动态列表或包含大量数据的列表时,这种方法非常好用。它本质上相当于 Android 上的 RecyclerView,会自动为你回收列表元素:
kotlin
import 'dart:developer' as developer;
import 'package:flutter/material.dart';
void main() {
runApp(const SampleApp());
}
class SampleApp extends StatelessWidget {
const SampleApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
const SampleAppPage({super.key});
@override
State<SampleAppPage> createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List<Widget> widgets = [];
@override
void initState() {
super.initState();
for (int i = 0; i < 100; i++) {
widgets.add(getRow(i));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Sample App')),
body: ListView.builder(
itemCount: widgets.length,
itemBuilder: (context, position) {
return getRow(position);
},
),
);
}
Widget getRow(int i) {
return GestureDetector(
onTap: () {
setState(() {
widgets.add(getRow(widgets.length));
developer.log('row $i');
});
},
child: Padding(padding: const EdgeInsets.all(10), child: Text('Row $i')),
);
}
}
不要创建 "ListView",而是创建一个 ListView.builder,它接受两个关键参数:列表的初始长度和一个 ItemBuilder 函数。
ItemBuilder 函数类似于 Android 适配器中的 getView 函数,它接收一个位置参数,并返回你希望在该位置渲染的行。
最后,但最重要的是,注意 onTap () 函数不再重新创建列表,而是向列表中添加元素
Text
我如何在我的文本部件上设置自定义字体?
在 Android SDK(截至 Android O 版本)中,你需要创建一个字体资源文件,并将其传入 TextView 的 FontFamily 参数中。
在 Flutter 中,将字体文件放在一个文件夹中,并在 pubspec.yaml 文件中引用它,类似于导入图片的方式。
kotlin
fonts:
- family: MyCustomFont
fonts:
- asset: fonts/MyCustomFont.ttf
- style: italic
kotlin
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Sample App')),
body: const Center(
child: Text(
'This is a custom font text',
style: TextStyle(fontFamily: 'MyCustomFont'),
),
),
);
}
我该如何设置我的文本小部件的样式?
除了字体,你还可以自定义文本组件上的其他样式元素。文本组件的 style 参数接受一个 TextStyle 对象,在该对象中,你可以自定义许多参数,例如:
color
decoration
decorationColor
decorationStyle
fontFamily
fontSize
fontStyle
fontWeight
hashCode
height
inherit
letterSpacing
textBaseline
wordSpacing

效果如下:
