Low-Cost Screen Adaptation Solutions in Flutter
This article explores low-cost screen adaptation techniques in Flutter, analyzing physical vs logical pixels, evaluating existing solutions like flutter_screenutil, and proposing two approaches—modifying engine-level ViewConfiguration and customizing WidgetsFlutterBinding—to achieve consistent UI across devices without invasive code changes.
Introduces the problem of screen adaptation in Flutter, explaining physical pixels and logical pixels, and the relationship: physical pixel = logical pixel × devicePixelRatio.
Reviews the popular flutter_screenutil solution, showing its core code:
double get scaleWidth => _screenWidth / uiSize.width;
double setWidth(num width) => width * scaleWidth;Usage example:
Container(width: ScreenUtil().setWidth(50), height: ScreenUtil().setHeight(200));
Container(width: 50.w, height: 200.h);Discusses limitations of flutter_screenutil (intrusive, maintenance overhead).
Proposes a low‑cost approach by modifying the engine’s ViewConfiguration: override devicePixelRatio in FlutterWindow.setViewConfiguration, e.g.:
final modifiedViewConfiguration = window.viewConfiguration.copyWith(devicePixelRatio: window.physicalSize.width / 375);
window.setViewConfigureation(modifiedViewConfiguration);Notes that this requires SDK recompilation and may cause maintenance issues.
Presents an application‑layer solution by subclassing WidgetsFlutterBinding, overriding createViewConfiguration and handling pointer events to keep gesture coordinates correct:
class MyWidgetsFlutterBinding extends WidgetsFlutterBinding {
@override
ui.ViewConfiguration createViewConfiguration() {
return ui.ViewConfiguration(
devicePixelRatio: window.physicalSize.width / 375,
);
}
@override
void handleMetricsChanged() {
renderView.configuration = createViewConfiguration();
scheduleForcedFrame();
}
@override
void initInstances() {
super.initInstances();
window.onPointerDataPacket = _handlePointerDataPacket;
}
void _handlePointerDataPacket(PointerDataPacket packet) {
_pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.physicalSize.width / 375));
if (!locked) _flushPointerEventQueue();
}
}Shows how to run the app with the custom binding:
void runMyApp(Widget app) {
MyWidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}
void main() => runMyApp(MyApp());Additionally, fixes MediaQuery size and gesture issues by wrapping MaterialApp with a builder that adjusts MediaQueryData:
builder: (context, widget) => MediaQuery(
child: widget,
data: MediaQuery.of(context).copyWith(
size: Size(375, window.physicalSize.height / (window.physicalSize.width / 375)),
devicePixelRatio: window.physicalSize.width / 375,
textScaleFactor: 1.0,
),
)Concludes that the application‑layer method is low‑cost, non‑intrusive, and maintains correct UI and gesture handling, while suggesting further research into engine‑level hooks for even more elegant solutions.
政采云技术
ZCY Technology Team (Zero), based in Hangzhou, is a growth-oriented team passionate about technology and craftsmanship. With around 500 members, we are building comprehensive engineering, project management, and talent development systems. We are committed to innovation and creating a cloud service ecosystem for government and enterprise procurement. We look forward to your joining us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.