flutter_table_plus에서 scale 기능을 구현하면서 얻은 교훈을 정리합니다. folderview/treeview 등 동일한 스크롤 구조를 가진 패키지에서도 참고할 수 있습니다.
Listener(onPointerSignal:)로 Ctrl+wheel을 감지해 줌을 처리해도,
PointerSignalEvent는 전파를 막을 수 없어서 내부 Scrollable이 동시에 스크롤됩니다.
Scrollable._receivedPointerSignal()은 스크롤 처리 전에 physics.shouldAcceptUserOffset()을 호출합니다.
이 메서드가 false를 반환하면 pointerSignalResolver에 아예 등록하지 않아서 스크롤 자체가 발생하지 않습니다.
class _ScaleBlockingScrollPhysics extends ClampingScrollPhysics {
const _ScaleBlockingScrollPhysics({super.parent});
@override
_ScaleBlockingScrollPhysics applyTo(ScrollPhysics? ancestor) {
return _ScaleBlockingScrollPhysics(parent: buildParent(ancestor));
}
@override
bool shouldAcceptUserOffset(ScrollMetrics position) {
if (HardwareKeyboard.instance.isControlPressed) return false;
return super.shouldAcceptUserOffset(position);
}
}onScaleChanged가 non-null일 때만 이 physics를 적용- null이면 기존
ClampingScrollPhysics유지 HardwareKeyboard.instance.isControlPressed는 동기적으로 확인 가능
ScrollPhysics가 1차 방어선이지만, 만약을 대비해 Listener.onPointerSignal에서
스크롤 발생 전 offset을 저장하고 didUpdateWidget에서 보정합니다.
// Listener에서 (스크롤 오염 전)
_preScaleVerticalOffset = controller.offset;
// didUpdateWidget에서 (scale 변경 후)
final baseOffset = _preScaleVerticalOffset ?? controller.offset;
controller.jumpTo((baseOffset * ratio).clamp(0, maxExtent));scale이 바뀌면 콘텐츠 크기가 변하므로 스크롤 위치 보정이 필요합니다.
offset=500, scale 1.0→2.0 일 때:
보정 없음: offset=500 그대로 → 콘텐츠 중간으로 점프
보정 있음: offset=500*2=1000 → 같은 콘텐츠 위치 유지
didUpdateWidget에서 addPostFrameCallback으로 보정해야 합니다.
(새 레이아웃의 maxScrollExtent가 계산된 후에 jumpTo 가능)
| 스케일 O | 스케일 X |
|---|---|
| 행 높이, 컬럼 너비 | 스크롤바 (UI chrome) |
| 폰트 크기, 패딩 | 색상, 불리언 |
| 아이콘 (FittedBox) | Border/Divider 두께 |
| 리사이즈 핸들 | Duration |
비례 분배 로직을 변경하지 않고 스케일 적용하는 방법:
// 논리 공간에서 계산 → 결과에 scale 곱하기
final logicalWidths = _calculateColumnWidths(availableWidth / scale, columns);
final columnWidths = logicalWidths.map((w) => w * scale).toList();_resizedWidths는 논리 단위(unscaled)로 저장해야 scale 변경에도 값이 유지됩니다.
사용자가 제공하는 Widget(예: sort icon)은 내부 size가 고정이라 SizedBox만 키워도 안 커집니다.
FittedBox로 감싸면 SizedBox에 맞게 자동 스케일되고, scale=1.0에서는 no-op입니다.
SizedBox(
width: scaledIconWidth,
child: FittedBox(child: userProvidedIcon),
)Material Checkbox의 렌더링 크기는 materialTapTargetSize와 visualDensity에 의해 결정되므로,
SizedBox wrapper를 키워도 체크박스 자체는 안 커집니다. 커스텀 체크박스가 필요합니다.