library flutterptcontrol; import 'dart:math' as MATH; import 'package:flutter/material.dart'; typedef void PtCallback(int status); //pan-tilt Control class PtControlWidget extends StatefulWidget { PtControlWidget( {@required this.innerRadius, @required this.outerRadius, @required this.callback}); ///内径 final double innerRadius; ///外径 final double outerRadius; ///点击回调 final PtCallback callback; @override State createState() { return _PtControlWidgetState(); } } class _PtControlWidgetState extends State { int status = -1; /* 3.获取控件的坐标: RenderBox renderBox = anchorKey.currentContext.findRenderObject(); var offset = renderBox.localToGlobal(Offset.zero); 控件的横坐标:offset.dx 控件的纵坐标:offset.dy 如果想获得控件正下方的坐标: RenderBox renderBox = anchorKey.currentContext.findRenderObject(); var offset = renderBox.localToGlobal(Offset(0.0, renderBox.size.height));    控件下方的横坐标:offset.dx    控件下方的纵坐标:offset.dy ———————————————— 版权声明:本文为CSDN博主「笨鸟不飞 ≧0≦」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/baidu_34120295/article/details/86495861 */ //获得本组件中心点对象:Point(offset.dx, offset.dy) //Another exception was thrown: type 'Point' is not a subtype of type 'Point' MATH.Point getCenterPoint() { RenderBox renderBox = context.findRenderObject(); //控件中心坐标:(offset.dx, offset.dy) var offset = renderBox.localToGlobal(Offset(renderBox.size.width / 2, renderBox.size.height / 2)); print('(offset.dx, offset.dy) = (${offset.dx}, ${offset.dy})'); return MATH.Point(offset.dx.toInt(), offset.dy.toInt()); } @override Widget build(BuildContext context) { return GestureDetector( onPanDown: (details) { //坐标系转换 var x = details.localPosition.dx - widget.outerRadius; var y = -(details.localPosition.dy - widget.outerRadius); //判断点击位置是否超出范围 var dis = MATH.Point(x, y).distanceTo(MATH.Point(0, 0)); /* 计算圆周上坐标点的公式,其中圆心坐标(xo,yo),r为圆半径,α为0~2π,(x,y)为圆上的点 x = xo + r * cos(α) y = yo + r * sin(α) 原点坐标为(0, 0),x轴向左为正,y轴向上为正,逆时针方向的8个点的弧度为 0 π * 1/8 1 π * 3/8 2 π * 5/8 3 π * 7/8 4 π * 9/8 5 π * 11/8 6 π * 13/8 7 π * 15/8 */ // dart 计算原点坐标为(0, 0),x轴向左为正,y轴向上为正,逆时针方向的8个点的坐标 List listInnerPoint = List.generate(8, (index) { double _radians = MATH.pi * (index * 2 + 1) / 8; //print('index = $index'); return { 'x': MATH.cos(_radians) * widget.innerRadius, 'y': MATH.sin(_radians) * widget.innerRadius, }; }); // dart 计算原点坐标为(0, 0),x轴向左为正,y轴向上为正,逆时针方向的8个点的坐标 List listOuterPoint = List.generate(8, (index) { double _radians = MATH.pi * (index * 2 + 1) / 8; //print('index = $index'); return { 'x': MATH.cos(_radians) * widget.outerRadius, 'y': MATH.sin(_radians) * widget.outerRadius, }; }); print('listInnerPoint = ${listInnerPoint}'); print('[x, y] = [$x, $y]'); //超出范围 if (dis > widget.outerRadius) { status = -1; } else if (dis < widget.innerRadius) { //还原 status = 8; } else if (x > listOuterPoint[1]['x'] || (y > 0 && x > listInnerPoint[1]['x'] && y < listInnerPoint[1]['y'] + (x - listInnerPoint[1]['x']) / MATH.tan(MATH.pi / 8)) || (y < 0 && x > listInnerPoint[6]['x'] && y > listInnerPoint[6]['y'] - (x - listInnerPoint[6]['x']) / MATH.tan(MATH.pi / 8))) { if (y > listOuterPoint[0]['y'] || y > listInnerPoint[0]['y'] && x < listInnerPoint[0]['x'] + (y - listInnerPoint[0]['y']) / MATH.tan(MATH.pi / 8)) { //右上 status = 1; } else if (y > listInnerPoint[7]['y'] || y > listOuterPoint[7]['y'] && x > listInnerPoint[7]['x'] + (listInnerPoint[7]['y'] - y) / MATH.tan(MATH.pi / 8)) { //右 status = 0; } else { //右下 status = 7; } } else if (x > listInnerPoint[2]['x'] || y > 0 && x > listOuterPoint[2]['x'] && y > listInnerPoint[2]['y'] + (listInnerPoint[2]['x'] - x) / MATH.tan(MATH.pi / 8) || y < 0 && x < listInnerPoint[5]['x'] && y < listInnerPoint[5]['y'] - (listInnerPoint[5]['x'] - x) / MATH.tan(MATH.pi / 8)) { if (y > 0) { //上 status = 2; } else { //下 status = 6; } } else { if (y > listOuterPoint[3]['y'] || y > listInnerPoint[3]['y'] && x > listOuterPoint[3]['x'] - (y - listOuterPoint[3]['y']) / MATH.tan(MATH.pi / 8)) { //左上 status = 3; } else if (y > listInnerPoint[4]['y'] || y > listOuterPoint[4]['y'] && x < listOuterPoint[4]['x'] + (listInnerPoint[4]['y'] - y) / MATH.tan(MATH.pi / 8)) { //左 status = 4; } else { //左下 status = 5; } } widget.callback(status); setState(() {}); }, onPanEnd: (details) { status = -1; setState(() {}); }, ///点击回调 /// status == -1 超出范围 /// status == 0 右 /// status == 1 右上 /// status == 2 上 /// status == 3 左上 /// status == 4 左 /// status == 5 左下 /// status == 6 下 /// status == 7 右下 /// status == 8 还原 /// color: Colors.black12 child: Stack( children: [ Image.asset( null == widget.callback ? 'images/pt_background_disable.png' : 'images/pt_background_enable.png', package: 'flutterptcontrol'), status == 0 ? Image.asset('images/pt_right.png', package: 'flutterptcontrol') : SizedBox.shrink(), status == 1 ? Image.asset('images/pt_right_top.png', package: 'flutterptcontrol') : SizedBox.shrink(), status == 2 ? Image.asset('images/pt_top.png', package: 'flutterptcontrol') : SizedBox.shrink(), status == 3 ? Image.asset('images/pt_left_top.png', package: 'flutterptcontrol') : SizedBox.shrink(), status == 4 ? Image.asset('images/pt_left.png', package: 'flutterptcontrol') : SizedBox.shrink(), status == 5 ? Image.asset('images/pt_left_bottom.png', package: 'flutterptcontrol') : SizedBox.shrink(), status == 6 ? Image.asset('images/pt_bottom.png', package: 'flutterptcontrol') : SizedBox.shrink(), status == 7 ? Image.asset('images/pt_right_bottom.png', package: 'flutterptcontrol') : SizedBox.shrink(), // status == 8 // ? Image.asset('images/pt_center.png', package: 'flutterptcontrol') // : SizedBox.shrink(), ], ), ); } }