如何让Flutter中Stack内的Positioned.fill实现响应式布局?
问题描述
我用Positioned.fill的top属性调整图片上方文本的位置,同时借助MediaQuery实现响应式布局,但大屏手机上调整好后,小屏手机会出现显示问题。
Stack代码:
Stack(children: <Widget>[ GestureDetector( onTap: (() => Navigator.of(context).push( Transitions( transitionType: TransitionType.fade, curve: Curves.bounceInOut, duration: const Duration(milliseconds: 500), reverseCurve: Curves.bounceOut, widget: detailinvest(title)), )), child: Container( // height: 260, height: MediaQuery.of(context).size.height / 3, padding: const EdgeInsets.all(8), child: ClipRRect( borderRadius: BorderRadius.circular(20.0), child: Image.network( "https://arzenafees.com/wp-content/uploads/2021/06/Invest_Rudn_Enclave.jpg", fit: BoxFit.cover, )), ), ), Positioned.fill( // top: 255, top: MediaQuery.of(context).size.width * 0.5, child: Align( alignment: Alignment.bottomCenter, child: ListTile( title: Text( 'Rudn Enclave', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, fontFamily: 'Roboto'), ), subtitle: Row( children: [ Icon( Icons.location_on, color: Constants.colorMain, ), Expanded( child: Text( 'Adiala Road, Rawalpindi, Rawalpindi Cantonment, Rawalpindi District, Punjab, 46600, Pakistan', style: TextStyle( fontSize: MediaQuery.of(context).size.width / 45, ), ), ), ], ), )), ), ]),
说明
该UI在小屏设备上显示正常,截图如下:
补充:GridView代码
body: GridView.count( primary: false, padding: const EdgeInsets.all(8), crossAxisSpacing: 12, mainAxisSpacing: 22, crossAxisCount: 2, childAspectRatio: 0.6, children: <Widget>[
解决方案
问题核心在于你用MediaQuery.of(context).size.width * 0.5设置top值——不同设备宽高比差异大,大屏手机宽度更大,计算出的top值会过高,导致文本位置偏移甚至超出图片容器。正确的做法是基于图片容器的高度定位,而非屏幕宽度。
方法1:关联图片容器高度计算top值
先定义图片容器高度变量,再用该高度减去文本区域的预估高度来设置top,保证不同设备上文本始终贴在图片底部附近:
final imageHeight = MediaQuery.of(context).size.height / 3; Stack(children: <Widget>[ GestureDetector( onTap: (() => Navigator.of(context).push( Transitions( transitionType: TransitionType.fade, curve: Curves.bounceInOut, duration: const Duration(milliseconds: 500), reverseCurve: Curves.bounceOut, widget: detailinvest(title)), )), child: Container( height: imageHeight, padding: const EdgeInsets.all(8), child: ClipRRect( borderRadius: BorderRadius.circular(20.0), child: Image.network( "https://arzenafees.com/wp-content/uploads/2021/06/Invest_Rudn_Enclave.jpg", fit: BoxFit.cover, )), ), ), Positioned.fill( // 用图片高度减去文本区域高度(可根据实际显示调整数值) top: imageHeight - 80, child: Align( alignment: Alignment.bottomCenter, child: ListTile( title: Text( 'Rudn Enclave', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, fontFamily: 'Roboto'), ), subtitle: Row( children: [ Icon( Icons.location_on, color: Constants.colorMain, ), Expanded( child: Text( 'Adiala Road, Rawalpindi, Rawalpindi Cantonment, Rawalpindi District, Punjab, 46600, Pakistan', style: TextStyle( fontSize: MediaQuery.of(context).size.width / 45, ), ), ), ], ), )), ), ]),
方法2:改用Positioned直接固定底部(更简洁)
放弃Positioned.fill,直接用Positioned的left/right/bottom属性,把文本固定在图片容器底部,无需计算top值:
final imageHeight = MediaQuery.of(context).size.height / 3; Stack(children: <Widget>[ GestureDetector( onTap: (() => Navigator.of(context).push( Transitions( transitionType: TransitionType.fade, curve: Curves.bounceInOut, duration: const Duration(milliseconds: 500), reverseCurve: Curves.bounceOut, widget: detailinvest(title)), )), child: Container( height: imageHeight, padding: const EdgeInsets.all(8), child: ClipRRect( borderRadius: BorderRadius.circular(20.0), child: Image.network( "https://arzenafees.com/wp-content/uploads/2021/06/Invest_Rudn_Enclave.jpg", fit: BoxFit.cover, )), ), ), // 直接定位在图片底部,左右铺满,内边距和图片容器对应 Positioned( left: 16, right: 16, bottom: 8, child: ListTile( contentPadding: EdgeInsets.zero, title: Text( 'Rudn Enclave', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, fontFamily: 'Roboto'), ), subtitle: Row( children: [ Icon( Icons.location_on, color: Constants.colorMain, ), Expanded( child: Text( 'Adiala Road, Rawalpindi, Rawalpindi Cantonment, Rawalpindi District, Punjab, 46600, Pakistan', style: TextStyle( fontSize: MediaQuery.of(context).size.width / 45, ), ), ), ], ), ), ), ]),
额外优化:GridView适配
如果GridView子项在不同屏幕上比例失调,可以把childAspectRatio改成基于屏幕宽高比计算,保证显示一致性:
childAspectRatio: MediaQuery.of(context).size.width / (MediaQuery.of(context).size.height / 1.8),
内容的提问来源于stack exchange,提问作者Ali Punjabi
相关产品推荐
相关产品推荐

