MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Flutter Cupertino组件与iOS原生体验对比

2022-12-303.7k 阅读

Flutter Cupertino 组件简介

Flutter 是谷歌开发的一款跨平台移动应用开发框架,其目标是通过一套代码库在 iOS、Android 等多个平台上构建高性能、高保真的应用程序。Cupertino 组件库是 Flutter 专门为了在 iOS 平台上实现与原生 iOS 应用相似的用户体验而设计的。

Cupertino 组件库的设计理念

Flutter 的 Cupertino 组件库遵循了 iOS 的设计语言和交互规范。例如,在颜色、字体、按钮样式以及各种交互反馈上,都尽力与 iOS 原生应用保持一致。这使得使用 Cupertino 组件开发的应用,在 iOS 设备上能够让用户感受到熟悉的操作体验,减少用户的学习成本。

主要 Cupertino 组件分类

  1. 导航组件:如 CupertinoNavigationBar,它模仿了 iOS 原生的导航栏样式,包括左侧的返回按钮、中间的标题以及右侧可能的操作按钮等。在原生 iOS 开发中,导航栏是用户在应用不同页面间切换的重要交互元素,CupertinoNavigationBar 则在 Flutter 中实现了类似的功能。
  2. 按钮组件CupertinoButton 提供了与 iOS 原生按钮外观和交互一致的按钮样式。例如,按压时的反馈效果与 iOS 原生按钮相似,并且可以设置不同的状态样式。
  3. 模态组件CupertinoAlertDialogCupertinoActionSheet 分别对应 iOS 原生的弹窗和操作菜单。它们在外观和交互上都与 iOS 原生组件高度相似,能够在合适的场景下提供标准的 iOS 风格交互。

代码示例 - CupertinoNavigationBar

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      home: CupertinoPageScaffold(
        navigationBar: CupertinoNavigationBar(
          middle: Text('示例页面'),
          trailing: CupertinoButton(
            child: Text('操作'),
            onPressed: () {
              // 这里添加按钮点击的逻辑
            },
          ),
        ),
        child: Center(
          child: Text('页面内容'),
        ),
      ),
    );
  }
}

在上述代码中,CupertinoApp 是整个应用的入口,CupertinoPageScaffold 提供了一个基本的页面结构。CupertinoNavigationBar 设置了页面的导航栏,中间显示标题,右侧添加了一个操作按钮。

iOS 原生体验剖析

iOS 原生开发的技术栈

iOS 原生应用开发主要使用 Swift 或 Objective - C 语言,基于苹果提供的 UIKit 框架。UIKit 是一个庞大且功能丰富的框架,它涵盖了从视图构建、布局管理到事件处理等一系列与用户界面相关的功能。

iOS 原生导航体验

  1. UINavigationController:这是 iOS 原生导航的核心类。它管理一个视图控制器栈,通过 push 和 pop 操作实现页面的跳转。在原生开发中,当用户点击导航栏上的返回按钮时,UINavigationController 会自动弹出当前视图控制器,返回上一级页面。这种导航方式在 iOS 应用中非常普遍,用户已经习惯了这种流畅的页面切换方式。
  2. 导航栏自定义:iOS 原生开发允许开发者对导航栏进行高度自定义。可以设置导航栏的背景颜色、标题样式、按钮样式等。例如,通过修改 UINavigationBarbarTintColor 属性来改变导航栏的背景颜色,通过 titleTextAttributes 来设置标题的字体、颜色等样式。

iOS 原生按钮交互体验

  1. UIButton:iOS 原生的按钮类。按钮的外观可以通过设置不同的属性来定制,如背景图片、标题颜色、字体等。当按钮被按压时,会有一个默认的反馈效果,例如按钮会轻微缩小,松开后恢复原状。开发者也可以通过自定义 UIButton 的子类,重写一些方法来实现更个性化的交互效果。
  2. 触摸事件处理:iOS 原生开发中,按钮的触摸事件(如点击、长按等)通过添加目标 - 动作对来处理。例如,使用 addTarget(_:action:for:) 方法,将按钮的点击事件与某个方法关联起来,当按钮被点击时,就会执行该方法中的逻辑。

iOS 原生模态交互体验

  1. UIAlertController:用于创建弹窗和操作菜单。可以通过不同的样式(.alert.actionSheet)来创建不同类型的模态视图。弹窗通常用于显示重要信息或提示用户进行确认操作,操作菜单则用于提供一系列的操作选项。
  2. 动画与过渡效果:iOS 原生的模态视图在显示和消失时都有默认的动画效果。例如,弹窗会从底部向上滑动并伴有淡入效果,操作菜单则是从底部向上滑出。这些动画效果为用户提供了流畅且自然的交互体验。

Flutter Cupertino 组件与 iOS 原生体验对比 - 导航

导航栏外观一致性

  1. CupertinoNavigationBar:在外观上,CupertinoNavigationBar 与 iOS 原生的 UINavigationBar 非常相似。它的高度、字体样式以及按钮的布局方式都遵循 iOS 的设计规范。例如,标题默认居中显示,左侧的返回按钮和右侧的操作按钮的样式和位置也与原生导航栏一致。
  2. UINavigationBar:iOS 原生的 UINavigationBar 在不同的 iOS 版本中有一些细微的样式变化,但总体上保持了简洁、统一的风格。其背景颜色、透明度等都可以根据应用的需求进行定制,并且在不同设备上能够自适应显示。

导航逻辑与交互

  1. Flutter 中的导航:在 Flutter 中,使用 Navigator 来管理页面的导航。结合 CupertinoNavigationBar,可以实现类似 iOS 原生的导航体验。例如,通过 Navigator.pushNavigator.pop 方法来实现页面的跳转和返回。不过,Flutter 的导航逻辑相对更灵活一些,它不依赖于视图控制器栈的概念,而是通过路由表来管理页面导航。
  2. iOS 原生导航:iOS 原生基于 UINavigationController 的导航逻辑更加依赖视图控制器栈。当一个视图控制器被 push 到栈顶时,它会显示在屏幕上,而 pop 操作则会将栈顶的视图控制器移除并返回上一级视图控制器。这种基于栈的导航方式在处理复杂的页面层级关系时非常直观,但相对来说灵活性稍差。

代码示例 - 导航对比

Flutter Cupertino 导航

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      initialRoute: '/',
      routes: {
        '/': (context) => HomePage(),
        '/second': (context) => SecondPage(),
      },
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text('首页'),
        trailing: CupertinoButton(
          child: Text('跳转'),
          onPressed: () {
            Navigator.pushNamed(context, '/second');
          },
        ),
      ),
      child: Center(
        child: Text('这是首页'),
      ),
    );
  }
}

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text('第二页'),
      ),
      child: Center(
        child: CupertinoButton(
          child: Text('返回'),
          onPressed: () {
            Navigator.pop(context);
          },
        ),
      ),
    );
  }
}

iOS 原生 Swift 导航

import UIKit

class HomeViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = "首页"
        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "跳转", style:.plain, target: self, action: #selector(goToSecondPage))
    }
    
    @objc func goToSecondPage() {
        let secondVC = SecondViewController()
        navigationController?.pushViewController(secondVC, animated: true)
    }
}

class SecondViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = "第二页"
        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "返回", style:.plain, target: self, action: #selector(goBack))
    }
    
    @objc func goBack() {
        navigationController?.popViewController(animated: true)
    }
}

class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        let navigationController = UINavigationController(rootViewController: HomeViewController())
        window = UIWindow(frame: UIScreen.main.bounds)
        window?.rootViewController = navigationController
        window?.makeKeyAndVisible()
        return true
    }
}

在上述代码对比中,Flutter 通过 Navigator 和路由表实现导航,而 iOS 原生通过 UINavigationController 和视图控制器栈来管理导航,虽然实现方式不同,但都能达到相似的导航效果。

Flutter Cupertino 组件与 iOS 原生体验对比 - 按钮

按钮外观与样式

  1. CupertinoButtonCupertinoButton 的外观模仿了 iOS 原生按钮的样式。默认情况下,它有一个透明的背景,按压时会有一个灰色的遮罩效果,以提供反馈。可以通过设置 color 属性来改变按钮的背景颜色,通过 child 属性设置按钮的文本或图标。
  2. UIButton:iOS 原生的 UIButton 外观定制性非常强。可以通过设置 backgroundImage 属性来为按钮添加背景图片,setTitleColor(_:for:) 方法来设置不同状态下的标题颜色,还可以通过 titleLabel 的属性来设置字体等样式。

按钮交互反馈

  1. Flutter 按钮交互CupertinoButton 按压时的反馈效果与 iOS 原生按钮相似,通过改变透明度或添加遮罩来表示按钮被按下。在 Flutter 中,可以通过 onPressed 属性来定义按钮点击时执行的逻辑,并且可以通过 enabled 属性来控制按钮的可点击状态。
  2. iOS 原生按钮交互:iOS 原生的 UIButton 在按压时除了有默认的缩小反馈效果外,开发者还可以通过重写 touchesBegan(_:with:)touchesEnded(_:with:) 等方法来自定义更复杂的交互反馈,比如添加动画效果等。通过 addTarget(_:action:for:) 方法来绑定按钮点击事件的处理逻辑。

代码示例 - 按钮对比

Flutter Cupertino 按钮

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      home: CupertinoPageScaffold(
        child: Center(
          child: CupertinoButton(
            color: CupertinoColors.activeBlue,
            child: Text('点击我'),
            onPressed: () {
              print('按钮被点击了');
            },
          ),
        ),
      ),
    );
  }
}

iOS 原生 Swift 按钮

import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let button = UIButton(type:.system)
        button.frame = CGRect(x: 100, y: 200, width: 200, height: 50)
        button.setTitle("点击我", for:.normal)
        button.setTitleColor(.blue, for:.normal)
        button.addTarget(self, action: #selector(buttonTapped), for:.touchUpInside)
        view.addSubview(button)
    }
    
    @objc func buttonTapped() {
        print("按钮被点击了")
    }
}

从代码对比可以看出,Flutter 的 CupertinoButton 通过简单的属性设置来实现按钮的外观和交互逻辑,而 iOS 原生的 UIButton 则需要更多的代码来设置外观和绑定事件。

Flutter Cupertino 组件与 iOS 原生体验对比 - 模态

模态视图外观

  1. CupertinoAlertDialogCupertinoActionSheetCupertinoAlertDialog 的外观与 iOS 原生的弹窗相似,有一个白色的背景,标题和内容区域清晰分开,按钮排列在底部。CupertinoActionSheet 模仿了 iOS 原生操作菜单的样式,从底部向上滑出,按钮有明显的分割线。
  2. UIAlertController:iOS 原生的 UIAlertController 在外观上同样具有简洁、直观的特点。弹窗的标题、消息和按钮布局符合 iOS 的设计规范,操作菜单的样式也在不同 iOS 版本中保持相对稳定,并且可以根据应用主题进行一定程度的自定义。

模态视图交互与动画

  1. Flutter 模态交互:在 Flutter 中,显示 CupertinoAlertDialog 通常使用 showCupertinoDialog 方法,而显示 CupertinoActionSheet 使用 showCupertinoModalPopup 方法。它们在显示和消失时都有与 iOS 原生相似的动画效果,例如 CupertinoAlertDialog 淡入淡出,CupertinoActionSheet 从底部滑出和滑入。
  2. iOS 原生模态交互:iOS 原生通过 present(_:animated:completion:) 方法来显示 UIAlertController,动画效果是系统默认的,开发者也可以通过自定义过渡动画来实现更个性化的效果。在用户操作模态视图时,如点击按钮关闭弹窗或操作菜单,原生系统会提供流畅的交互反馈。

代码示例 - 模态对比

Flutter Cupertino 模态

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      home: CupertinoPageScaffold(
        child: Center(
          child: CupertinoButton(
            child: Text('显示弹窗'),
            onPressed: () {
              showCupertinoDialog(
                context: context,
                builder: (context) {
                  return CupertinoAlertDialog(
                    title: Text('提示'),
                    content: Text('这是一个弹窗'),
                    actions: [
                      CupertinoDialogAction(
                        child: Text('确定'),
                        onPressed: () {
                          Navigator.pop(context);
                        },
                      ),
                    ],
                  );
                },
              );
            },
          ),
        ),
      ),
    );
  }
}

iOS 原生 Swift 模态

import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let button = UIButton(type:.system)
        button.frame = CGRect(x: 100, y: 200, width: 200, height: 50)
        button.setTitle("显示弹窗", for:.normal)
        button.setTitleColor(.blue, for:.normal)
        button.addTarget(self, action: #selector(showAlert), for:.touchUpInside)
        view.addSubview(button)
    }
    
    @objc func showAlert() {
        let alertController = UIAlertController(title: "提示", message: "这是一个弹窗", preferredStyle:.alert)
        let okAction = UIAlertAction(title: "确定", style:.default, handler: nil)
        alertController.addAction(okAction)
        present(alertController, animated: true, completion: nil)
    }
}

通过上述代码对比,可以看到 Flutter 和 iOS 原生在实现模态视图上都有各自的方式,但都能为用户提供相似的交互体验。

性能与资源占用对比

Flutter Cupertino 组件性能

  1. 渲染性能:Flutter 使用自己的渲染引擎,采用基于 Skia 的图形渲染。在使用 Cupertino 组件时,由于其采用了高效的渲染机制,能够快速地将界面元素绘制到屏幕上。例如,在页面切换或按钮交互时,动画过渡非常流畅,这得益于 Flutter 的自绘机制,它可以直接控制像素级别的绘制,避免了传统跨平台开发中因系统原生绘制机制带来的性能问题。
  2. 资源占用:Flutter 应用在资源占用方面相对较为优化。虽然 Flutter 应用的初始包体可能比原生应用稍大,这是因为它包含了 Dart 运行时和 Skia 渲染引擎等。但在运行过程中,通过其高效的内存管理和资源复用机制,如对象池技术等,能够有效地减少内存占用。特别是在使用 Cupertino 组件时,由于组件的复用性较高,进一步降低了资源的消耗。

iOS 原生性能

  1. 渲染性能:iOS 原生应用基于 UIKit 框架,使用系统原生的图形渲染机制。在渲染性能方面,iOS 原生应用经过多年的优化,表现非常出色。例如,在处理复杂的动画和视图切换时,能够利用硬件加速等技术,提供流畅的用户体验。不过,在某些情况下,如在复杂视图层次结构中进行频繁的视图更新时,可能会因为 UIKit 的渲染机制问题而出现性能瓶颈。
  2. 资源占用:iOS 原生应用在资源占用上有较好的控制。苹果对应用的资源使用有严格的规范和限制,这促使开发者优化应用的资源占用。原生应用可以直接调用系统底层资源,在内存管理和资源调度方面具有一定的优势。但如果开发者在代码编写过程中没有进行良好的优化,如频繁创建和销毁对象,也可能导致较高的资源消耗。

性能与资源占用测试对比

为了更直观地对比 Flutter Cupertino 组件和 iOS 原生应用在性能与资源占用方面的差异,我们可以进行以下简单测试:

  1. 测试场景:创建一个包含多个页面、按钮、弹窗和操作菜单的应用场景,模拟用户在不同页面间频繁切换、点击按钮、显示和关闭模态视图等操作。
  2. 测试工具:在 iOS 原生开发中,可以使用 Instruments 工具来监测应用的性能指标,如 CPU 使用率、内存占用等。在 Flutter 开发中,可以使用 Flutter DevTools 来监测应用的性能,包括帧率、内存使用情况等。
  3. 测试结果分析:经过多次测试发现,在简单场景下,Flutter Cupertino 组件和 iOS 原生应用的性能和资源占用差异不大,都能提供流畅的用户体验。但在复杂场景下,如同时显示多个复杂视图和进行频繁的动画操作时,iOS 原生应用在 CPU 使用率方面略低,这可能得益于其对硬件加速的更好利用。而 Flutter 应用在内存占用方面表现相对稳定,这得益于其高效的内存管理机制。不过,这些差异在实际应用中对于大多数用户来说可能并不明显,因为现代设备的性能都相对较强,能够很好地支持这两种开发方式。

开发效率与维护成本对比

Flutter Cupertino 组件开发效率

  1. 代码复用:Flutter 的一大优势就是代码复用性高。使用 Cupertino 组件开发 iOS 应用时,大部分代码可以在 Android 等其他平台上复用,只需要针对不同平台进行少量的适配。这大大减少了开发工作量,提高了开发效率。例如,一个基于 Cupertino 组件开发的页面,只需要修改一些样式相关的代码,就可以在 Android 平台上使用 Material 组件库进行显示。
  2. 快速迭代:Flutter 提供了热重载功能,在开发过程中,开发者可以快速看到代码修改后的效果,无需重新编译整个应用。这使得开发过程更加流畅,能够快速迭代和优化应用的界面和功能。特别是在使用 Cupertino 组件时,通过热重载可以即时看到组件样式和交互的变化,提高了开发效率。

iOS 原生开发效率

  1. 平台特定开发:iOS 原生开发需要使用 Swift 或 Objective - C 语言,并且基于 UIKit 框架。由于 iOS 平台的独特性,开发过程中需要针对 iOS 的特性进行大量的代码编写。虽然 UIKit 框架提供了丰富的功能,但开发不同平台的应用需要重新编写大部分代码,开发效率相对较低。
  2. 编译时间:iOS 原生应用在每次修改代码后,需要进行编译才能看到效果。对于大型项目,编译时间可能较长,这在一定程度上影响了开发效率。虽然 Xcode 也在不断优化编译速度,但与 Flutter 的热重载功能相比,在快速迭代开发方面仍有差距。

维护成本对比

  1. Flutter 维护成本:由于 Flutter 使用一套代码库开发多个平台的应用,在维护过程中,只需要修改一处代码,就可以在多个平台上生效。这降低了维护成本,特别是在修复 bug 和更新功能时,能够减少工作量。同时,Flutter 社区活跃,有丰富的文档和开源库,开发者在遇到问题时能够较容易地找到解决方案。
  2. iOS 原生维护成本:iOS 原生应用维护需要熟悉 Swift 或 Objective - C 语言以及 UIKit 框架。随着 iOS 系统的不断更新,应用可能需要进行适配,这增加了维护的工作量。而且不同版本的 iOS 系统可能存在兼容性问题,需要开发者花费更多的精力去测试和修复。此外,由于 iOS 原生开发与其他平台开发差异较大,维护不同平台的应用需要不同的技术团队,进一步增加了维护成本。

综上所述,Flutter Cupertino 组件在实现与 iOS 原生相似体验的同时,在开发效率和维护成本方面具有一定优势,而 iOS 原生开发在某些性能细节和对平台特性的深度利用上有其独特之处。开发者可以根据项目的具体需求和目标来选择合适的开发方式。