主要是通過 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( width: double.infinity, height: double.infinity, child: Stack( children: <Widget>[ GestureDetector( child: Container( width: 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, ), width: 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( width: 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; }