一、先搞懂两个组件库的核心区别
很多刚接触Flutter的开发者,最头疼的就是同一个App在安卓和苹果手机上显示不一样——要么安卓用了苹果的圆角按钮,要么苹果用了安卓的Material风格卡片,显得特别违和。其实Flutter自带的两个组件库就是专门解决这个问题的:Material是安卓的官方设计规范组件库,Cupertino是苹果的官方设计规范组件库,两者的核心区别全在细节里。 比如Material的按钮默认是有阴影的,点击会有按压反馈;Cupertino的按钮默认是扁平的,点击会有缩放反馈;Material的导航栏可以是纯色或渐变,Cupertino的导航栏默认是半透明的毛玻璃效果;甚至连文本的行高、图标样式都有细微差别,这些细节凑在一起,才是两个平台的原生质感。
二、自动适配还是手动指定?两种使用方式的场景
很多人会问:我能不能让App自动根据手机系统选组件库?当然可以,Flutter有专门的适配方法,但手动指定也有适用场景,得根据自己的需求选。
2.1 自动适配:适合全平台通用的App
自动适配的核心是用Platform判断当前系统,然后动态返回对应平台的组件。比如一个按钮,安卓用ElevatedButton,苹果用CupertinoButton,这样不用改代码就能适配两个平台。
先给大家放一个完整的自动适配按钮示例,所有示例都用Flutter 3.16+版本(这个版本的组件库最稳定,兼容大部分项目):
// 技术栈:Flutter 3.16+
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'dart:io'; // 用于判断当前平台
class AutoAdaptButton extends StatelessWidget {
final String text; // 按钮显示的文本
final VoidCallback onPressed; // 按钮点击事件
const AutoAdaptButton({
super.key,
required this.text,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
// 判断当前系统:安卓用Material按钮,苹果用Cupertino按钮
if (Platform.isAndroid) {
return ElevatedButton(
onPressed: onPressed,
child: Text(text),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
);
} else {
return CupertinoButton(
onPressed: onPressed,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
borderRadius: BorderRadius.circular(8),
child: Text(text),
);
}
}
}
这个按钮的好处是不用管用户用什么手机,自动适配,适合那种只做一个包,同时发布到安卓应用市场和苹果App Store的通用App。
2.2 手动指定:适合需要严格控制风格的场景
有些场景下自动适配反而会出问题,比如你做的是安卓专属App,或者苹果专属App,甚至是同一个App里有固定的风格要求(比如安卓端的活动页必须用Material的大卡片),这时候就可以手动指定组件库。 比如手动指定一个Material风格的卡片,不管在什么平台都显示安卓风格:
// 技术栈:Flutter 3.16+
import 'package:flutter/material.dart';
class ManualMaterialCard extends StatelessWidget {
final String title;
final String content;
const ManualMaterialCard({
super.key,
required this.title,
required this.content,
});
@override
Widget build(BuildContext context) {
// 手动用Material的Card组件,不管当前系统是什么
return Card(
elevation: 4, // Material卡片特有的阴影效果
margin: const EdgeInsets.all(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
Text(content),
],
),
),
);
}
}
同样,手动指定Cupertino风格的导航栏也很简单,不管在安卓还是苹果手机上,都显示苹果的半透明导航栏:
// 技术栈:Flutter 3.16+
import 'package:flutter/cupertino.dart';
class ManualCupertinoNav extends StatelessWidget {
final Widget child;
const ManualCupertinoNav({
super.key,
required this.child,
});
@override
Widget build(BuildContext context) {
// 手动用Cupertino的导航栏组件
return CupertinoPageScaffold(
navigationBar: const CupertinoNavigationBar(
middle: Text('我的页面'),
// Cupertino导航栏默认带半透明毛玻璃效果,Material没有这个属性
),
child: child,
);
}
}
三、核心组件的适配实战:导航栏、按钮、弹窗
导航栏、按钮、弹窗是App里最常用的三个组件,也是最容易出现风格违和的地方,下面给大家放完整的适配示例。
3.1 导航栏适配
导航栏是App的骨架,适配不好会特别显眼。下面这个完整的页面,会根据系统自动切换Material和Cupertino的导航栏:
// 技术栈:Flutter 3.16+
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'dart:io';
class AdaptHomePage extends StatelessWidget {
const AdaptHomePage({super.key});
@override
Widget build(BuildContext context) {
// 安卓用MaterialApp的Scaffold,苹果用CupertinoPageScaffold
if (Platform.isAndroid) {
return Scaffold(
appBar: AppBar(
title: const Text('首页'),
// Material导航栏默认是固定颜色,没有毛玻璃效果
),
body: const Center(child: Text('安卓首页')),
);
} else {
return CupertinoPageScaffold(
navigationBar: const CupertinoNavigationBar(
middle: Text('首页'),
// Cupertino导航栏默认带毛玻璃效果,符合苹果规范
),
child: const Center(child: Text('苹果首页')),
);
}
}
}
3.2 弹窗适配
弹窗是用户交互最多的组件之一,风格不对会显得很奇怪。下面这个适配弹窗的示例,安卓用Material的AlertDialog,苹果用CupertinoAlertDialog:
// 技术栈:Flutter 3.16+
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'dart:io';
class AdaptDialog {
// 静态方法,直接调用就能弹出适配的弹窗
static void show(BuildContext context, String title, String content) {
if (Platform.isAndroid) {
// 安卓用Material弹窗
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text(content),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('确定'),
),
],
),
);
} else {
// 苹果用Cupertino弹窗
showCupertinoDialog(
context: context,
builder: (context) => CupertinoAlertDialog(
title: Text(title),
content: Text(content),
actions: [
CupertinoDialogAction(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
CupertinoDialogAction(
onPressed: () => Navigator.pop(context),
child: const Text('确定'),
),
],
),
);
}
}
}
这个弹窗的用法很简单,在任何地方调用AdaptDialog.show(context, '提示', '确定要退出吗?'),就会弹出对应平台的弹窗。
四、技术优缺点和注意事项
4.1 技术优缺点
先说说自动适配的优点:不用写两套代码,维护成本低,同一个包就能适配两个平台,适合大部分通用App;缺点是有些组件的属性不通用,比如Material的elevation(阴影)Cupertino没有,Cupertino的backgroundColor是半透明的,Material的是纯色,得自己额外处理。
手动指定的优点是风格完全可控,适合做专属平台的App,或者需要严格统一风格的场景;缺点是如果要同时适配两个平台,得写两套代码,维护成本高,比如改一个按钮的颜色,得同时改Material和Cupertino的代码。
4.2 注意事项
第一,不要混用组件库的核心容器,比如不要在Material的Scaffold里放Cupertino的导航栏,也不要在Cupertino的PageScaffold里放Material的AppBar,会出现布局错乱;第二,有些组件的交互逻辑不一样,比如Material的按钮长按会有波纹,Cupertino的按钮长按会有缩放,不要为了统一风格改原生交互,否则会让用户觉得不习惯;第三,判断平台的时候不要只用Platform,如果是Web端,Platform会报错,得加判断,比如kIsWeb(这个是Flutter自带的常量,用来判断是不是Web端),比如可以改成if (kIsWeb) { /* Web端的处理 */ } else if (Platform.isAndroid) { /* 安卓处理 */ } else { /* 苹果处理 */ };第四,不要过度自定义组件,比如把Cupertino的按钮改成Material的样式,反而会失去原生质感,Flutter的组件库已经经过官方优化,尽量用默认样式。
五、应用场景总结
适合用自动适配的场景:同时发布到安卓和苹果的通用App,比如电商App、社交App、工具类App,用户量比较大,需要覆盖两个平台;适合用手动指定的场景:安卓专属App(比如国内的很多游戏、企业内部的安卓App)、苹果专属App(比如海外的一些只在App Store发布的App)、同一个App里有特殊风格要求的页面(比如活动页、专题页,需要固定用Material或Cupertino的风格)。
六、文章总结
用Flutter的Material和Cupertino组件库做适配,核心就是搞懂两个组件库的区别,根据自己的需求选自动适配还是手动指定,常用的导航栏、按钮、弹窗等组件的适配方法掌握了,大部分App的UI都能搞定。不管用哪种方式,都要尽量符合两个平台的原生规范,不要为了统一风格改原生交互,这样做出来的App才会让用户觉得自然,不会有违和感。
评论
围绕“怎样利用Flutter的Material和Cupertino组件库打造符合两大平台设计规范的UI?”参与讨论