Bạn có danh sách card và muốn chuyển từ một cột sang hai cột. numColumns giúp FlatList xếp nhiều item trong một hàng, nhưng không biến nó thành masonry với các cột tự lấp khoảng trống theo chiều cao từng card. Trước khi đặt prop, chốt card có cùng chiều cao hay không.
Bài đối chiếu React Native 0.88 ngày 07/11/2026. JSX được rà thủ công theo FlatList và tài liệu kích thước; chưa build native, chạy Hermes hoặc kiểm tra grid trên Android/iOS. Hàm tạo dữ liệu được thử riêng bằng Node.js 24.18.1; việc ấy không phải bằng chứng layout native đã chạy.
Chốt hợp đồng của hai cột
Tài liệu FlatList quy định nhiều cột dùng với danh sách dọc, tức horizontal false, và các item nên có cùng chiều cao. columnWrapperStyle áp dụng cho các hàng nhiều item do FlatList tạo.
Demo dùng card cao 96 và rộng 48% vùng hàng. Quy tắc chiều rộng này giúp một card còn lại ở hàng lẻ giữ cùng độ rộng, thay vì dùng flex: 1 rồi tự chiếm toàn hàng cuối. Đây là lựa chọn của ví dụ, không phải kích thước mặc định của FlatList.
1. Màn hình có bảy card
Đặt component sau vào một màn hình của project React Native đã chạy được. Không có ảnh, API hay thư viện grid bên ngoài:
import React from 'react';
import {FlatList, StyleSheet, Text, View} from 'react-native';
function makeCards() {
return Array.from({length: 7}, (_, index) => ({
id: String(index + 1),
title: `Chủ đề ${index + 1}`,
}));
}
const cards = makeCards();
export default function TwoColumnDemo() {
return (
<View style={styles.screen}>
<FlatList
data={cards}
numColumns={2}
horizontal={false}
keyExtractor={item => item.id}
contentContainerStyle={styles.content}
columnWrapperStyle={styles.row}
renderItem={({item}) => (
<View style={styles.card}>
<Text numberOfLines={2}>{item.title}</Text>
</View>
)}
/>
</View>
);
}
const styles = StyleSheet.create({
screen: {flex: 1},
content: {padding: 16},
row: {justifyContent: 'space-between'},
card: {width: '48%', height: 96, marginBottom: 12, padding: 12,
backgroundColor: '#dbeafe', borderRadius: 12},
});
Hai card rộng tổng cộng 96% của hàng, phần còn lại dành cho khoảng cách theo justifyContent. Bài không cam kết khoảng cách ngang luôn là một số pixel cụ thể trên mọi màn hình. Nếu thiết kế cần gutter cố định, tính chiều rộng theo vùng chứa thực và kiểm tra lại.
Mỗi entity có id riêng dùng làm key. Không thêm một entity giả chỉ để lấp cột cuối trong ví dụ này. Số dữ liệu vẫn là bảy; cách bố trí chịu trách nhiệm cho khoảng trống còn lại của hàng.
2. Kiểm tra hàng cuối và nội dung dài
Chạy với bảy card để quan sát ba hàng đủ và một hàng lẻ. Card cuối cần giữ cùng độ rộng như card ở các hàng trước. Sau đó thử sáu, một và không có card để kiểm tra các trạng thái ít dữ liệu.
Thay title bằng đoạn dài và tăng cỡ chữ của hệ thống. Card cao cố định cùng numberOfLines là một giới hạn hiển thị có chủ đích trong demo, không bảo đảm mọi nội dung lớn đều đọc đủ. Nếu sản phẩm cần toàn bộ text, thiết kế lại chiều cao đồng nhất hoặc kiểu danh sách theo yêu cầu thực.
Đừng trộn grid với masonry
Nếu mỗi card có chiều cao khác nhau và bạn muốn cột bên cạnh tiếp tục lấp từ vị trí trống sớm nhất, đó là một hợp đồng masonry khác. Bài chưa triển khai layout ấy và không khuyên chữa bằng flexWrap trên vùng nội dung của FlatList.
Giữ numColumns cố định trong demo. Việc đổi số cột động theo kích thước màn hình còn liên quan cách dựng lại list và giữ vị trí cuộn; chưa được thử ở bài này. Đừng suy ra một màn hình hai cột tĩnh đã chứng minh chuyển cột khi xoay thiết bị hoạt động đúng.
Kiểm tra trước khi tối ưu
Xác nhận thứ tự card, id không trùng, hàng lẻ và vùng cha có kích thước hợp lệ. Màn hình dùng flex: 1 để cấp vùng hiển thị cho list; kiểm tra cả layout tổ tiên nếu list không hiện.
Không thêm getItemLayout chỉ vì card có height cố định mà chưa xác định đúng cách list nhiều cột tính hàng và offset. Bài chưa đo hiệu năng cuộn hoặc cấp các offset tối ưu. Hoàn thành kiểm tra bố trí trước, rồi đo trên dữ liệu và thiết bị đích nếu cần tối ưu.

