Material & Cupertino trong Flutter

MaterialApp vs CupertinoApp in Flutter — What I Actually Use in Real Apps | by Ankit Mehra | Medium

1. Material

Material Design là hệ thống thiết kế giao diện người dùng (UI Design System) do Google phát triển. Material cung cấp các nguyên tắc thiết kế, màu sắc, kiểu chữ, bố cục, hiệu ứng chuyển động và các thành phần giao diện nhằm giúp ứng dụng có giao diện nhất quán, hiện đại và dễ sử dụng.

Trong Flutter, Material được hỗ trợ thông qua thư viện material.dart. Thư viện này cung cấp nhiều Widget có sẵn như Scaffold, AppBar, ElevatedButton, FloatingActionButton, Drawer, Card, TextField và nhiều thành phần khác.

Material thường được sử dụng để xây dựng ứng dụng Android, ứng dụng đa nền tảng hoặc những ứng dụng muốn có giao diện theo phong cách Material Design.

Ví dụ:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Material Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.blue,
        ),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Material Design'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            debugPrint('Button clicked');
          },
          child: const Text('Click me'),
        ),
      ),
    );
  }
}

Trong ví dụ trên, MaterialApp thiết lập môi trường ứng dụng theo Material Design. Scaffold tạo cấu trúc trang, AppBar tạo thanh tiêu đề và ElevatedButton tạo nút bấm có kiểu dáng Material.

2. Các thành phần chính của Material

2.1. MaterialApp

MaterialApp là Widget cấp cao thường được sử dụng làm gốc cho ứng dụng Flutter theo phong cách Material Design.

Nó cung cấp các chức năng như cấu hình giao diện tổng thể, quản lý theme, điều hướng giữa các màn hình, ngôn ngữ và một số thiết lập chung của ứng dụng.

Các thuộc tính phổ biến:

Thuộc tính Ý nghĩa
title Tên ứng dụng
home Màn hình mặc định khi mở ứng dụng
theme Theme sáng hoặc theme chính của ứng dụng
darkTheme Theme tối
themeMode Chọn theme sáng, tối hoặc theo hệ thống
routes Khai báo các tuyến đường điều hướng
initialRoute Tuyến đường khởi đầu
debugShowCheckedModeBanner Hiển thị hoặc ẩn nhãn DEBUG
locale Thiết lập ngôn ngữ và khu vực

Ví dụ:

MaterialApp(
  title: 'My App',
  debugShowCheckedModeBanner: false,
  theme: ThemeData(
    colorScheme: ColorScheme.fromSeed(
      seedColor: Colors.green,
    ),
  ),
  home: const HomePage(),
);

MaterialApp không trực tiếp tạo toàn bộ giao diện của một trang. Nó thiết lập môi trường để các Widget bên trong có thể sử dụng những tính năng của Material.

2.2. Scaffold

Scaffold cung cấp cấu trúc bố cục cơ bản cho một màn hình Material. Nó giúp tổ chức các khu vực như thanh ứng dụng, nội dung chính, thanh điều hướng dưới cùng và nút hành động nổi.

Các thuộc tính thường dùng:

Thuộc tính Ý nghĩa
appBar Thanh tiêu đề phía trên
body Nội dung chính
floatingActionButton Nút hành động nổi
drawer Menu trượt từ cạnh màn hình
bottomNavigationBar Thanh điều hướng phía dưới
backgroundColor Màu nền của màn hình
persistentFooterButtons Các nút cố định ở cuối màn hình

Ví dụ:

Scaffold(
  appBar: AppBar(
    title: const Text('Trang chủ'),
  ),
  body: const Center(
    child: Text('Xin chào Flutter'),
  ),
  floatingActionButton: FloatingActionButton(
    onPressed: () {},
    child: const Icon(Icons.add),
  ),
);

Scaffold giúp lập trình viên không cần tự xây dựng lại những khu vực giao diện phổ biến trên mỗi màn hình.

2.3. AppBar

AppBar là thanh ứng dụng thường nằm ở đầu màn hình. Nó thường chứa tiêu đề, nút quay lại, nút mở menu và các hành động liên quan đến màn hình hiện tại.

Các thuộc tính phổ biến:

Thuộc tính Ý nghĩa
title Tiêu đề
leading Widget nằm ở đầu thanh
actions Danh sách hành động ở cuối thanh
backgroundColor Màu nền
foregroundColor Màu của nội dung phía trước
centerTitle Căn giữa tiêu đề
elevation Độ nổi và bóng đổ

Ví dụ:

AppBar(
  title: const Text('Sản phẩm'),
  centerTitle: true,
  actions: [
    IconButton(
      onPressed: () {},
      icon: const Icon(Icons.search),
    ),
    IconButton(
      onPressed: () {},
      icon: const Icon(Icons.shopping_cart),
    ),
  ],
);

2.4. Các loại Button trong Material

Material cung cấp nhiều loại nút bấm phù hợp với từng mức độ ưu tiên của hành động.

Widget Đặc điểm Trường hợp sử dụng
ElevatedButton Nút có nền nổi bật Lưu thông tin, xác nhận
FilledButton Nút có nền màu đặc Hành động chính
OutlinedButton Nút có viền Hành động phụ
TextButton Nút dạng văn bản Hủy, xem thêm
IconButton Nút chứa biểu tượng Tìm kiếm, chỉnh sửa
FloatingActionButton Nút hành động nổi Thêm sản phẩm, tạo mới

Ví dụ:

Column(
  mainAxisSize: MainAxisSize.min,
  children: [
    ElevatedButton(
      onPressed: () {},
      child: const Text('Elevated Button'),
    ),
    FilledButton(
      onPressed: () {},
      child: const Text('Filled Button'),
    ),
    OutlinedButton(
      onPressed: () {},
      child: const Text('Outlined Button'),
    ),
    TextButton(
      onPressed: () {},
      child: const Text('Text Button'),
    ),
  ],
);

Việc lựa chọn loại nút phù hợp giúp người dùng nhận biết đâu là hành động chính, đâu là hành động phụ.

2.5. TextField

TextField là Widget cho phép người dùng nhập dữ liệu như tên, email, mật khẩu hoặc từ khóa tìm kiếm.

Một số thuộc tính quan trọng:

Thuộc tính Ý nghĩa
controller Quản lý và đọc nội dung nhập
decoration Thiết lập nhãn, gợi ý, viền và biểu tượng
keyboardType Loại bàn phím
obscureText Ẩn nội dung, thường dùng cho mật khẩu
onChanged Xử lý khi nội dung thay đổi
onSubmitted Xử lý khi người dùng gửi dữ liệu

Ví dụ:

TextField(
  decoration: const InputDecoration(
    labelText: 'Email',
    hintText: 'Nhập email của bạn',
    prefixIcon: Icon(Icons.email),
    border: OutlineInputBorder(),
  ),
);

TextField có thể được kết hợp với Form, TextFormField và các trình kiểm tra dữ liệu để xây dựng biểu mẫu đăng nhập, đăng ký hoặc nhập thông tin sản phẩm.

2.6. Card

Card là Widget dùng để nhóm những nội dung có liên quan trong một khu vực giao diện. Card thường có màu nền, góc bo tròn và hiệu ứng đổ bóng tùy cấu hình.

Card phù hợp với danh sách sản phẩm, thông tin người dùng, bài viết, thống kê hoặc các mục trong dashboard quản trị.

Ví dụ:

Card(
  child: Padding(
    padding: const EdgeInsets.all(16),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          'Laptop Gaming',
          style: TextStyle(
            fontSize: 18,
            fontWeight: FontWeight.bold,
          ),
        ),
        const SizedBox(height: 8),
        const Text('Giá: 25.000.000 VNĐ'),
        const SizedBox(height: 8),
        FilledButton(
          onPressed: () {},
          child: const Text('Xem chi tiết'),
        ),
      ],
    ),
  ),
);

2.7. ThemeData

ThemeData là đối tượng cấu hình phong cách chung cho ứng dụng Material. Thay vì đặt màu sắc, kiểu chữ và hình dạng riêng lẻ cho từng Widget, lập trình viên có thể khai báo theme để tái sử dụng.

Ví dụ:

MaterialApp(
  theme: ThemeData(
    useMaterial3: true,
    colorScheme: ColorScheme.fromSeed(
      seedColor: Colors.deepPurple,
    ),
    appBarTheme: const AppBarTheme(
      centerTitle: true,
    ),
    inputDecorationTheme: const InputDecorationTheme(
      border: OutlineInputBorder(),
    ),
  ),
  home: const HomePage(),
);

Lợi ích của ThemeData:

  • Giúp giao diện nhất quán giữa các màn hình.

  • Giảm việc khai báo lặp lại màu sắc và kiểu chữ.

  • Dễ dàng thay đổi giao diện toàn bộ ứng dụng.

  • Hỗ trợ xây dựng theme sáng và theme tối.

  • Giúp việc bảo trì, mở rộng dự án thuận tiện hơn.

Material 3 là phiên bản hệ thống thiết kế Material hiện đại. Trong Flutter, có thể bật bằng useMaterial3: true; đây cũng là giá trị mặc định trong các phiên bản Flutter hiện đại.

3. Cupertino

Cupertino là bộ Widget trong Flutter được thiết kế theo phong cách giao diện của Apple, đặc biệt là iOS. Cupertino hướng đến việc mô phỏng cách trình bày, điều hướng và tương tác quen thuộc của ứng dụng trên iPhone và iPad.

Thư viện này được sử dụng thông qua cupertino.dart:

import 'package:flutter/cupertino.dart';

Cupertino cung cấp những thành phần như CupertinoApp, CupertinoPageScaffold, CupertinoNavigationBar, CupertinoButton, CupertinoTextField, CupertinoSwitch và CupertinoTabScaffold.

Các Widget này có kiểu dáng, hiệu ứng và hành vi tương tác gần với giao diện iOS hơn so với những Widget Material tương ứng.

Cupertino đặc biệt phù hợp khi phát triển ứng dụng dành cho người dùng iPhone, khi muốn giao diện tuân theo phong cách Apple hoặc khi cần tạo trải nghiệm nhất quán với các ứng dụng iOS quen thuộc.

Ví dụ:

import 'package:flutter/cupertino.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: const Text('Cupertino UI'),
      ),
      child: Center(
        child: CupertinoButton.filled(
          onPressed: () {},
          child: const Text('Click me'),
        ),
      ),
    );
  }
}

Trong ví dụ trên, CupertinoApp thiết lập môi trường ứng dụng theo phong cách iOS. CupertinoPageScaffold tạo cấu trúc màn hình, CupertinoNavigationBar hiển thị thanh điều hướng và CupertinoButton.filled tạo nút bấm theo phong cách Apple.

4. Các thành phần chính của Cupertino

4.1. CupertinoApp

CupertinoApp là Widget gốc thường dùng để thiết lập một ứng dụng theo phong cách iOS.

Nó cung cấp cấu hình điều hướng, theme, ngôn ngữ và những thiết lập chung cho giao diện Cupertino.

Các thuộc tính phổ biến:

Thuộc tính Ý nghĩa
title Tên ứng dụng
home Màn hình mặc định
theme Theme của ứng dụng Cupertino
routes Các tuyến đường điều hướng
initialRoute Tuyến đường khởi đầu
locale Ngôn ngữ và khu vực
debugShowCheckedModeBanner Hiển thị hoặc ẩn nhãn DEBUG

Ví dụ:

CupertinoApp(
  title: 'My iOS App',
  theme: const CupertinoThemeData(
    primaryColor: CupertinoColors.systemBlue,
  ),
  home: const HomePage(),
);

CupertinoApp đóng vai trò tương tự MaterialApp, nhưng tập trung vào các quy ước giao diện của nền tảng Apple.

4.2. CupertinoPageScaffold

CupertinoPageScaffold là Widget tạo cấu trúc cơ bản cho một trang theo phong cách iOS.

Các thuộc tính phổ biến:

Thuộc tính Ý nghĩa
navigationBar Thanh điều hướng phía trên
child Nội dung chính của màn hình
backgroundColor Màu nền
resizeToAvoidBottomInset Điều chỉnh bố cục khi bàn phím xuất hiện

Ví dụ:

CupertinoPageScaffold(
  navigationBar: const CupertinoNavigationBar(
    middle: Text('Cài đặt'),
  ),
  child: SafeArea(
    child: Center(
      child: Text(
        'Nội dung trang cài đặt',
        style: TextStyle(fontSize: 18),
      ),
    ),
  ),
);

Widget này giúp tổ chức giao diện trang theo cách quen thuộc của iOS. Khi cần tránh nội dung bị che bởi vùng tai thỏ hoặc khu vực hệ thống, có thể sử dụng thêm SafeArea.

4.3. CupertinoNavigationBar

CupertinoNavigationBar là thanh điều hướng theo phong cách iOS, thường xuất hiện ở phía trên màn hình.

Nó có thể chứa tiêu đề ở giữa, nút quay lại, nút thao tác và các thành phần điều hướng khác.

Ví dụ:

CupertinoNavigationBar(
  leading: CupertinoButton(
    padding: EdgeInsets.zero,
    onPressed: () {},
    child: const Icon(
      CupertinoIcons.back,
    ),
  ),
  middle: const Text('Chi tiết sản phẩm'),
  trailing: CupertinoButton(
    padding: EdgeInsets.zero,
    onPressed: () {},
    child: const Icon(
      CupertinoIcons.heart,
    ),
  ),
);

Trong đó, CupertinoIcons là bộ biểu tượng theo phong cách Apple. Việc sử dụng biểu tượng phù hợp giúp giao diện có cảm giác gần với ứng dụng iOS gốc hơn.

4.4. CupertinoButton

CupertinoButton là Widget tạo nút bấm theo phong cách iOS. Nút thường có cách thể hiện nhẹ nhàng, ít nhấn mạnh hiệu ứng nổi so với một số loại nút Material.

Các thuộc tính phổ biến:

Thuộc tính Ý nghĩa
child Nội dung bên trong nút
onPressed Hàm xử lý khi nhấn
color Màu nền
padding Khoảng cách bên trong
borderRadius Độ bo góc
disabledColor Màu khi nút bị vô hiệu hóa

Ví dụ:

CupertinoButton(
  onPressed: () {},
  child: const Text('Hủy'),
);

Nếu cần một nút có nền màu nổi bật, có thể sử dụng:

CupertinoButton.filled(
  onPressed: () {},
  child: const Text('Đăng nhập'),
);

CupertinoButton thường được sử dụng cho các hành động như xác nhận, hủy, lưu, đăng nhập hoặc chuyển sang màn hình khác.

4.5. CupertinoTextField

CupertinoTextField là Widget nhập liệu được thiết kế theo phong cách iOS.

Widget này thường có cách hiển thị đơn giản, đường viền nhẹ và kiểu trình bày gần với trường nhập liệu trên iPhone.

Ví dụ:

CupertinoTextField(
  placeholder: 'Nhập tên người dùng',
  prefix: const Padding(
    padding: EdgeInsets.only(left: 10),
    child: Icon(CupertinoIcons.person),
  ),
  padding: const EdgeInsets.all(12),
);

Một số thuộc tính phổ biến:

Thuộc tính Ý nghĩa
placeholder Văn bản gợi ý
controller Quản lý nội dung nhập
keyboardType Loại bàn phím
obscureText Ẩn nội dung nhập
onChanged Xử lý khi nội dung thay đổi
padding Khoảng cách bên trong
decoration Trang trí trường nhập liệu

Widget này có thể được sử dụng trong màn hình đăng nhập, tìm kiếm, cài đặt tài khoản hoặc biểu mẫu nhập thông tin.

4.6. CupertinoSwitch

CupertinoSwitch là công tắc bật/tắt theo phong cách iOS. Nó thường được dùng để điều chỉnh các tùy chọn có hai trạng thái, chẳng hạn như bật thông báo, bật chế độ tối hoặc cho phép sử dụng vị trí.

Ví dụ:

class SettingsPage extends StatefulWidget {
  const SettingsPage({super.key});

  @override
  State<SettingsPage> createState() => _SettingsPageState();
}

class _SettingsPageState extends State<SettingsPage> {
  bool notificationsEnabled = true;

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      child: SafeArea(
        child: CupertinoListTile(
          title: const Text('Thông báo'),
          trailing: CupertinoSwitch(
            value: notificationsEnabled,
            onChanged: (value) {
              setState(() {
                notificationsEnabled = value;
              });
            },
          ),
        ),
      ),
    );
  }
}

Ở ví dụ này, value xác định trạng thái hiện tại của công tắc. onChanged nhận trạng thái mới, còn setState() cập nhật giao diện khi người dùng bật hoặc tắt công tắc.

4.7. CupertinoAlertDialog

CupertinoAlertDialog là hộp thoại thông báo theo phong cách iOS. Nó được dùng để thông báo kết quả, yêu cầu xác nhận hoặc cung cấp các lựa chọn cho người dùng.

Ví dụ:

showCupertinoDialog<void>(
  context: context,
  builder: (context) {
    return CupertinoAlertDialog(
      title: const Text('Xác nhận'),
      content: const Text(
        'Bạn có muốn xóa sản phẩm này không?',
      ),
      actions: [
        CupertinoDialogAction(
          onPressed: () {
            Navigator.pop(context);
          },
          child: const Text('Hủy'),
        ),
        CupertinoDialogAction(
          isDestructiveAction: true,
          onPressed: () {
            Navigator.pop(context);
          },
          child: const Text('Xóa'),
        ),
      ],
    );
  },
);

Hộp thoại này giúp ứng dụng thể hiện các thao tác xác nhận theo cách quen thuộc với người dùng iOS.

4.8. CupertinoTabScaffold

CupertinoTabScaffold cung cấp cấu trúc điều hướng bằng các tab ở phía dưới màn hình, tương tự thanh tab thường gặp trong ứng dụng iOS.

Widget này thường kết hợp với CupertinoTabBar để hiển thị các tab như Trang chủ, Tìm kiếm, Yêu thích và Tài khoản.

Ví dụ:

CupertinoTabScaffold(
  tabBar: CupertinoTabBar(
    items: const [
      BottomNavigationBarItem(
        icon: Icon(CupertinoIcons.home),
        label: 'Trang chủ',
      ),
      BottomNavigationBarItem(
        icon: Icon(CupertinoIcons.search),
        label: 'Tìm kiếm',
      ),
      BottomNavigationBarItem(
        icon: Icon(CupertinoIcons.person),
        label: 'Tài khoản',
      ),
    ],
  ),
  tabBuilder: (context, index) {
    return CupertinoTabView(
      builder: (context) {
        return CupertinoPageScaffold(
          child: Center(
            child: Text('Tab $index'),
          ),
        );
      },
    );
  },
);

Cách tổ chức này giúp người dùng chuyển đổi giữa các khu vực chính của ứng dụng một cách nhanh chóng.

5. So sánh Material và Cupertino

Material và Cupertino đều là hệ thống Widget có sẵn trong Flutter, nhưng được xây dựng theo hai định hướng thiết kế khác nhau.

Tiêu chí Material Cupertino
Phong cách thiết kế Material Design của Google Giao diện theo phong cách Apple
Thư viện material.dart cupertino.dart
Widget gốc MaterialApp CupertinoApp
Cấu trúc trang Scaffold CupertinoPageScaffold
Thanh điều hướng AppBar CupertinoNavigationBar
Nút bấm ElevatedButton, FilledButton CupertinoButton
Trường nhập liệu TextField CupertinoTextField
Công tắc Switch CupertinoSwitch
Hộp thoại AlertDialog CupertinoAlertDialog
Hệ sinh thái biểu tượng Icons CupertinoIcons
Trải nghiệm giao diện Theo quy ước Material Theo quy ước iOS
Phạm vi sử dụng Android, iOS và nhiều nền tảng khác Đặc biệt phù hợp với giao diện iOS

Điểm cần lưu ý: Material không chỉ dành cho Android và Cupertino không chỉ chạy trên iOS. Cả hai bộ Widget đều có thể được sử dụng trên nhiều nền tảng mà Flutter hỗ trợ. Sự khác biệt chủ yếu nằm ở thiết kế và hành vi giao diện, chứ không phải giới hạn nền tảng.

6. Có thể sử dụng Material và Cupertino cùng lúc không?

Có. Flutter cho phép kết hợp Widget Material và Cupertino trong cùng một ứng dụng. Lập trình viên không bắt buộc phải chọn duy nhất một hệ thống thiết kế cho toàn bộ dự án.

Ví dụ, ứng dụng có thể sử dụng MaterialApp làm Widget gốc nhưng hiển thị một CupertinoSwitch hoặc CupertinoAlertDialog tại một khu vực cụ thể.

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

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Mixed UI'),
        ),
        body: const Center(
          child: CupertinoButton.filled(
            onPressed: null,
            child: Text('Cupertino Button'),
          ),
        ),
      ),
    );
  }
}

Trong ví dụ trên, ứng dụng sử dụng MaterialApp và Scaffold, nhưng nút bấm lại sử dụng phong cách Cupertino.

Một số trường hợp kết hợp hai hệ thống:

  • Sử dụng Material cho cấu trúc tổng thể và Cupertino cho công tắc hoặc hộp thoại.

  • Sử dụng Cupertino cho các màn hình cần trải nghiệm iOS đặc trưng.

  • Tạo ứng dụng đa nền tảng nhưng có những thành phần giao diện khác nhau theo từng hệ điều hành.

  • Tái sử dụng những Widget có sẵn thay vì xây dựng lại từ đầu.

Tuy nhiên, cần tránh trộn hai phong cách một cách tùy tiện. Nếu sử dụng quá nhiều thành phần có kiểu dáng khác nhau mà không có quy chuẩn, giao diện có thể thiếu nhất quán.

7. Phát hiện nền tảng để lựa chọn giao diện

Trong ứng dụng Flutter đa nền tảng, lập trình viên có thể kiểm tra nền tảng hiện tại để quyết định sử dụng Material hay Cupertino.

Ví dụ:

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

class AdaptiveButton extends StatelessWidget {
  const AdaptiveButton({super.key});

  @override
  Widget build(BuildContext context) {
    if (defaultTargetPlatform == TargetPlatform.iOS) {
      return CupertinoButton.filled(
        onPressed: () {},
        child: const Text('Đăng nhập'),
      );
    }

    return FilledButton(
      onPressed: () {},
      child: const Text('Đăng nhập'),
    );
  }
}

Trong ví dụ này:

  • defaultTargetPlatform cung cấp thông tin nền tảng mặc định mà Flutter đang sử dụng.

  • TargetPlatform.iOS đại diện cho nền tảng iOS.

  • Nếu nền tảng là iOS, ứng dụng sử dụng CupertinoButton.filled.

  • Với các nền tảng khác, ứng dụng sử dụng FilledButton.

Đây là một ví dụ về giao diện thích ứng (Adaptive UI), tức là giao diện được điều chỉnh để phù hợp với nền tảng hoặc môi trường sử dụng.

Trong thực tế, việc lựa chọn Widget thích ứng cần được thiết kế có chủ đích. Một số ứng dụng muốn duy trì giao diện giống nhau trên mọi nền tảng, trong khi những ứng dụng khác muốn tuân theo quy ước riêng của từng hệ điều hành.

Ngoài ra, defaultTargetPlatform không nhất thiết phản ánh mọi đặc điểm của thiết bị thực tế trong các môi trường giả lập hoặc kiểm thử. Với những trường hợp cần xác định chính xác môi trường chạy, cần xem xét thêm yêu cầu của ứng dụng.

8. Adaptive UI và Responsive UI khác nhau như thế nào?

Hai khái niệm này thường xuất hiện khi học giao diện Flutter, nhưng chúng giải quyết những vấn đề khác nhau.

Responsive UI (giao diện đáp ứng) là cách xây dựng giao diện có khả năng thay đổi bố cục theo kích thước màn hình hoặc không gian hiển thị. Ví dụ, một danh sách sản phẩm hiển thị một cột trên điện thoại nhưng nhiều cột trên máy tính bảng.

Adaptive UI (giao diện thích ứng) là cách điều chỉnh giao diện, thành phần hoặc hành vi để phù hợp với nền tảng và môi trường sử dụng. Ví dụ, ứng dụng dùng CupertinoSwitch trên iOS và Switch theo Material trên Android.

Tiêu chí Responsive UI Adaptive UI
Mục tiêu Phù hợp với kích thước không gian Phù hợp với nền tảng hoặc môi trường
Yếu tố thường xét Chiều rộng, chiều cao, tỷ lệ bố cục Hệ điều hành, phương thức tương tác, quy ước giao diện
Ví dụ Một cột trên điện thoại, ba cột trên máy tính bảng Nút Material trên Android, nút Cupertino trên iOS
Widget thường dùng LayoutBuilder, MediaQuery, Expanded, Wrap Material, Cupertino và logic lựa chọn Widget

Một ứng dụng tốt có thể cần cả hai: giao diện đáp ứng kích thước màn hình và thích ứng với nền tảng đang chạy.

9. Nguyên tắc lựa chọn Material và Cupertino

Khi bắt đầu một dự án Flutter, nên xác định hệ thống thiết kế dựa trên đối tượng người dùng, nền tảng triển khai và mục tiêu giao diện.

Nên chọn Material khi:

  • Phát triển ứng dụng theo phong cách Material Design.

  • Muốn tận dụng hệ sinh thái Widget phong phú của Flutter Material.

  • Xây dựng ứng dụng quản lý, bán hàng, dashboard hoặc các ứng dụng nghiệp vụ.

  • Muốn có một hệ thống theme thống nhất và dễ tùy chỉnh.

  • Cần tái sử dụng nhiều thành phần giao diện phổ biến.

Nên chọn Cupertino khi:

  • Muốn xây dựng trải nghiệm giao diện gần với ứng dụng iOS gốc.

  • Ứng dụng chủ yếu hướng đến người dùng iPhone hoặc iPad.

  • Muốn sử dụng các thành phần điều hướng và tương tác theo phong cách Apple.

  • Cần thể hiện các quy ước giao diện đặc trưng của iOS.

Nên kết hợp cả hai khi:

  • Ứng dụng đa nền tảng có yêu cầu giao diện khác nhau trên từng hệ điều hành.

  • Một số thành phần cần trải nghiệm đặc trưng của iOS, còn phần còn lại sử dụng Material.

  • Dự án có quy chuẩn thiết kế rõ ràng cho từng loại Widget.

Không có hệ thống nào luôn tốt hơn hệ thống còn lại. Điều quan trọng là lựa chọn phù hợp với mục tiêu của dự án, duy trì tính nhất quán và bảo đảm trải nghiệm sử dụng tốt.

10. Các lỗi và lưu ý thường gặp

10.1. Quên import thư viện

Nếu sử dụng Widget Material mà không import material.dart, trình biên dịch có thể báo lỗi không tìm thấy lớp hoặc tên Widget.

Tương tự, các Widget Cupertino cần thư viện cupertino.dart.

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

Chỉ cần import những thư viện thực sự được sử dụng trong tệp để mã nguồn dễ đọc và quản lý hơn.

10.2. Sử dụng sai cấu trúc trang

Scaffold và CupertinoPageScaffold có mục đích tương tự nhưng không hoàn toàn giống nhau.

Nếu sử dụng Widget dành cho Material trong cấu trúc Cupertino mà không hiểu cách hoạt động của chúng, một số chi tiết như màu sắc, điều hướng hoặc hiệu ứng có thể không đồng nhất.

10.3. Không cập nhật trạng thái của Widget

Một số Widget như CupertinoSwitch yêu cầu cung cấp trạng thái hiện tại thông qua value và xử lý thay đổi thông qua onChanged.

Nếu chỉ viết giao diện mà không cập nhật trạng thái, công tắc có thể không phản ánh đúng thao tác của người dùng.

Với những trạng thái cục bộ đơn giản, có thể dùng StatefulWidget và setState(). Với ứng dụng lớn, có thể cân nhắc các giải pháp quản lý trạng thái như Provider, Riverpod hoặc Bloc.

10.4. Trộn hai phong cách thiếu nhất quán

Việc kết hợp Material và Cupertino hoàn toàn hợp lệ, nhưng cần có quy chuẩn thiết kế. Ví dụ, một ứng dụng có thể dùng Material cho toàn bộ trang quản trị nhưng dùng Cupertino cho một số thành phần cụ thể trên iOS.

Mục tiêu là tạo trải nghiệm thống nhất, không phải sử dụng càng nhiều loại Widget càng tốt.

10.5. Cho rằng đổi Widget gốc sẽ tự động chuyển toàn bộ giao diện

Chuyển từ MaterialApp sang CupertinoApp không tự động biến mọi Widget Material thành Widget Cupertino. Những thành phần như ElevatedButton, Scaffold hoặc AppBar vẫn cần được thay thế hoặc thiết kế lại nếu muốn chuyển hoàn toàn sang phong cách iOS.

11. Kết luận

Material và Cupertino là hai hệ thống Widget quan trọng trong Flutter, cung cấp những thành phần giao diện có sẵn để xây dựng ứng dụng mà không cần lập trình mọi thành phần từ đầu.

Material tập trung vào hệ thống thiết kế của Google, với các Widget như MaterialApp, Scaffold, AppBar, ElevatedButton, TextField và ThemeData. Cupertino tập trung vào phong cách giao diện Apple, với các Widget như CupertinoApp, CupertinoPageScaffold, CupertinoNavigationBar, CupertinoButton và CupertinoTextField.

Hai hệ thống có thể được sử dụng độc lập hoặc kết hợp trong cùng một ứng dụng. Việc lựa chọn phụ thuộc vào yêu cầu giao diện, nền tảng mục tiêu và trải nghiệm mà ứng dụng muốn mang lại.

Khi học Flutter, cần hiểu rằng Material và Cupertino không chỉ là hai bộ nút bấm khác nhau. Chúng còn cung cấp cấu trúc trang, hệ thống điều hướng, thành phần nhập liệu, hộp thoại, theme và các quy ước tương tác. Nắm vững hai hệ thống này giúp lập trình viên xây dựng giao diện nhanh hơn, dễ bảo trì hơn và phù hợp hơn với người dùng trên từng nền tảng.

Bài viết khác

Flutter Layout & Responsive UI

      Trong phát triển ứng dụng hiện đại, giao diện người dùng không chỉ cần đẹp mà còn phải thích ứng tốt với nhiều kích thước màn hình, tỷ lệ khung hình và phương thức tương tác khác nhau. Một ứng dụng có thể được sử dụng trên điện thoại nhỏ, máy tính […]

StatelessWidget và StatefulWidget trong Flutter

    Trong số các loại widget phổ biến nhất, StatelessWidget và StatefulWidget giữ vai trò đặc biệt quan trọng. Việc phân biệt hai loại widget này không chỉ giúp lập trình viên viết mã đúng mà còn ảnh hưởng đến khả năng tổ chức, bảo trì và tối ưu hiệu năng của ứng dụng. StatelessWidget phù hợp […]

Test Case & Test Plan & Test Data

1. Lời mở đầu và vị trí nền tảng của bộ ba quản lý kiểm thử trong dự án phần mềm Trong quy trình sản xuất phần mềm hiện đại, nếu hoạt động lập trình (Coding) là việc hiện thực hóa các ý tưởng thiết kế thành sản phẩm chạy được, thì hoạt động kiểm […]

Forms & Input Validation trong Flutter

Forms & Input Validation trong Flutter 1. Tổng quan về Forms & Input Validation Forms & Input Validation là hai thành phần quan trọng trong quá trình phát triển ứng dụng Flutter, được sử dụng để thu thập thông tin từ người dùng và kiểm tra tính hợp lệ của dữ liệu trước khi xử […]

Navigation & Routing

Navigation & Routing trong Flutter 1. Navigation & Routing Navigation & Routing là cơ chế điều hướng giữa các màn hình trong ứng dụng Flutter. Cơ chế này cho phép người dùng di chuyển từ màn hình này sang màn hình khác, quay lại màn hình trước đó, truyền dữ liệu giữa các màn hình […]

Flutter Widget Fundamentals

       Flutter là bộ công cụ phát triển giao diện người dùng do Google xây dựng, cho phép lập trình viên tạo ứng dụng đa nền tảng từ một cơ sở mã nguồn duy nhất. Điểm đặc biệt nhất của Flutter nằm ở triết lý mọi thứ đều là widget. Từ một dòng […]

Leave a Reply

Your email address will not be published. Required fields are marked *