vector_normal.dart 9.9 KB

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