// tools/vector_normal.dart import 'package:flutter/material.dart'; import '../utils/rationalsqrt2.dart'; class VectorNormalPage extends StatefulWidget { @override _VectorNormalPageState createState() => _VectorNormalPageState(); } class _VectorNormalPageState extends State { final List _controllers = List.generate(9, (_) => TextEditingController()); final List _focusNodes = List.generate(9, (_) => FocusNode()); TextEditingController? _currentFocusedController; FocusNode? _currentFocusedNode; String _result = ''; bool _isCalculating = false; @override void initState() { super.initState(); // 为每个焦点节点添加监听器 for (int i = 0; i < _focusNodes.length; i++) { _focusNodes[i].addListener(() { if (_focusNodes[i].hasFocus) { _setCurrentFocusedController(_controllers[i], _focusNodes[i]); } }); } } @override void dispose() { for (var controller in _controllers) { controller.dispose(); } for (var focusNode in _focusNodes) { focusNode.dispose(); } super.dispose(); } // 设置当前聚焦的控制器和焦点节点 void _setCurrentFocusedController(TextEditingController controller, FocusNode focusNode) { setState(() { _currentFocusedController = controller; _currentFocusedNode = focusNode; }); } // 插入根号到当前光标位置 void _insertSqrt() { if (_currentFocusedController == null || _currentFocusedNode == null) return; final controller = _currentFocusedController!; final focusNode = _currentFocusedNode!; final text = controller.text; final selection = controller.selection; final newText = text.substring(0, selection.start) + '√' + text.substring(selection.end); final newCursorPosition = selection.start + 1; controller.value = TextEditingValue( text: newText, selection: TextSelection.collapsed(offset: newCursorPosition), ); // 确保焦点保持在当前输入框 if (!focusNode.hasFocus) { focusNode.requestFocus(); } // 延迟设置光标位置,避免全选 Future.delayed(Duration(milliseconds: 10), () { controller.selection = TextSelection.collapsed(offset: newCursorPosition); }); } // 计算法向量 void _calculateNormal() { setState(() { _isCalculating = true; _result = ''; }); try { // 解析三个点的坐标 List points = []; for (int i = 0; i < 3; i++) { final x = RationalExtend.parse(_controllers[i * 3].text.isEmpty ? '0' : _controllers[i * 3].text); final y = RationalExtend.parse(_controllers[i * 3 + 1].text.isEmpty ? '0' : _controllers[i * 3 + 1].text); final z = RationalExtend.parse(_controllers[i * 3 + 2].text.isEmpty ? '0' : _controllers[i * 3 + 2].text); points.add(Vector3(x, y, z)); } // 计算两个向量 final vector1 = points[1] - points[0]; final vector2 = points[2] - points[0]; // 计算叉积(法向量) final normal = vector1.cross(vector2); setState(() { _result = '法向量:(${normal.x}, ${normal.y}, ${normal.z})'; }); } catch (e) { setState(() { _result = '计算错误:$e'; }); } finally { setState(() { _isCalculating = false; }); } } // 清除所有输入 void _clearAll() { for (var controller in _controllers) { controller.clear(); } setState(() { _result = ''; _currentFocusedController = null; _currentFocusedNode = null; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('三点法向量计算'), backgroundColor: Colors.white, elevation: 1, ), body: SingleChildScrollView( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildInputSection('点A', 0), SizedBox(height: 16), _buildInputSection('点B', 3), SizedBox(height: 16), _buildInputSection('点C', 6), SizedBox(height: 24), _buildControlButtons(), SizedBox(height: 24), _buildResultSection(), SizedBox(height: 24), _buildInstructions(), ], ), ), ); } Widget _buildInputSection(String title, int startIndex) { return Card( elevation: 2, child: Padding( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), SizedBox(height: 12), Row( children: [ _buildCoordinateInput('x', _controllers[startIndex], _focusNodes[startIndex]), SizedBox(width: 12), _buildCoordinateInput('y', _controllers[startIndex + 1], _focusNodes[startIndex + 1]), SizedBox(width: 12), _buildCoordinateInput('z', _controllers[startIndex + 2], _focusNodes[startIndex + 2]), ], ), ], ), ), ); } Widget _buildCoordinateInput(String label, TextEditingController controller, FocusNode focusNode) { return Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TextStyle(fontSize: 14, color: Colors.grey[600]), ), SizedBox(height: 4), TextField( controller: controller, focusNode: focusNode, decoration: InputDecoration( hintText: '0', border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), keyboardType: TextInputType.number, ), ], ), ); } Widget _buildControlButtons() { return Row( children: [ Expanded( flex: 3, child: ElevatedButton( onPressed: _isCalculating ? null : _calculateNormal, style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, padding: EdgeInsets.symmetric(vertical: 12), ), child: _isCalculating ? SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.white), ), ) : Text( '计算法向量', style: TextStyle(fontSize: 16, color: Colors.white), ), ), ), SizedBox(width: 12), Expanded( flex: 2, child: OutlinedButton( onPressed: _clearAll, style: OutlinedButton.styleFrom( padding: EdgeInsets.symmetric(vertical: 12), ), child: Text('清除', style: TextStyle(fontSize: 16)), ), ), SizedBox(width: 12), // 使用 GestureDetector 而不是 ElevatedButton 来避免焦点转移 GestureDetector( onTap: _insertSqrt, child: Container( width: 60, padding: EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(4), ), child: Text( '√', style: TextStyle(fontSize: 18, color: Colors.white), textAlign: TextAlign.center, ), ), ), ], ); } Widget _buildResultSection() { if (_result.isEmpty) return SizedBox(); return Card( color: Colors.grey[50], elevation: 2, child: Padding( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '计算结果', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), SizedBox(height: 12), Text( _result, style: TextStyle(fontSize: 16, color: Colors.blue[700]), ), SizedBox(height: 8), Text( '说明:法向量是通过向量AB和向量AC的叉积计算得到', style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), ], ), ), ); } Widget _buildInstructions() { return Card( color: Colors.grey[50], elevation: 2, child: Padding( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '使用说明', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), SizedBox(height: 12), Text( '√2/2请输入为1/2√2\n' '如果已知两向量坐标,将一个点设为原点即可', style: TextStyle(fontSize: 14, color: Colors.grey[700]), ), ], ), ), ); } } // 三维向量类,使用RationalExtend进行计算 class Vector3 { final RationalExtend x, y, z; Vector3(this.x, this.y, this.z); // 向量减法 Vector3 operator -(Vector3 other) { return Vector3(x - other.x, y - other.y, z - other.z); } // 向量叉积 Vector3 cross(Vector3 other) { final newX = y * other.z - z * other.y; final newY = z * other.x - x * other.z; final newZ = x * other.y - y * other.x; return Vector3(newX, newY, newZ); } @override String toString() { return '($x, $y, $z)'; } }