aedev.net
  • Trang chủ
  • AI
  • Mobile
  • Flutter
  • React Native
  • Backend
  • DevOps
  • Hướng dẫn
No Result
View All Result
SAVED POSTS
aedev.net
  • Trang chủ
  • AI
  • Mobile
  • Flutter
  • React Native
  • Backend
  • DevOps
  • Hướng dẫn
No Result
View All Result
aedev.net
No Result
View All Result
Minh họa phát triển ứng dụng mobile đa nền tảng với Flutter

Flutter LayoutBuilder: chọn layout theo vùng cha thay vì toàn cửa sổ

Suong Nguyen by Suong Nguyen
29/10/2026
in Flutter
0
585
SHARES
3.2k
VIEWS
Summarize with ChatGPTShare to Facebook

Một màn hình desktop rộng nhưng card đang nằm trong sidebar hẹp. Nếu card dùng chiều rộng cửa sổ để quyết định hiển thị hai cột, nó có thể chọn layout không vừa chỗ được cấp. Vấn đề nằm ở câu hỏi đang đo: toàn vùng app hay vùng cha dành cho component.

Bài đối chiếu Flutter API ngày 29/10/2026. Ví dụ dùng Material, LayoutBuilder và MediaQuery.sizeOf; cần SDK có các API này. Code được rà theo tài liệu, chưa chạy Flutter build, widget test hoặc thiết bị. Các tình huống bên dưới là checklist để bạn kiểm tra trên dự án của mình.

Hai phép đo phục vụ hai phạm vi

MediaQuery.sizeOf(context) lấy size từ MediaQuery gần context nhất. Trong cây app thông thường, nó hữu ích khi chọn cấu trúc theo kích thước cửa sổ. Nó không hứa trả chiều rộng thực tế của một card sau Padding, sidebar hoặc SizedBox.

LayoutBuilder cung cấp constraints do cha truyền vào lúc layout. Với một component cần quyết định theo chỗ còn lại, constraints.maxWidth thường là dữ kiện phù hợp hơn. Constraints là giới hạn được phép, chưa phải phép đo kích thước cuối cùng của mọi widget con.

Hướng dẫn adaptive của Flutter phân biệt phạm vi cửa sổ và phạm vi cục bộ này. Hãy xác định component có thể được đặt ở đâu trước khi chọn API.

Tạo một vùng hẹp có chủ đích

Thay nội dung lib/main.dart trong project thử nghiệm bằng ví dụ sau. Ngưỡng 600 là lựa chọn minh họa cho hai panel, không phải quy tắc bắt buộc cho mọi tablet hoặc mọi ứng dụng.

import 'package:flutter/material.dart';

void main() => runApp(const MaterialApp(home: DemoPage()));

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

  @override
  Widget build(BuildContext context) {
    final windowWidth = MediaQuery.sizeOf(context).width;
    return Scaffold(
      appBar: AppBar(title: const Text('Layout theo vùng cha')),
      body: SafeArea(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              Text('Cửa sổ: ${windowWidth.toStringAsFixed(0)}'),
              const SizedBox(height: 12),
              const Align(
                alignment: Alignment.centerLeft,
                child: SizedBox(width: 360, child: SummaryPanels()),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

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

  Widget panel(String title) => Card(
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Text(title),
        ),
      );

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(builder: (context, constraints) {
      final wide = constraints.maxWidth >= 600;
      final first = panel('Thông tin đơn hàng');
      final second = panel('Ghi chú giao hàng có thể dài hơn dự kiến');
      return Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Text('Vùng cha: ${constraints.maxWidth.toStringAsFixed(0)}'),
          if (wide)
            Row(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Expanded(child: first),
                const SizedBox(width: 12),
                Expanded(child: second),
              ],
            )
          else ...[
            first,
            const SizedBox(height: 12),
            second,
          ],
        ],
      );
    });
  }
}

Ở cửa sổ rộng, SizedBox vẫn chỉ đề nghị rộng 360 và chịu giới hạn từ cha. SummaryPanels vì vậy có vùng hẹp để chọn một cột. Trong code, MediaQuery chỉ dùng để hiển thị thông tin so sánh, không điều khiển nhánh layout của panel.

Kiểm tra sự khác biệt

Đầu tiên, mở cửa sổ rộng hơn 600 và giữ nguyên wrapper 360. Hai con số hiển thị cần được đọc như hai phạm vi khác nhau; đừng mặc định chúng phải bằng nhau.

Tiếp theo, thay toàn bộ widget Align trong children bằng const SummaryPanels(). Column ngoài đang stretch theo chiều ngang nên component nhận vùng rộng của phần nội dung, sau padding. Khi vùng ấy đạt 600, nhánh hai panel theo hàng mới được chọn.

Cuối cùng, thu hẹp cửa sổ để vùng nội dung xuống dưới 600. Kiểm tra panel quay về một cột, chữ dài vẫn đọc được, và có thể cuộn khi chiều cao không đủ. Thử sát hai phía của ngưỡng để phát hiện việc đặt breakpoint trên sai lớp Padding.

Constraints cần có giới hạn phù hợp

Ví dụ dựa vào chiều ngang hữu hạn do màn hình truyền xuống. Nếu đưa SummaryPanels vào vùng cuộn ngang không ràng buộc chiều rộng, maxWidth có thể vô hạn; so với 600 lúc ấy không còn giải quyết được bố cục. Cần cấp một chiều rộng hữu hạn ở cha trước, thay vì thêm Expanded ngẫu nhiên để dập lỗi.

Theo LayoutBuilder API, builder chạy khi các điều kiện layout hoặc dependency liên quan thay đổi; không nên dùng nó để ghi database hay phát request mỗi lần dựng cây. Giữ quyết định layout là phép tính từ constraints và dữ liệu hiện có.

Lỗi thường gặp và giới hạn

  • Dùng chiều rộng cửa sổ để đo card lồng trong vùng nhỏ.
  • Gọi maxWidth là chiều rộng cuối cùng đã render của mọi con.
  • Đặt ngưỡng 600 mà không thử nội dung thật, chữ lớn hoặc bản dịch dài.
  • Chỉ kiểm tra điện thoại toàn màn hình, bỏ qua cửa sổ nhỏ và bố cục chia cột.

MediaQuery.sizeOf API vẫn phù hợp khi quyết định thực sự thuộc về toàn vùng MediaQuery. Với component tái sử dụng, bắt đầu từ phần không gian cha cấp sẽ giúp nó thích nghi đúng khi chuyển sang sidebar, dialog hoặc vùng nội dung khác.

Nguồn tham khảo

  • Flutter: General approach to adaptive apps
  • Flutter API: LayoutBuilder
  • Flutter API: MediaQuery.sizeOf

Tags: FlutterLayoutBuilderResponsive UI
SummarizeShare234
Suong Nguyen

Suong Nguyen

Related Stories

Minh họa phát triển ứng dụng mobile đa nền tảng với Flutter

Flutter showDialog: nhận kết quả xác nhận và kiểm tra context sau await

by Suong Nguyen
28/10/2026
0

Trả bool từ dialog qua Navigator.pop, phân biệt false với null và chỉ cập nhật giao diện khi context gọi vẫn còn mounted.

Minh họa phát triển ứng dụng mobile đa nền tảng với Flutter

Flutter Expanded và Flexible: cho Text trong Row một giới hạn chiều rộng

by Suong Nguyen
27/10/2026
0

Sửa một Row có tiêu đề dài bằng Expanded, phân biệt giới hạn tight và loose, rồi kiểm tra đúng vị trí của Padding và constraints.

Minh họa phát triển ứng dụng mobile đa nền tảng với Flutter

Flutter ValueKey: giữ state đi cùng dữ liệu khi đảo thứ tự danh sách

by Suong Nguyen
26/10/2026
0

Dựng hai hàng có bộ đếm riêng để hiểu key ổn định, vị trí widget và lý do UniqueKey mới mỗi lần build có thể làm mất state.

Minh họa phát triển ứng dụng mobile đa nền tảng với Flutter

Flutter Form: validate dữ liệu trước khi gọi save

by Suong Nguyen
25/10/2026
0

Tổ chức Form và TextFormField với GlobalKey ổn định, validator rõ ràng và luồng validate rồi save có thể kiểm tra.

0 0 votes
Article Rating
Subscribe
Login
Notify of
guest
guest
0 Comments
Oldest
Newest Most Voted
AE Dev

AE Dev mang đến các bài viết về lập trình, mobile app, backend, frontend, WordPress và công nghệ, phù hợp cho developer muốn học hỏi và cập nhật kiến thức thực tế.

Recent Posts

  • React Native TextInput: bàn phím numeric không thay thế validation
  • Phỏng vấn JavaScript: vì sao tách method thành callback lại mất this?
  • Android NumberFormat: tách locale, mã tiền tệ và giá trị cần hiển thị

Categories

  • AI
  • Backend
  • Đánh giá
  • DevOps
  • Flutter
  • Hướng dẫn
  • Lập trình Mobile
  • Phỏng vấn
  • React Native
  • Uncategorized

Weekly Newsletter

  • Buy JNews
  • Support Forum
  • Pre-sale Question
  • Contact Us

© 2026 AE Dev - Blog công nghệ, lập trình và chia sẻ kiến thức IT.

Welcome Back!

Login to your account below

Forgotten Password?

Retrieve your password

Please enter your username or email address to reset your password.

Log In
No Result
View All Result
  • Trang chủ
  • AI
  • Mobile
  • React Native
  • Backend
  • DevOps
  • Hướng dẫn
  • Flutter

© 2026 AE Dev - Blog công nghệ, lập trình và chia sẻ kiến thức IT.

wpDiscuz
0
0
Would love your thoughts, please comment.x
()
x
| Reply