PageViewBuilder: Gã Khổng Lồ 'Lướt' Mượt Mà Cho Flutter
Flutter

PageViewBuilder: Gã Khổng Lồ 'Lướt' Mượt Mà Cho Flutter

Author

Admin System

@root

Ngày xuất bản

20 Mar, 2026

Lượt xem

2 Lượt

"PageViewBuilder"

Chào các "dev-er" tương lai, hôm nay chúng ta sẽ cùng "mổ xẻ" một "ông thần" trong vũ trụ Flutter, đó là PageViewBuilder. Nghe cái tên đã thấy "builder" rồi, mà đã là "builder" thì thường là "hệ tối ưu" rồi đó.

1. PageViewBuilder là gì và để làm gì?

Nếu bạn đã từng lướt qua các ứng dụng như Instagram Story, Facebook Stories, hoặc mấy cái màn hình giới thiệu app (onboarding screens) khi mới cài đặt, bạn sẽ thấy mình "vuốt vuốt" ngang qua các nội dung khác nhau. Mỗi lần vuốt là một "trang" mới xuất hiện. Đằng sau cái sự mượt mà đó, rất có thể có bóng dáng của PageViewBuilder.

Nói một cách dễ hiểu, PageViewBuilder giống như một "cuốn album ảnh cưới của đứa bạn thân" vậy đó. Bạn chỉ lật đến ảnh nào thì mới lôi cái ảnh đó ra xem. Chứ không ai lại đi lôi hết 500 tấm ảnh ra trải dài trên sàn nhà để xem cùng một lúc cả, vừa tốn sức, vừa tốn chỗ, lại còn dễ bị mẹ la.

PageViewBuilder là một widget trong Flutter dùng để tạo ra một danh sách các "trang" (pages) có thể cuộn ngang hoặc dọc. Điểm đặc biệt của nó so với PageView "thường" là khả năng "lười biếng" (lazy loading). Tức là, nó chỉ xây dựng (build) những trang thực sự cần thiếtđang hiển thị trên màn hình, hoặc những trang ở gần đó. Những trang còn lại? Cứ để đó, khi nào cần thì "triệu hồi" sau. Điều này giúp tối ưu hiệu năng cực kỳ tốt, đặc biệt khi bạn có một số lượng trang lớn, thậm chí là vô hạn.

Tóm lại: PageViewBuilder sinh ra để làm "carousel", "slider", "story feeds" hay "onboarding screens" mà không làm "lag" máy của người dùng, giữ cho app của bạn mượt mà như "phim hành động" vậy.

2. Code Ví Dụ Minh Hoạ Rõ Ràng

Giờ thì "xắn tay áo" lên, chúng ta cùng xem "ông thần" này hoạt động như thế nào qua một ví dụ đơn giản nhé. Chúng ta sẽ tạo một PageViewBuilder với vài trang màu sắc khác nhau.

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: 'PageViewBuilder Demo của Creyt',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final PageController _pageController = PageController();
  final List<Color> _pageColors = [
    Colors.red, Colors.green, Colors.blue, Colors.purple, Colors.orange, Colors.teal, Colors.pink,
  ];

  @override
  void dispose() {
    _pageController.dispose(); // Đừng quên dispose controller!
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('PageViewBuilder Demo'),
      ),
      body: PageView.builder(
        controller: _pageController,
        itemCount: _pageColors.length, // Tổng số trang
        itemBuilder: (BuildContext context, int index) {
          // Hàm này sẽ được gọi để xây dựng từng trang
          return Container(
            color: _pageColors[index], // Màu sắc của trang
            child: Center(
              child: Text(
                'Trang ${index + 1}',
                style: const TextStyle(
                  color: Colors.white,
                  fontSize: 48,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // Ví dụ: chuyển đến trang kế tiếp
          if (_pageController.hasClients) {
            _pageController.nextPage(
              duration: const Duration(milliseconds: 300),
              curve: Curves.easeIn,
            );
          }
        },
        child: const Icon(Icons.arrow_forward),
      ),
    );
  }
}

Giải thích code:

  • PageController _pageController = PageController();: Đây là "tay lái" của bạn. Nó cho phép bạn điều khiển PageViewBuilder một cách lập trình, ví dụ như chuyển trang, lắng nghe sự kiện cuộn, v.v. Nhớ dispose() nó khi không dùng nữa để tránh rò rỉ bộ nhớ.
  • itemCount: _pageColors.length: Chúng ta nói cho PageViewBuilder biết có tổng cộng bao nhiêu trang.
  • itemBuilder: (BuildContext context, int index) { ... }: Đây là "nhà máy sản xuất" từng trang. Khi PageViewBuilder cần hiển thị trang index nào, nó sẽ gọi hàm này để tạo ra widget tương ứng. Trong ví dụ này, chúng ta chỉ trả về một Container với màu sắc và số trang.
Illustration

3. Mẹo (Best Practices) để ghi nhớ hoặc dùng thực tế

  • "Lười biếng có chiến lược": Luôn nhớ PageViewBuilder chỉ xây dựng những gì cần thiết. Đừng bao giờ bỏ qua itemBuilderitemCount khi bạn có một danh sách trang lớn hoặc động. Đây là "chìa khóa vàng" cho hiệu năng.
  • PageController là "người quản lý": Nếu bạn muốn tự động chuyển trang, nhảy đến một trang cụ thể, hoặc biết người dùng đang ở trang nào, hãy dùng PageController. Nó là "cánh tay nối dài" của bạn để tương tác với PageViewBuilder.
  • viewportFraction cho "cửa sổ nhìn": Muốn hiển thị một phần của trang kế tiếp hoặc trang trước đó? Dùng viewportFraction trong PageController. Nó giống như bạn "hé" cửa sổ ra một chút để nhìn thấy cảnh bên ngoài vậy.
  • keepPage "nhớ vị trí": Mặc định là true. Khi bạn quay lại một trang đã xem, nó sẽ nhớ vị trí cuộn của trang đó. Hữu ích cho các trang có nội dung cuộn.
  • physics cho "cảm giác cuộn": Muốn cuộn như "nước chảy", "đàn hồi" hay "không cuộn"? physics trong PageViewBuilder cho phép bạn tùy chỉnh cảm giác cuộn. Ví dụ NeverScrollableScrollPhysics() nếu bạn muốn chặn người dùng cuộn.

4. Văn phong học thuật sâu của anh Creyt, dạy dễ hiểu tuyệt đối

Các bạn hình dung thế này: trong lập trình, chúng ta hay nói về "tài nguyên" (resources) như bộ nhớ (RAM), CPU. PageViewBuilder là một minh chứng điển hình cho việc "quản lý tài nguyên một cách khôn ngoan".

Khi bạn tạo một PageView "thường" với một danh sách widget con trực tiếp (ví dụ: children: [Widget1, Widget2, ...] ), Flutter sẽ cố gắng xây dựng tất cả các widget con đó ngay lập tức. Điều này giống như bạn "đặt hàng" 100 món ăn cùng lúc trong một nhà hàng mà bạn chỉ có thể ăn 1-2 món thôi vậy. Rõ ràng là tốn kém và lãng phí.

Còn với PageViewBuilder, bạn chỉ cung cấp một "công thức" (itemBuilder) và "số lượng" (itemCount). Khi Flutter cần một món ăn (một trang), nó sẽ "gọi" itemBuilder để "chế biến" món đó ngay tại chỗ. Tối ưu hơn hẳn đúng không? Nó chỉ giữ lại một vài món ăn "đã chế biến" ở gần bạn (những trang hiển thị và lân cận) để bạn có thể ăn ngay lập tức khi bạn "lướt" tới.

Đây chính là mô hình "Lazy Loading" kinh điển, được áp dụng rộng rãi trong các framework hiện đại để cải thiện hiệu năng. Nó giúp ứng dụng của bạn không bị "ngộp thở" khi phải xử lý quá nhiều thứ cùng lúc, đặc biệt trên các thiết bị di động với tài nguyên hạn chế.

5. Ví dụ thực tế các ứng dụng/website đã ứng dụng

Bạn sẽ thấy tư duy của PageViewBuilder ở khắp mọi nơi:

  • Instagram/Facebook Stories: Khi bạn vuốt qua các story, không phải tất cả story của bạn bè đều được tải và render cùng lúc. Chỉ những story bạn đang xem và một vài story kế tiếp/trước đó mới được xử lý.
  • Onboarding Screens: Các màn hình giới thiệu ứng dụng ban đầu, bạn vuốt qua từng trang để xem tính năng. Thường thì chỉ có 3-5 trang, nhưng nếu có nhiều hơn, PageViewBuilder là lựa chọn tuyệt vời.
  • Image Carousels/Sliders: Các banner quảng cáo xoay vòng trên website hoặc ứng dụng, hoặc album ảnh trong ứng dụng thư viện ảnh.
  • Weather Apps: Một số ứng dụng thời tiết cho phép bạn vuốt ngang để xem dự báo cho các thành phố khác nhau. Mỗi thành phố là một "trang" riêng biệt.
  • TikTok/Reels: Mặc dù TikTok dùng ListView.builder (cuộn dọc) nhưng nguyên lý "chỉ tải và hiển thị video đang xem và lân cận" là hoàn toàn tương tự với PageViewBuilder.

6. Thử nghiệm đã từng và hướng dẫn nên dùng cho case nào

Creyt đã từng "đau đầu" với một dự án cần hiển thị hàng trăm tấm ảnh trong một gallery dạng carousel. Ban đầu, "non tay" dùng PageView thường, kết quả là app "đơ như cây cơ", cuộn giật cục, tốn RAM khủng khiếp. Sau đó chuyển sang PageViewBuilder, "phù phép" một cái là app chạy mượt mà như "nhung", "lụa". Bài học rút ra là: Đừng coi thường hiệu năng!

Nên dùng PageViewBuilder khi:

  • Số lượng trang lớn hoặc không xác định: Bạn có thể có 100, 1000 trang hoặc thậm chí là một danh sách vô hạn (ví dụ: feed bài viết). PageViewBuilder sẽ "cứu cánh" bạn khỏi tình trạng "ngốn" tài nguyên.
  • Nội dung trang phức tạp: Mỗi trang chứa nhiều widget, hình ảnh, hoặc dữ liệu cần tải. Việc chỉ build những trang cần thiết sẽ giảm tải cho CPU và GPU.
  • Cần kiểm soát cuộn bằng lập trình: Dùng PageController để tạo hiệu ứng chuyển trang tự động, hoặc nhảy đến trang cụ thể sau một sự kiện nào đó.
  • Xây dựng các thành phần UI "vuốt ngang" hoặc "vuốt dọc" có tính "lazy loading": Carousel, gallery, onboarding, story viewer, v.v.

Không nên dùng PageViewBuilder (mà có thể dùng PageView hoặc TabBarView) khi:

  • Số lượng trang rất ít và cố định: Ví dụ chỉ có 2-3 trang đơn giản. Lúc này, sự phức tạp của itemBuilder có thể không cần thiết, dùng PageView với children trực tiếp hoặc TabBarView có khi lại gọn gàng hơn.

Vậy đó, PageViewBuilder không chỉ là một widget, nó là một "triết lý" về tối ưu hiệu năng. Nắm vững nó, bạn sẽ có thêm một "vũ khí hạng nặng" trong bộ công cụ của mình để tạo ra những ứng dụng Flutter mượt mà, chuyên nghiệp. Cứ thực hành nhiều vào, rồi bạn sẽ thấy "sức mạnh" của nó!

Thuộc Series: Flutter

Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!

#tech #cyberpunk #laravel
Chỉnh sửa bài viết

Bình luận (0)

Vui lòng Đăng Nhập để Bình luận

Hỗ trợ Markdown cơ bản
Nguyễn Văn A
1 ngày trước

Tính năng này đỉnh quá ad ơi, chờ mãi mới thấy một blog Tiếng Việt có UI/UX xịn như vầy!