此Flutter代码通过WebSocket连接到ESP32,并显示从设备接收的数据。应用程序有一个选项卡栏,允许在不同连接的ESP32设备之间切换。我可以通过打开“添加工作站”对话框添加新的ESP32设备,在该对话框中我可以输入设备的名称、IP地址和端口。当我添加新设备时,为该设备创建了一个新的选项卡,应用程序使用提供的IP地址和端口通过WebSocket连接到设备。我还可以删除任何添加的设备。我遇到的问题是,当我添加新设备时,创建了一个新的选项卡,但ESP32的名称,这些值和重新连接按钮不显示在TabBarView中。
在这件事上我需要一些帮助。我不知道我做错了什么。
先谢谢你的帮助。
遵循代码:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:web_socket_channel/io.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
title: 'ESP3" Monitor',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
MyHomePageState createState() => MyHomePageState();
}
class Station {
late final String name;
late final String ipAddress;
late final String port;
double rangeData;
double forceData;
Station({
required this.name,
required this.ipAddress,
required this.port,
this.rangeData = 0.0,
this.forceData = 0.0,
});
}
class MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final List<Station> _stations = [];
late TabController _tabController;
late IOWebSocketChannel _channel;
late TextEditingController _stationName;
late TextEditingController _ipAddress;
late TextEditingController _port;
String? selectedTab = tabs[0].text;
var tabName = "";
static List<Tab> tabs = [
const Tab(text: ""),
];
@override
void initState() {
_stationName = TextEditingController();
_ipAddress = TextEditingController();
_port = TextEditingController();
_tabController = TabController(vsync: this, length: tabs.length);
super.initState();
}
@override
void dispose() {
_stationName.dispose();
_ipAddress.dispose();
_port.dispose();
_tabController.dispose();
super.dispose();
}
void _connectToEsp32(String ipAddress, String port, Station station) {
_channel = IOWebSocketChannel.connect('ws://$ipAddress:$port');
_channel.stream.listen(
(data) {
final Map<String, dynamic> receivedData = jsonDecode(data);
setState(() {
final int index = _stations.indexWhere((element) => element.name == station.name);
if (index >= 0) {
station.rangeData = receivedData['range'];
station.forceData = receivedData['force'];
_tabController.animateTo(index);
}
});
},
);
}
void _openAddStationDialog(BuildContext context) {
_stationName.text = "";
_ipAddress.text = "";
_port.text = "";
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Add Station'),
content: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextFormField(
decoration: const InputDecoration(
suffixIcon: Icon(Icons.list),
labelText: 'Station Name',
),
controller: _stationName,
validator: (value) {
tabName = value!;
if (value.isEmpty) {
return 'Please enter a Station Name';
}
return null;
},
),
TextFormField(
decoration: const InputDecoration(
suffixIcon: Icon(Icons.computer),
labelText: 'IP Address',
),
controller: _ipAddress,
validator: (value) {
if (value!.isEmpty) {
return 'Please enter IP address';
}
return null;
},
),
TextFormField(
decoration: const InputDecoration(
suffixIcon: Icon(Icons.settings_applications),
labelText: 'Port',
),
controller: _port,
validator: (value) {
if (value!.isEmpty) {
return 'Please enter Port';
}
return null;
},
),
],
),
),
actions: <Widget>[
ElevatedButton.icon(
onPressed: () {
setState(() {});
if (_formKey.currentState!.validate()) {
final station = Station(
name: _stationName.text,
ipAddress: _ipAddress.text,
port: _port.text,
);
_stations.add(station);
tabs.add(Tab(text: tabName));
_connectToEsp32(station.ipAddress, station.port, station);
Navigator.pop(context);
}
},
icon: const Icon(
Icons.add,
),
label: const Text('Add'),
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return DefaultTabController(
initialIndex: 0,
length: tabs.length,
child: Scaffold(
appBar: AppBar(
title: const Text('Station Monitor'),
actions: <Widget>[
ElevatedButton.icon(
onPressed: () {
_openAddStationDialog(context);
},
icon: const Icon(
Icons.keyboard_control_rounded,
),
label: const Text('Add Station'),
),
ElevatedButton.icon(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Select tab to remove"),
content: tabs.isNotEmpty
? StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return DropdownButton<String>(
items: tabs.map((tab) {
return DropdownMenuItem<String>(
value: tab.text,
child: Text(tab.text ?? ""),
);
}).toList(),
onChanged: (String? value) {
setState(() {
selectedTab = value;
});
},
value: selectedTab,
);
})
: Container(),
actions: <Widget>[
ElevatedButton(
child: const Text("Remove"),
onPressed: () {
if (tabs.isNotEmpty) {
setState(() {
int index = tabs.indexWhere((tab) => tab.text == selectedTab);
tabs.removeAt(index);
selectedTab = tabs.isNotEmpty ? tabs[0].text : null;
});
Navigator.of(context).pop();
}
},
),
],
);
},
);
},
icon: const Icon(Icons.remove),
label: const Text('Remove Station'),
),
],
bottom: TabBar(tabs: tabs),
),
body: TabBarView(
controller: _tabController,
children: _stations.map((station) {
return Column(
children: <Widget>[
Column(children: <Widget>[
const SizedBox(
height: 30,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Station Name: ${station.name}"),
const SizedBox(width: 10),
PopupMenuButton<int>(
itemBuilder: (context) => [
const PopupMenuItem(
value: 1,
child: Text("Reconnect"),
),
],
onSelected: (value) {
if (value == 1) {
_connectToEsp32(station.ipAddress, station.port, station);
}
},
),
],
),
]),
Text("Range: ${station.rangeData}"),
Text("Force: ${station.forceData}"),
],
);
}).toList(),
),
),
);
}
}
1条答案
按热度按时间wpx232ag1#
TabController
的重新构建(或者更确切地说是改变它的状态)有点棘手,所以解决方案之一是在需要重新构建它(它的状态)时更新它的键。下面是一个完整的工作示例,说明如何以编程方式添加一个新选项卡,以便您可以根据需要调整它: