一、先搞懂两个组件库的核心区别

很多刚接触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才会让用户觉得自然,不会有违和感。