主要是通过 RenderObject 获取widget 相对屏幕的坐标, 从而动态设置 Dialog 的位置.
函数 getTransformTo(RenderObject ancestor) 参数 ancestor 为null, 表示相对根组件的位置(也就是相对屏幕的位置)
代码示例如下:
所点击的widget
class CloseTap extends StatefulWidget { @override _CloseTapTapState createState() => _CloseTapTapState(); } class _CloseTapTapState extends State<CloseTap> with WidgetsBindingObserver { void _onAfterRendering(Duration timeStamp) { RenderObject renderObject = context.findRenderObject(); Size size = renderObject.paintBounds.size; var vector3 = renderObject.getTransformTo(null)?.getTranslation(); CommonUtils.showChooseDialog(context, size, vector3); } @override Widget build(BuildContext context) { return GestureDetector( child: Icon(Icons.close), onTapDown: (TapDownDetails details) { WidgetsBinding.instance.addPostFrameCallback(_onAfterRendering); setState(() {}); }, ); } }
根据所点击的widget的坐标, 展示dialog
class CommonUtils { static showChooseDialog(BuildContext context, Size size, var vector3) { final double wx = size.height; final double dx = vector3[0]; final double dy = vector3[1]; final double w = MediaQuery.of(context).size.width; final double h = MediaQuery.of(context).size.height; return showDialog( context: context, builder: (BuildContext context) { return new Material( color: Colors.transparent, child: Container( double.infinity, height: double.infinity, child: Stack( children: <Widget>[ GestureDetector( child: Container( double.infinity, height: double.infinity, child: Text(''), ), onTap: () { Navigator.of(context).pop(); }, ), Positioned( left: 10.0, top: dy < h / 2 ? dy + wx / 2 : null, bottom: dy < h / 2 ? null : (h - dy + wx / 2), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.all( Radius.circular(10.0), ), color: Colors.white, ), w - 20.0, child: GestureDetector( child: Column( children: <Widget>[ ListTile( leading: Icon(Icons.highlight_off), title: Text('不感兴趣'), subtitle: Text('减少这类内容')), Divider(), ListTile( leading: Icon(Icons.error_outline), title: Text('反馈垃圾内容'), subtitle: Text('低俗、标题党等')), Divider(), ListTile( leading: Icon(Icons.not_interested), title: Text('屏蔽'), subtitle: Text('请选择屏蔽的广告类型')), Divider(), ListTile( leading: Icon(Icons.help_outline), title: Text('为什么看到此广告'), ), ], ), onTap: () { Navigator.of(context).pop(); }, ), ), ), Positioned( left: dx - 10.0, top: dy < h / 2 ? dy - wx / 2 : null, bottom: dy < h / 2 ? null : (h - dy - wx / 2), child: ClipPath( clipper: Triangle(dir: dy - h / 2), child: Container( 30.0, height: 30.0, color: Colors.white, child: null, ), ), ), ], ), ), ); }, ); } }
小三角组件, 利用贝塞尔曲线api, 以及 CustomClipper 的使用
class Triangle extends CustomClipper<Path> { double dir; Triangle({this.dir}); @override Path getClip(Size size) { var path = Path(); double w = size.width; double h = size.height; if (dir < 0) { path.moveTo(0, h); path.quadraticBezierTo(0, 0, w * 2 / 3, 0); path.quadraticBezierTo(w / 4, h / 2, w, h); } else { path.quadraticBezierTo(0, h / 2, w * 2 / 3, h); path.quadraticBezierTo(w / 3, h / 3, w, 0); path.lineTo(0, 0); } return path; } @override bool shouldReclip(CustomClipper<Path> oldClipper) => false; }