Chuyển đến nội dung chính

Bài đăng

Hiển thị các bài đăng có nhãn UI

RTL layouts that do not look broken in Flutter — hướng dẫn Flutter

Half the world reads right-to-left. Mirroring is not optional. Bài này chuyển quan sát đó thành một mô hình nhỏ để bạn có thể kiểm thử, đo lường và giữ cho code dễ bảo trì khi app lớn lên. Vấn đề cốt lõi Directionality, icons, and mirrored gestures. Câu hỏi hữu ích không phải API hay pattern có đẹp riêng lẻ hay không, mà là state nằm ở đâu, boundary nào chịu trách nhiệm khi lỗi xảy ra, và người dùng phục hồi thế nào khi happy path biến mất. Mô hình thực tế Use start/end, not left/right, in padding and alignment. Hãy bắt đầu bằng một owner rõ ràng cho behavior. Widget chỉ nên render và phát intent; IO, persistence, permission và retry nên nằm sau một interface nhỏ. Nhờ vậy bạn có seam để fake trong test và một chỗ ghi lại các dữ kiện cần theo dõi ở production. Trong app Flutter, boundary thường có dạng: Widget phát intent như load, submit, refresh hoặc retry. Controller hoặc use case validate intent rồi gọi boundary. Boundary trả về data có kiểu hoặc failure có kiểu, không tr...

CupertinoMenuAnchor and RawMenuAnchor menus — hướng dẫn Flutter

RawMenuAnchor is the shared primitive behind Cupertino and Material menus. Bài này chuyển quan sát đó thành một mô hình nhỏ để bạn có thể kiểm thử, đo lường và giữ cho code dễ bảo trì khi app lớn lên. Vấn đề cốt lõi Native-feeling iOS menus without a plugin stack. Câu hỏi hữu ích không phải API hay pattern có đẹp riêng lẻ hay không, mà là state nằm ở đâu, boundary nào chịu trách nhiệm khi lỗi xảy ra, và người dùng phục hồi thế nào khi happy path biến mất. Mô hình thực tế Do not rebuild the entire menu tree on every pointer event. Hãy bắt đầu bằng một owner rõ ràng cho behavior. Widget chỉ nên render và phát intent; IO, persistence, permission và retry nên nằm sau một interface nhỏ. Nhờ vậy bạn có seam để fake trong test và một chỗ ghi lại các dữ kiện cần theo dõi ở production. Trong app Flutter, boundary thường có dạng: Widget phát intent như load, submit, refresh hoặc retry. Controller hoặc use case validate intent rồi gọi boundary. Boundary trả về data có kiểu hoặc failure...

CupertinoMenuAnchor and RawMenuAnchor menus

RawMenuAnchor is the shared primitive behind Cupertino and Material menus. This article turns that observation into a small implementation model you can test, measure, and keep boring when the app grows. The problem in one sentence Native-feeling iOS menus without a plugin stack. The useful question is not whether the API or pattern looks elegant in isolation. It is where state lives, which boundary owns failure, and how a user recovers when the happy path disappears. A practical model Do not rebuild the entire menu tree on every pointer event. Start with one explicit owner for the behavior. Keep widgets responsible for rendering and user intent; keep IO, persistence, permissions, and retries behind a small interface. That gives you a seam for a fake in tests and a place to record the facts that matter in production. For a Flutter app, the boundary usually looks like this: The widget emits an intent such as load, submit, refresh, or retry. A controller or use case validates t...

Pagination and infinite scroll without layout jank — hướng dẫn Flutter

Feeds are the most common jank source. Paginate and set itemExtent when fixed. Bài này chuyển quan sát đó thành một mô hình nhỏ để bạn có thể kiểm thử, đo lường và giữ cho code dễ bảo trì khi app lớn lên. Vấn đề cốt lõi Cursor APIs, item extents, and loading states that do not thrash. Câu hỏi hữu ích không phải API hay pattern có đẹp riêng lẻ hay không, mà là state nằm ở đâu, boundary nào chịu trách nhiệm khi lỗi xảy ra, và người dùng phục hồi thế nào khi happy path biến mất. Mô hình thực tế Keep loading indicators out of the list path when possible. Hãy bắt đầu bằng một owner rõ ràng cho behavior. Widget chỉ nên render và phát intent; IO, persistence, permission và retry nên nằm sau một interface nhỏ. Nhờ vậy bạn có seam để fake trong test và một chỗ ghi lại các dữ kiện cần theo dõi ở production. Trong app Flutter, boundary thường có dạng: Widget phát intent như load, submit, refresh hoặc retry. Controller hoặc use case validate intent rồi gọi boundary. Boundary trả về data...

shadcn_flutter: composable UI primitives for Flutter

shadcn_flutter: composable UI primitives for Flutter shadcn_flutter is useful because it focuses on unstyled primitives, New York style, Material interop, and theming. The important engineering move is to place the package behind a clear boundary, so your Flutter UI depends on a stable capability rather than a vendor-shaped API. What the library should own Keep package calls inside a named adapter or feature boundary. Make lifecycle, errors, and loading state part of a testable contract. Expose only the capability the app needs; hide implementation details from the whole tree. A focused starting point runApp ( ShadcnApp ( theme : ThemeData (colorScheme : ColorSchemes . lightZinc ()), home : Scaffold ( body : Center ( child : PrimaryButton (child : const Text ( 'Continue' )), ), ), ), ); Production checklist Read the README and changelog for the exact version you pin. Add one test for lifecycle, failure, and app ...

flutter_hooks: reusable stateful behavior as tiny hooks

flutter_hooks: reusable stateful behavior as tiny hooks flutter_hooks is useful because it focuses on hook lifecycle, controller ownership, effects, and composable UI logic. The important engineering move is to place the package behind a clear boundary, so your Flutter UI depends on a stable capability rather than a vendor-shaped API. What the library should own Keep package calls inside a named adapter or feature boundary. Make lifecycle, errors, and loading state part of a testable contract. Expose only the capability the app needs; hide implementation details from the whole tree. A focused starting point class SearchField extends HookWidget { const SearchField ({ super .key}); @override Widget build ( BuildContext context) { final controller = useTextEditingController (); final focusNode = useFocusNode (); useEffect (() { focusNode. requestFocus (); return null ; }, const []); return TextField (controller : co...

Đường ống ảnh trong Flutter: từ một URL tới pixel trên màn hình

Image.network(url) là một trong những API thân thiện nhất của Flutter và cũng là một trong những cái dễ đánh bại bạn nhất. Nó chạy được ngay, rồi một tháng sau bạn có những tấm ảnh loé trắng mỗi lần rebuild, một thư viện ảnh tải lại khi cuộn ngược, và 400 MB bộ nhớ trên một máy 3 GB. Cả ba đều là cùng một hiểu nhầm: Image là widget, nhưng việc cache và giải mã diễn ra trong một đối tượng mà nó uỷ thác — ImageProvider . Biết đối tượng đó đánh khoá theo cái gì, và cache cái gì, sẽ giải thích được tất cả. Năm giai đoạn Khoá. ImageProvider sinh ra một khoá — với NetworkImage là URL cộng với scale. Hai provider có khoá bằng nhau là cùng một ảnh dưới góc nhìn của cache. Tải. Byte đến từ mạng, từ asset bundle, từ file, hoặc từ bộ nhớ. Giải mã. Byte đã nén trở thành một dart:ui.Image : pixel thô, rộng × cao × 4 byte. Cache. Ảnh đã giải mã đi vào PaintingBinding.instance.imageCache , đánh khoá theo bước 1. Vẽ. Widget vẽ nó ra, áp dụng fit , alignment , và mọi bộ lọc màu. Hệ ...

Material 3 theming in Flutter: colour roles, not colour values

The Material 3 migration in Flutter produced a predictable pattern. Teams call ColorScheme.fromSeed(seedColor: brandPurple) , look at the result, decide it is not their brand, and start passing explicit colours to every widget again. Six months later the app has two hundred hard-coded hex values, dark mode is a separate list of two hundred more, and changing the brand colour is a week of work. The thing that was skipped is the idea Material 3 is actually built on: you do not style widgets with colours, you assign them roles . A FilledButton does not have a purple background — it has a primary background with onPrimary content. Once that is true across the app, the palette becomes one object you can swap. The roles, and what each one is for ColorScheme has about thirty members. They are not thirty independent choices; they are pairs and families. Role Used for Its “on” pair primary The main action, filled buttons, active states onPrim...

Keyboard shortcuts and pointer support for tablets

Tablets and desktops need keyboard-first paths. This article turns that observation into a small implementation model you can test, measure, and keep boring when the app grows. The problem in one sentence Shortcuts widget, hover, and trackpad scrolling. The useful question is not whether the API or pattern looks elegant in isolation. It is where state lives, which boundary owns failure, and how a user recovers when the happy path disappears. A practical model Add Shortcuts/Actions for primary flows. Start with one explicit owner for the behavior. Keep widgets responsible for rendering and user intent; keep IO, persistence, permissions, and retries behind a small interface. That gives you a seam for a fake in tests and a place to record the facts that matter in production. For a Flutter app, the boundary usually looks like this: The widget emits an intent such as load, submit, refresh, or retry. A controller or use case validates the intent and calls the boundary. The boundar...

Vì sao cú chạm của bạn không ăn: hit testing và đấu trường cử chỉ

Có một loại bug Flutter rất đặc trưng, đủ để ngốn cả buổi chiều. Widget đang hiển thị trên màn hình. onTap đã được nối. Bạn thêm một lệnh print và nó không bao giờ chạy. Không có gì trong console, không lỗi, không cảnh báo — cú chạm đơn giản là không tồn tại. Mọi trường hợp như vậy đều thuộc một trong hai nguyên nhân. Hoặc con trỏ chưa từng tới được detector của bạn trong lúc hit testing, hoặc nó đã tới nơi và detector thua đấu trường cử chỉ trước một đối thủ khác. Đó là hai vấn đề khác nhau với hai cách sửa khác nhau, và chúng phân biệt được. Con trỏ tìm đến một widget như thế nào Khi ngón tay chạm xuống, framework duyệt cây render từ gốc, hỏi từng render object xem điểm đó có nằm trong nó không. Kết quả là một đường hit test : một danh sách có thứ tự từ đối tượng sâu nhất trúng đích lên tới gốc. Sự kiện con trỏ sau đó được phát dọc theo đường này. Ba quy tắc giải thích phần lớn những bất ngờ: Hit testing là hình học, và nó dùng cái hộp của render object. Không phải dùng hình...