vector_normal.dart 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. // tools/vector_normal.dart
  2. import 'package:flutter/material.dart';
  3. import 'package:rational/rational.dart';
  4. import '../utils/rationalsqrt2.dart';
  5. class VectorNormalPage extends StatefulWidget {
  6. @override
  7. _VectorNormalPageState createState() => _VectorNormalPageState();
  8. }
  9. class _VectorNormalPageState extends State<VectorNormalPage> {
  10. final List<TextEditingController> _controllers = List.generate(9, (_) => TextEditingController());
  11. final List<FocusNode> _focusNodes = List.generate(9, (_) => FocusNode());
  12. TextEditingController? _currentFocusedController;
  13. FocusNode? _currentFocusedNode;
  14. String _result = '';
  15. bool _isCalculating = false;
  16. @override
  17. void initState() {
  18. super.initState();
  19. // 为每个焦点节点添加监听器
  20. for (int i = 0; i < _focusNodes.length; i++) {
  21. _focusNodes[i].addListener(() {
  22. if (_focusNodes[i].hasFocus) {
  23. _setCurrentFocusedController(_controllers[i], _focusNodes[i]);
  24. }
  25. });
  26. }
  27. }
  28. @override
  29. void dispose() {
  30. for (var controller in _controllers) {
  31. controller.dispose();
  32. }
  33. for (var focusNode in _focusNodes) {
  34. focusNode.dispose();
  35. }
  36. super.dispose();
  37. }
  38. // 设置当前聚焦的控制器和焦点节点
  39. void _setCurrentFocusedController(TextEditingController controller, FocusNode focusNode) {
  40. setState(() {
  41. _currentFocusedController = controller;
  42. _currentFocusedNode = focusNode;
  43. });
  44. }
  45. // 插入根号到当前光标位置
  46. void _insertSqrt() {
  47. if (_currentFocusedController == null || _currentFocusedNode == null) return;
  48. final controller = _currentFocusedController!;
  49. final focusNode = _currentFocusedNode!;
  50. final text = controller.text;
  51. final selection = controller.selection;
  52. final newText = text.substring(0, selection.start) + '√' + text.substring(selection.end);
  53. final newCursorPosition = selection.start + 1;
  54. controller.value = TextEditingValue(
  55. text: newText,
  56. selection: TextSelection.collapsed(offset: newCursorPosition),
  57. );
  58. // 确保焦点保持在当前输入框
  59. if (!focusNode.hasFocus) {
  60. focusNode.requestFocus();
  61. }
  62. // 延迟设置光标位置,避免全选
  63. Future.delayed(Duration(milliseconds: 10), () {
  64. controller.selection = TextSelection.collapsed(offset: newCursorPosition);
  65. });
  66. }
  67. // 计算法向量
  68. void _calculateNormal() {
  69. setState(() {
  70. _isCalculating = true;
  71. _result = '';
  72. });
  73. try {
  74. // 解析三个点的坐标
  75. List<Vector3> points = [];
  76. for (int i = 0; i < 3; i++) {
  77. final x = RationalExtend.parse(_controllers[i * 3].text.isEmpty ? '0' : _controllers[i * 3].text);
  78. final y = RationalExtend.parse(_controllers[i * 3 + 1].text.isEmpty ? '0' : _controllers[i * 3 + 1].text);
  79. final z = RationalExtend.parse(_controllers[i * 3 + 2].text.isEmpty ? '0' : _controllers[i * 3 + 2].text);
  80. points.add(Vector3(x, y, z));
  81. }
  82. // 计算两个向量
  83. final vector1 = points[1] - points[0];
  84. final vector2 = points[2] - points[0];
  85. // 计算叉积(法向量)
  86. final normal = vector1.cross(vector2);
  87. setState(() {
  88. _result = '法向量:(${normal.x}, ${normal.y}, ${normal.z})';
  89. });
  90. } catch (e) {
  91. setState(() {
  92. _result = '计算错误:$e';
  93. });
  94. } finally {
  95. setState(() {
  96. _isCalculating = false;
  97. });
  98. }
  99. }
  100. // 清除所有输入
  101. void _clearAll() {
  102. for (var controller in _controllers) {
  103. controller.clear();
  104. }
  105. setState(() {
  106. _result = '';
  107. _currentFocusedController = null;
  108. _currentFocusedNode = null;
  109. });
  110. }
  111. @override
  112. Widget build(BuildContext context) {
  113. return Scaffold(
  114. appBar: AppBar(
  115. title: Text('三点法向量计算'),
  116. backgroundColor: Colors.white,
  117. elevation: 1,
  118. ),
  119. body: SingleChildScrollView(
  120. padding: EdgeInsets.all(16),
  121. child: Column(
  122. crossAxisAlignment: CrossAxisAlignment.start,
  123. children: [
  124. _buildInputSection('点A', 0),
  125. SizedBox(height: 16),
  126. _buildInputSection('点B', 3),
  127. SizedBox(height: 16),
  128. _buildInputSection('点C', 6),
  129. SizedBox(height: 24),
  130. _buildControlButtons(),
  131. SizedBox(height: 24),
  132. _buildResultSection(),
  133. SizedBox(height: 24),
  134. _buildInstructions(),
  135. ],
  136. ),
  137. ),
  138. );
  139. }
  140. Widget _buildInputSection(String title, int startIndex) {
  141. return Card(
  142. elevation: 2,
  143. child: Padding(
  144. padding: EdgeInsets.all(16),
  145. child: Column(
  146. crossAxisAlignment: CrossAxisAlignment.start,
  147. children: [
  148. Text(
  149. title,
  150. style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
  151. ),
  152. SizedBox(height: 12),
  153. Row(
  154. children: [
  155. _buildCoordinateInput('x', _controllers[startIndex], _focusNodes[startIndex]),
  156. SizedBox(width: 12),
  157. _buildCoordinateInput('y', _controllers[startIndex + 1], _focusNodes[startIndex + 1]),
  158. SizedBox(width: 12),
  159. _buildCoordinateInput('z', _controllers[startIndex + 2], _focusNodes[startIndex + 2]),
  160. ],
  161. ),
  162. ],
  163. ),
  164. ),
  165. );
  166. }
  167. Widget _buildCoordinateInput(String label, TextEditingController controller, FocusNode focusNode) {
  168. return Expanded(
  169. child: Column(
  170. crossAxisAlignment: CrossAxisAlignment.start,
  171. children: [
  172. Text(
  173. label,
  174. style: TextStyle(fontSize: 14, color: Colors.grey[600]),
  175. ),
  176. SizedBox(height: 4),
  177. TextField(
  178. controller: controller,
  179. focusNode: focusNode,
  180. decoration: InputDecoration(
  181. hintText: '0',
  182. border: OutlineInputBorder(),
  183. contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
  184. ),
  185. keyboardType: TextInputType.number,
  186. ),
  187. ],
  188. ),
  189. );
  190. }
  191. Widget _buildControlButtons() {
  192. return Row(
  193. children: [
  194. Expanded(
  195. flex: 3,
  196. child: ElevatedButton(
  197. onPressed: _isCalculating ? null : _calculateNormal,
  198. style: ElevatedButton.styleFrom(
  199. backgroundColor: Colors.blue,
  200. padding: EdgeInsets.symmetric(vertical: 12),
  201. ),
  202. child: _isCalculating
  203. ? SizedBox(
  204. width: 20,
  205. height: 20,
  206. child: CircularProgressIndicator(
  207. strokeWidth: 2,
  208. valueColor: AlwaysStoppedAnimation(Colors.white),
  209. ),
  210. )
  211. : Text(
  212. '计算法向量',
  213. style: TextStyle(fontSize: 16, color: Colors.white),
  214. ),
  215. ),
  216. ),
  217. SizedBox(width: 12),
  218. Expanded(
  219. flex: 2,
  220. child: OutlinedButton(
  221. onPressed: _clearAll,
  222. style: OutlinedButton.styleFrom(
  223. padding: EdgeInsets.symmetric(vertical: 12),
  224. ),
  225. child: Text('清除', style: TextStyle(fontSize: 16)),
  226. ),
  227. ),
  228. SizedBox(width: 12),
  229. // 使用 GestureDetector 而不是 ElevatedButton 来避免焦点转移
  230. GestureDetector(
  231. onTap: _insertSqrt,
  232. child: Container(
  233. width: 60,
  234. padding: EdgeInsets.symmetric(vertical: 12),
  235. decoration: BoxDecoration(
  236. color: Colors.blue,
  237. borderRadius: BorderRadius.circular(4),
  238. ),
  239. child: Text(
  240. '√',
  241. style: TextStyle(fontSize: 18, color: Colors.white),
  242. textAlign: TextAlign.center,
  243. ),
  244. ),
  245. ),
  246. ],
  247. );
  248. }
  249. Widget _buildResultSection() {
  250. if (_result.isEmpty) return SizedBox();
  251. return Card(
  252. color: Colors.grey[50],
  253. elevation: 2,
  254. child: Padding(
  255. padding: EdgeInsets.all(16),
  256. child: Column(
  257. crossAxisAlignment: CrossAxisAlignment.start,
  258. children: [
  259. Text(
  260. '计算结果',
  261. style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
  262. ),
  263. SizedBox(height: 12),
  264. Text(
  265. _result,
  266. style: TextStyle(fontSize: 16, color: Colors.blue[700]),
  267. ),
  268. SizedBox(height: 8),
  269. Text(
  270. '说明:法向量是通过向量AB和向量AC的叉积计算得到',
  271. style: TextStyle(fontSize: 12, color: Colors.grey[600]),
  272. ),
  273. ],
  274. ),
  275. ),
  276. );
  277. }
  278. Widget _buildInstructions() {
  279. return Card(
  280. color: Colors.grey[50],
  281. elevation: 2,
  282. child: Padding(
  283. padding: EdgeInsets.all(16),
  284. child: Column(
  285. crossAxisAlignment: CrossAxisAlignment.start,
  286. children: [
  287. Text(
  288. '使用说明',
  289. style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
  290. ),
  291. SizedBox(height: 12),
  292. Text(
  293. '√2/2请输入为1/2√2\n'
  294. '如果已知两向量坐标,将一个点设为原点即可',
  295. style: TextStyle(fontSize: 14, color: Colors.grey[700]),
  296. ),
  297. ],
  298. ),
  299. ),
  300. );
  301. }
  302. }
  303. // 三维向量类,使用RationalExtend进行计算
  304. class Vector3 {
  305. final RationalExtend x, y, z;
  306. Vector3(this.x, this.y, this.z);
  307. // 向量减法
  308. Vector3 operator -(Vector3 other) {
  309. return Vector3(x - other.x, y - other.y, z - other.z);
  310. }
  311. // 向量叉积
  312. Vector3 cross(Vector3 other) {
  313. final newX = y * other.z - z * other.y;
  314. final newY = z * other.x - x * other.z;
  315. final newZ = x * other.y - y * other.x;
  316. return Vector3(newX, newY, newZ);
  317. }
  318. @override
  319. String toString() {
  320. return '($x, $y, $z)';
  321. }
  322. }