您现在的位置是:网站首页> Flutter
Flutter项目实用技术收集
- Flutter
- 2024-12-18
- 193人已阅读
Flutter项目实用技术收集
读写NFC的例子
在Flutter中读写NFC需要使用第三方插件。一个常用的插件是 nfc_manager。以下是使用这个插件读写NFC的详细例子和说明:
1.首先,在 pubspec.yaml 文件中添加依赖:
dependencies:
flutter:
sdk: flutter
nfc_manager: ^3.2.0
2.在Android的 AndroidManifest.xml 文件中添加权限:
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="true" />
3.在iOS的 Info.plist 文件中添加权限:
<key>NFCReaderUsageDescription</key>
<string>需要NFC权限来读取和写入NFC标签</string>
4.现在,我们可以开始编写代码了。以下是一个包含读取和写入NFC功能的完整示例:
import 'package:flutter/material.dart';
import 'package:nfc_manager/nfc_manager.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ValueNotifier<dynamic> result = ValueNotifier(null);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('NFC Demo')),
body: SafeArea(
child: FutureBuilder<bool>(
future: NfcManager.instance.isAvailable(),
builder: (context, ss) => ss.data != true
? Center(child: Text('NFC 不可用'))
: Flex(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
direction: Axis.vertical,
children: [
Flexible(
flex: 2,
child: Container(
margin: EdgeInsets.all(4),
constraints: BoxConstraints.expand(),
decoration: BoxDecoration(border: Border.all()),
child: SingleChildScrollView(
child: ValueListenableBuilder<dynamic>(
valueListenable: result,
builder: (context, value, _) =>
Text('${value ?? ''}'),
),
),
),
),
Flexible(
flex: 3,
child: GridView.count(
padding: EdgeInsets.all(4),
crossAxisCount: 2,
childAspectRatio: 4,
crossAxisSpacing: 4,
mainAxisSpacing: 4,
children: [
ElevatedButton(
child: Text('Tag Read'), onPressed: _tagRead),
ElevatedButton(
child: Text('Ndef Write'),
onPressed: _ndefWrite),
],
),
),
],
),
),
),
),
);
}
void _tagRead() {
NfcManager.instance.startSession(onDiscovered: (NfcTag tag) async {
result.value = tag.data;
NfcManager.instance.stopSession();
});
}
void _ndefWrite() {
NfcManager.instance.startSession(onDiscovered: (NfcTag tag) async {
var ndef = Ndef.from(tag);
if (ndef == null || !ndef.isWritable) {
result.value = 'Tag is not ndef writable';
NfcManager.instance.stopSession(errorMessage: result.value);
return;
}
NdefMessage message = NdefMessage([
NdefRecord.createText('Hello NFC'),
NdefRecord.createUri(Uri.parse('https://flutter.dev')),
NdefRecord.createMime('text/plain', Uint8List.fromList('Hello'.codeUnits)),
NdefRecord.createExternal(
'com.example', 'mytype', Uint8List.fromList('mydata'.codeUnits)),
]);
try {
await ndef.write(message);
result.value = 'Success to "Ndef Write"';
NfcManager.instance.stopSession();
} catch (e) {
result.value = e;
NfcManager.instance.stopSession(errorMessage: result.value.toString());
return;
}
});
}
}
这个例子展示了如何读取NFC标签和写入NDEF消息到NFC标签。让我们详细解释一下这个代码:
1._tagRead 函数:
使用 NfcManager.instance.startSession 开始NFC会话。
当发现NFC标签时,将标签数据存储在 result 中并停止会话。
2._ndefWrite 函数:
同样使用 startSession 开始NFC会话。
检查标签是否支持NDEF格式并且可写。
创建一个 NdefMessage,包含多种类型的 NdefRecord:
文本记录
URI记录
MIME类型记录
外部类型记录
尝试将消息写入标签,如果成功则更新 result 并停止会话。
3.主UI:
使用 FutureBuilder 检查NFC是否可用。
如果NFC可用,显示一个包含结果显示区域和两个按钮的界面。
使用 ValueListenableBuilder 来动态显示 result 的值。
注意事项:
1.NFC功能需要真实设备测试,模拟器无法使用NFC功能。
2.在iOS上,NFC读取功能只在iPhone 7及以上机型支持,而写入功能则需要iPhone XS及以上机型。
3.在Android上,需要确保设备支持NFC并且已经打开NFC功能。
4.不同的NFC标签可能支持不同的操作,在实际应用中需要进行相应的错误处理。
5.NDEF是NFC数据交换格式的标准,但并非所有NFC标签都支持NDEF格式。在处理非NDEF格式的标签时,可能需要使用其他方法。
这个例子提供了NFC读写的基本框架,你可以根据具体需求进行修改和扩展。例如,你可能需要处理不同类型的NFC标签,或者实现更复杂的数据交换逻辑。
低功耗蓝牙例子
首先,你需要在pubspec.yaml文件中添加flutter_blue包:
dependencies:
flutter_blue: ^0.8.0
然后,这里是一个完整的示例代码:
import 'package:flutter/material.dart';
import 'package:flutter_blue/flutter_blue.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: BluetoothApp(),
);
}
}
class BluetoothApp extends StatefulWidget {
@override
_BluetoothAppState createState() => _BluetoothAppState();
}
class _BluetoothAppState extends State<BluetoothApp> {
FlutterBlue flutterBlue = FlutterBlue.instance;
List<ScanResult> scanResults = [];
bool isScanning = false;
BluetoothDevice? connectedDevice;
List<BluetoothService> services = [];
@override
void initState() {
super.initState();
// 监听蓝牙扫描结果
flutterBlue.scanResults.listen((results) {
setState(() {
scanResults = results;
});
});
}
// 开始扫描蓝牙设备
void startScan() {
setState(() {
isScanning = true;
scanResults.clear();
});
flutterBlue.startScan(timeout: Duration(seconds: 4));
Future.delayed(Duration(seconds: 4), () {
stopScan();
});
}
// 停止扫描
void stopScan() {
flutterBlue.stopScan();
setState(() {
isScanning = false;
});
}
// 连接到设备
void connect(BluetoothDevice device) async {
try {
await device.connect();
setState(() {
connectedDevice = device;
});
// 获取服务
services = await device.discoverServices();
} catch (e) {
print('连接失败: $e');
}
}
// 断开连接
void disconnect() {
if (connectedDevice != null) {
connectedDevice!.disconnect();
setState(() {
connectedDevice = null;
services.clear();
});
}
}
// 读取特征值
void readCharacteristic(BluetoothCharacteristic characteristic) async {
try {
List<int> value = await characteristic.read();
print('读取值: ${String.fromCharCodes(value)}');
} catch (e) {
print('读取失败: $e');
}
}
// 写入特征值
void writeCharacteristic(BluetoothCharacteristic characteristic, List<int> value) async {
try {
await characteristic.write(value);
print('写入成功');
} catch (e) {
print('写入失败: $e');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('蓝牙 BLE 示例'),
),
body: Column(
children: <Widget>[
ElevatedButton(
child: Text(isScanning ? '停止扫描' : '开始扫描'),
onPressed: isScanning ? stopScan : startScan,
),
Expanded(
child: ListView.builder(
itemCount: scanResults.length,
itemBuilder: (context, index) {
ScanResult result = scanResults[index];
return ListTile(
title: Text(result.device.name ?? 'Unknown device'),
subtitle: Text(result.device.id.toString()),
trailing: ElevatedButton(
child: Text(connectedDevice == result.device ? '断开' : '连接'),
onPressed: () {
if (connectedDevice == result.device) {
disconnect();
} else {
connect(result.device);
}
},
),
);
},
),
if (connectedDevice != null)
Expanded(
child: ListView.builder(
itemCount: services.length,
itemBuilder: (context, index) {
BluetoothService service = services[index];
return ExpansionTile(
title: Text('Service: ${service.uuid}'),
children: service.characteristics.map((c) {
return ListTile(
title: Text('Characteristic: ${c.uuid}'),
subtitle: Text('Properties: ${c.properties}'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
if (c.properties.read)
IconButton(
icon: Icon(Icons.visibility),
onPressed: () => readCharacteristic(c),
),
if (c.properties.write)
IconButton(
icon: Icon(Icons.edit),
onPressed: () => writeCharacteristic(c, [0x12, 0x34]), // 示例数据
),
],
),
);
}).toList(),
);
},
),
),
],
),
);
}
}
这个示例提供了以下功能:
1.扫描附近的BLE设备
2.显示扫描结果列表
3.连接到选定的设备
4.断开连接
5.显示连接设备的服务和特征
6.读取和写入特征值
下面是对代码的详细解释:
1.初始化:
我们使用FlutterBlue.instance获取FlutterBlue的单例。
在initState中,我们监听蓝牙扫描结果。
2.扫描:
startScan方法开始扫描,设置4秒超时。
stopScan方法停止扫描。
3.连接:
connect方法尝试连接到选定的设备,连接成功后发现服务。
disconnect方法断开当前连接的设备。
4.读写操作:
readCharacteristic方法读取特征值。
writeCharacteristic方法写入特征值。
5.UI构建:
主界面包含一个开始/停止扫描的按钮。
扫描结果显示在一个ListView中,每个项目都有一个连接/断开按钮。
当设备连接后,显示其服务和特征。
对于每个特征,如果支持读操作,显示一个"读取"按钮;如果支持写操作,显示一个"写入"按钮。
注意事项:
确保在Android的AndroidManifest.xml和iOS的Info.plist中添加必要的蓝牙权限。
这个示例仅供参考,实际应用中可能需要根据具体的BLE设备协议进行调整。
错误处理在这个示例中比较简单,实际应用中应该更加健
Android串口通讯
首先,我们需要创建一个新的 Flutter 项目。
Flutter 端代码:
在 lib/main.dart 文件中:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: SerialPortPage(),
);
}
}
class SerialPortPage extends StatefulWidget {
@override
_SerialPortPageState createState() => _SerialPortPageState();
}
class _SerialPortPageState extends State<SerialPortPage> {
static const platform = MethodChannel('com.example.serial_port');
String _portStatus = 'Closed';
String _receivedData = '';
Future<void> _openPort() async {
try {
final String result = await platform.invokeMethod('openPort');
setState(() {
_portStatus = result;
});
} on PlatformException catch (e) {
setState(() {
_portStatus = "Failed to open port: '${e.message}'.";
});
}
}
Future<void> _closePort() async {
try {
final String result = await platform.invokeMethod('closePort');
setState(() {
_portStatus = result;
});
} on PlatformException catch (e) {
setState(() {
_portStatus = "Failed to close port: '${e.message}'.";
});
}
}
Future<void> _sendData() async {
try {
await platform.invokeMethod('sendData', {'data': 'Hello, Serial Port!'});
} on PlatformException catch (e) {
print("Failed to send data: '${e.message}'.");
}
}
@override
void initState() {
super.initState();
platform.setMethodCallHandler((MethodCall call) async {
if (call.method == 'receiveData') {
setState(() {
_receivedData = call.arguments;
});
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Serial Port Communication'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Port Status: $_portStatus'),
ElevatedButton(
child: Text('Open Port'),
onPressed: _openPort,
),
ElevatedButton(
child: Text('Close Port'),
onPressed: _closePort,
),
ElevatedButton(
child: Text('Send Data'),
onPressed: _sendData,
),
Text('Received Data: $_receivedData'),
],
),
),
);
}
}
Android 端代码:
在 android/app/src/main/kotlin/com/example/your_project_name/MainActivity.kt 文件中:
package com.example.your_project_name
import android.hardware.usb.UsbManager
import android.content.Context
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import com.hoho.android.usbserial.driver.UsbSerialPort
import com.hoho.android.usbserial
import com.hoho.android.usbserial.driver.UsbSerialProber
import kotlinx.coroutines.*
class MainActivity: FlutterActivity() {
private val CHANNEL = "com.example.serial_port"
private var port: UsbSerialPort? = null
private var readJob: Job? = null
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"openPort" -> {
openPort(result)
}
"closePort" -> {
closePort(result)
}
"sendData" -> {
val data = call.argument<String>("data")
if (data != null) {
sendData(data, result)
} else {
result.error("INVALID_ARGUMENT", "Data is null", null)
}
}
else -> {
result.notImplemented()
}
}
}
}
private fun openPort(result: MethodChannel.Result) {
val manager = getSystemService(Context.USB_SERVICE) as UsbManager
val availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager)
if (availableDrivers.isEmpty()) {
result.error("NO_DRIVER", "No available drivers", null)
return
}
val driver = availableDrivers[0]
val connection = manager.openDevice(driver.device)
if (connection == null) {
result.error("CONNECTION_FAILED", "Could not open connection", null)
return
}
port = driver.ports[0]
try {
port?.open(connection)
port?.setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)
startReading()
result.success("Port opened successfully")
} catch (e: Exception) {
result.error("OPEN_FAILED", "Failed to open port: ${e.message}", null)
}
}
private fun closePort(result: MethodChannel.Result) {
readJob?.cancel()
try {
port?.close()
port = null
result.success("Port closed successfully")
} catch (e: Exception) {
result.error("CLOSE_FAILED", "Failed to close port: ${e.message}", null)
}
}
private fun sendData(data: String, result: MethodChannel.Result) {
port?.let {
try {
it.write(data.toByteArray(), 1000)
result.success(null)
} catch (e: Exception) {
result.error("WRITE_FAILED", "Failed to write data: ${e.message}", null)
}
} ?: result.error("PORT_CLOSED", "Port is not open", null)
}
private fun startReading() {
readJob = CoroutineScope(Dispatchers.IO).launch {
val buffer = ByteArray(1024)
while (isActive) {
try {
val bytesRead = port?.read(buffer, 1000) ?: 0
if (bytesRead > 0) {
val receivedData = String(buffer, 0, bytesRead)
withContext(Dispatchers.Main) {
MethodChannel(flutterEngine?.dartExecutor?.binaryMessenger, CHANNEL)
.invokeMethod("receiveData", receivedData)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
}
这个例子使用了 usb-serial-for-android 库来处理 Android 上的串口通信。你需要在 android/app/build.gradle 文件中添加以下依赖:
dependencies {
implementation 'com.github.mik3y:usb-serial-for-android:3.4.0'
}
现在让我们详细解释这个例子:
Flutter 端:
我们创建了一个 MethodChannel 来与 Android 原生代码通信。
提供了三个主要功能: 打开端口、关闭端口和发送数据。
使用 setMethodCallHandler 来接收来自 Android 的数据。
Android 端:
我们实现了与 Flutter 端对应的三个方法: openPort, closePort, 和 sendData。
openPort 方法查找可用的 USB 串口设备,打开连接,并设置串口参数。
closePort 方法关闭串口连接。
sendData 方法向串口写入数据。
startReading 方法在后台持续读取串口数据,并通过 MethodChannel 发送给 Flutter。
错误处理:
在每个操作中,我们都进行了适当的错误处理,并将错误信息返回给 Flutter。
协程:
我们使用 Kotlin 协程来处理串口的持续读取,这样可以避免阻塞主线程。
权限:
你需要在 android/app/src/main/AndroidManifest.xml 文件中添加 USB 权限:
<manifest ...>
<uses-feature android:name="android.hardware.usb.host" />
...
</manifest>
使用说明:
运行应用后,点击 "Open Port" 按钮来打开串口连接。
使用 "Send Data" 按钮发送预设的数据 ("Hello, Serial Port!")。
接收到的数据会显示在界面上。
完成后,点击 "Close Port" 按钮关闭连接。
注意事项:
这个例子假设只有一个串口设备连接。在实际应用中,你可能需要处理多个设备的情况。
串口参数 (波特率等) 是固定的。在实际应用中,你可能需要让用户能够配置这些参数。
这个例子只实现了 Android 端。如果你需要在 iOS 上使用串口,你需要编写相应的 iOS 原生代码。
在生产环境中,你可能需要添加更多的错误处理和日志记录。
这个例子使用了 USB 串口。如果你需要使用蓝牙串口或其他类型的串口,可能需要使用不同的库和 API。
最后,记得在使用 USB 设备时,要处理 USB 权限请求。你可能需要实现一个 BroadcastReceiver 来处理 UsbManager.ACTION_USB_DEVICE_ATTACHED 和 UsbManager.ACTION_USB_DEVICE_DETACHED 事件,以便在设备连接和断开时做出相应的处理。
当然,让我们继续深入讨论这个例子,并提供一些额外的建议和改进:
USB 权限处理:
在实际应用中,你需要处理 USB 权限请求。这里是一个简单的实现:
在 MainActivity.kt 中添加:
private val ACTION_USB_PERMISSION = "com.example.your_project_name.USB_PERMISSION"
private val usbReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (ACTION_USB_PERMISSION == intent.action) {
synchronized(this) {
val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
device?.apply {
// 权限被授予,可以打开设备
openPort(null)
}
} else {
// 权限被拒绝
Log.d("USB", "permission denied for device $device")
}
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val filter = IntentFilter(ACTION_USB_PERMISSION)
registerReceiver(usbReceiver, filter)
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(usbReceiver)
}
private fun requestUsbPermission() {
val manager = getSystemService(Context.USB_SERVICE) as UsbManager
val availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager)
if (availableDrivers.isEmpty()) {
return
}
val driver = availableDrivers[0]
val permissionIntent = PendingIntent.getBroadcast(this, 0, Intent(ACTION_USB_PERMISSION), 0)
manager.requestPermission(driver.device, permissionIntent)
}
多设备处理:
如果你需要处理多个串口设备,你可以修改 openPort 方法来显示一个设备选择对话框:
private fun openPort(result: MethodChannel.Result?) {
val manager = getSystemService(Context.USB_SERVICE) as UsbManager
val availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager)
if (availableDrivers.isEmpty()) {
result?.error("NO_DRIVER", "No available drivers", null)
return
}
AlertDialog.Builder(this)
.setTitle("Select a device")
.setItems(availableDrivers.map { it.device.deviceName }.toTypedArray()) { _, which ->
val driver = availableDrivers[which]
val connection = manager.openDevice(driver.device)
if (connection == null) {
result?.error("CONNECTION_FAILED", "Could not open connection", null)
return@setItems
}
port = driver.ports[0]
try {
port?.open(connection)
port?.setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)
startReading()
result?.success("Port opened successfully")
} catch (e: Exception) {
result?.error("OPEN_FAILED", "Failed to open port: ${e.message}", null)
}
}
.show()
}
可配置的串口参数 (续):
在 Flutter 端,我们可以添加一个设置页面:
class SettingsPage extends StatefulWidget {
@override
_SettingsPageState createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
int _baudRate = 115200;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Settings')),
body: ListView(
children: [
ListTile(
title: Text('Baud Rate'),
trailing: DropdownButton<int>(
value: _baudRate,
items: [9600, 19200, 38400, 57600, 115200].map((int value) {
return DropdownMenuItem<int>(
value: value,
child: Text(value.toString()),
);
}).toList(),
onChanged: (int? newValue) {
if (newValue != null) {
setState(() {
_baudRate = newValue;
});
}
},
),
),
],
),
);
}
}
然后在主页面添加一个按钮来打开设置页面,并在打开端口时传入波特率:
ElevatedButton(
child: Text('Settings'),
onPressed: () async {
await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SettingsPage()),
);
},
),
// 在 _openPort 方法中:
final int result = await platform.invokeMethod('openPort', {'baudRate': _baudRate});
在 Android 端,修改 openPort 方法以接受波特率参数:
private fun openPort(call: MethodCall, result: MethodChannel.Result) {
val baudRate = call.argument<Int>("baudRate") ?: 115200
// ... 其他代码保持不变
port?.setParameters(baudRate, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)
// ...
}
添加数据发送输入框:
在 Flutter 端,我们可以添加一个输入框让用户输入要发送的数据:
class _SerialPortPageState extends State<SerialPortPage> {
TextEditingController _dataController = TextEditingController();
// ... 其他代码保持不变
Future<void> _sendData() async {
try {
await platform.invokeMethod('sendData', {'data': _dataController.text});
_dataController.clear();
} on PlatformException catch (e) {
print("Failed to send data: '${e.message}'.");
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
// ... 其他代码保持不变
body: Column(
children: <Widget>[
// ... 其他控件
Padding(
padding: EdgeInsets.all(8.0),
child: TextField(
controller: _dataController,
decoration: InputDecoration(
labelText: 'Data to send',
border: OutlineInputBorder(),
),
),
),
ElevatedButton(
child: Text('Send Data'),
onPressed: _sendData,
),
// ... 其他控件
],
),
);
}
}
添加数据接收显示区域:
我们可以使用一个 ListView 来显示接收到的数据:
class _SerialPortPageState extends State<SerialPortPage> {
List<String> _receivedDataList = [];
ScrollController _scrollController = ScrollController();
// ... 其他代码保持不变
@override
void initState() {
super.initState();
platform.setMethodCallHandler((MethodCall call) async {
if (call.method == 'receiveData') {
setState(() {
_receivedDataList.add(call.arguments);
});
// 滚动到列表底部
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_scrollController.hasClients) {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: Duration(milliseconds: 300),
curve: Curves.easeOut,
);
}
});
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Serial Port Communication')),
body: Column(
children: <Widget>[
// ... 其他控件
Expanded(
child: ListView.builder(
controller: _scrollController,
itemCount: _receivedDataList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(_receivedDataList[index]),
);
},
),
),
// ... 其他控件
],
),
);
}
}
添加清除接收数据的功能:
ElevatedButton(
child: Text('Clear Received Data'),
onPressed: () {
setState(() {
_receivedDataList.clear();
});
},
),
添加数据发送格式选择:
我们可以让用户选择发送数据的格式 (ASCII 或 HEX):
enum DataFormat { ASCII, HEX }
class _SerialPortPageState extends State<SerialPortPage> {
DataFormat _dataFormat = DataFormat.ASCII;
// ... 其他代码保持不变
Future<void> _sendData() async {
try {
String dataToSend = _dataController.text;
if (_dataFormat == DataFormat.HEX) {
// 将 HEX 字符串转换为字节数组
dataToSend = String.fromCharCodes(
_dataController.text.split(' ').map((e) => int.parse(e, radix: 16))
);
}
await platform.invokeMethod('sendData', {'data': dataToSend});
_dataController.clear();
} on PlatformException catch (e) {
print("Failed to send data: '${e.message}'.");
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
// ... 其他代码保持不变
body: Column(
children: <Widget>[
// ... 其他控件
Row(
children: [
Text('Data Format:'),
Radio<DataFormat>(
value: DataFormat.ASCII,
groupValue: _dataFormat,
onChanged: (DataFormat? value) {
setState(() {
_dataFormat = value!;
});
},
),
Text('ASCII'),
Radio<DataFormat>(
value: DataFormat.HEX,
groupValue: _dataFormat,
onChanged: (DataFormat? value) {
setState(() {
_dataFormat = value!;
});
},
),
Text('HEX'),
],
),
// ... 其他控件
],
),
);
}
}
添加数据接收格式显示:
我们可以让用户选择以 ASCII 或 HEX 格式查看接收到的数据:
enum DisplayFormat { ASCII, HEX }
class _SerialPortPageState extends State<SerialPortPage> {
DisplayFormat _displayFormat = DisplayFormat.ASCII;
// ... 其他代码保持不变
String _formatReceivedData(String data) {
if (_displayFormat == DisplayFormat.ASCII) {
return data;
} else {
return data.codeUnits.map((e) => e.toRadixString(16).padLeft(2, '0')).join(' ');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
// ... 其他代码保持不变
body: Column(
children: <Widget>[
// ... 其他控件
Row(
children: [
Text('Display Format:'),
Radio<DisplayFormat>(
value: DisplayFormat.ASCII,
groupValue: _displayFormat,
onChanged: (DisplayFormat? value) {
setState(() {
_displayFormat = value!;
});
},
),
Text('ASCII'),
Radio<DisplayFormat>(
value: DisplayFormat.HEX,
groupValue: _displayFormat,
onChanged: (DisplayFormat? value) {
setState(() {
_displayFormat = value!;
});
},
),
Text('HEX'),
],
),
Expanded(
child: ListView.builder(
controller: _scrollController,
itemCount: _receivedDataList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(_formatReceivedData(_receivedDataList[index])),
);
},
),
),
// ... 其他控件
],
),
);
}
}
添加自动重连功能:
在 Android 端,我们可以添加一个自动重连的功能:
private var autoReconnect = false
private var reconnectJob: Job? = null
private fun startAutoReconnect() {
autoReconnect = true
reconnectJob = CoroutineScope(Dispatchers.IO).launch {
while (isActive && autoReconnect) {
if (port == null || !port!!.isOpen) {
try {
openPort(null)
} catch (e: Exception) {
Log.e("SerialPort", "Failed to reconnect: ${e.message}")
}
}
delay(5000) // 每5秒尝试重连一次
}
}
}
private fun stopAutoReconnect() {
autoReconnect = false
reconnectJob?.cancel()
}
在 Flutter 端,我们可以添加一个开关来控制自动重连:
bool _autoReconnect = false;
// ... 其他代码保持不变
Switch(
value: _autoReconnect,
onChanged: (value) {
setState(() {
_autoReconnect = value;
});
platform.invokeMethod('setAutoReconnect', {'enabled': value});
},
title: Text('Auto Reconnect'),
),
添加数据记录功能:
我们可以添加一个功能来将接收到的数据保存到文件中:
import 'dart:io';
import 'package:path_provider/path_provider.dart';
// ... 其他代码保持不变
Future<void> _saveDataToFile() async {
final directory = await getApplicationDocumentsDirectory();
final file = File('${directory.path}/serial_data_${DateTime.now().millisecondsSinceEpoch}.txt');
String dataToSave = _receivedDataList.join('\n');
await file.writeAsString(dataToSave);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Data saved to ${file.path}')),
);
}
// 在 build 方法中添加一个保存按钮
ElevatedButton(
child: Text('Save Data'),
onPressed: _saveDataToFile,
),
注意: 你需要在 pubspec.yaml 文件中添加 path_provider 依赖。
添加数据统计功能:
我们可以添加一个简单的数据统计功能,显示接收和发送的字节数:
class _SerialPortPageState extends State<SerialPortPage> {
int _receivedBytes = 0;
int _sentBytes = 0;
// ... 其他代码保持不变
@override
void initState() {
super.initState();
platform.setMethodCallHandler((MethodCall call) async {
if (call.method == 'receiveData') {
setState(() {
_receivedDataList.add(call.arguments);
_receivedBytes += call.arguments.length;
});
// ... 其他代码保持不变
}
});
}
Future<void> _sendData() async {
// ... 其他代码保持不变
setState(() {
_sentBytes += dataToSend.length;
});
// ... 其他代码保持不变
}
@override
Widget build(BuildContext context) {
return Scaffold(
// ... 其他代码保持不变
body: Column(
children: <Widget>[
// ... 其他控件
Text('Received: $_receivedBytes bytes, Sent: $_sentBytes bytes'),
// ... 其他控件
],
),
);
}
}
添加连接状态指示器:
我们可以添加一个简单的指示器来显示当前的连接状态:
class _SerialPortPageState extends State<SerialPortPage> {
bool _isConnected = false;
// ... 其他代码保持不变
Future<void> _openPort() async {
try {
final bool result = await platform.invokeMethod('openPort');
setState(() {
_isConnected = result;
});
} on PlatformException catch (e) {
// ... 错误处理
}
}
Future<void> _closePort() async {
try {
final bool result = await platform.invokeMethod('closePort');
setState(() {
_isConnected = !result;
});
} on PlatformException catch (e) {
// ... 错误处理
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Serial Port Communication'),
actions: [
Icon(_isConnected ? Icons.link : Icons.link_off),
],
),
// ... 其他代码保持不变
);
}
}
Windows串口通讯
对于 Windows 端的串口通信,我们可以使用 dart:ffi 来调用 Windows API 进行串口操作。这里是一个基本的实现示例:
首先,在 pubspec.yaml 文件中添加以下依赖:
dependencies:
ffi: ^2.0.1
win32: ^3.0.0
然后,创建一个新的 Dart 文件,比如 windows_serial_port.dart:
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:win32/win32.dart';
class WindowsSerialPort {
int? _handle;
bool _isOpen = false;
bool open(String portName, int baudRate) {
final lpFileName = TEXT(portName);
_handle = CreateFile(
lpFileName,
GENERIC_READ | GENERIC_WRITE,
0,
nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL,
);
if (_handle == INVALID_HANDLE_VALUE) {
print('Failed to open port');
return false;
}
var dcb = calloc<DCB>();
GetCommState(_handle!, dcb);
dcb.ref.BaudRate = baudRate;
dcb.ref.ByteSize = 8;
dcb.ref.StopBits = ONESTOPBIT;
dcb.ref.Parity = NOPARITY;
SetCommState(_handle!, dcb);
free(dcb);
var timeouts = calloc<COMMTIMEOUTS>();
GetCommTimeouts(_handle!, timeouts);
timeouts.ref.ReadIntervalTimeout = 50;
timeouts.ref.ReadTotalTimeoutConstant = 50;
timeouts.ref.ReadTotalTimeoutMultiplier = 10;
timeouts.ref.WriteTotalTimeoutConstant = 50;
timeouts.ref.WriteTotalTimeoutMultiplier = 10;
SetCommTimeouts(_handle!, timeouts);
free(timeouts);
_isOpen = true;
return true;
}
void close() {
if (_handle != null) {
CloseHandle(_handle!);
_handle = null;
_isOpen = false;
}
}
int write(List<int> data) {
if (!_isOpen) return 0;
final buffer = calloc<Uint8>(data.length);
for (var i = 0; i < data.length; i++) {
buffer[i] = data[i];
}
int bytesWritten = 0;
final lpNumberOfBytesWritten = calloc<Uint32>();
WriteFile(_handle!, buffer, data.length, lpNumberOfBytesWritten, nullptr);
bytesWritten = lpNumberOfBytesWritten.value;
free(lpNumberOfBytesWritten);
free(buffer);
return bytesWritten;
}
List<int> read(int maxLength) {
if (!_isOpen) return [];
final buffer = calloc<Uint8>(maxLength);
final lpNumberOfBytesRead = calloc<Uint32>();
ReadFile(_handle!, buffer, maxLength, lpNumberOfBytesRead, nullptr);
final bytesRead = lpNumberOfBytesRead.value;
final result = List<int>.generate(bytesRead, (index) => buffer[index]);
free(lpNumberOfBytesRead);
free(buffer);
return result;
}
bool get isOpen => _isOpen;
}
现在,你可以在你的 Flutter 应用中使用这个类:
import 'package:flutter/material.dart';
import 'windows_serial_port.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: SerialPortPage(),
);
}
}
class SerialPortPage extends StatefulWidget {
@override
_SerialPortPageState createState() => _SerialPortPageState();
}
class _SerialPortPageState extends State<SerialPortPage> {
final WindowsSerialPort _serialPort = WindowsSerialPort();
final TextEditingController _portController = TextEditingController(text: 'COM1');
final TextEditingController _baudRateController = TextEditingController(text: '9600');
final TextEditingController _sendDataController = TextEditingController();
String _receivedData = '';
@override
void dispose() {
_serialPort.close();
super.dispose();
}
void _openPort() {
if (_serialPort.open(_portController.text, int.parse(_baudRateController.text))) {
setState(() {});
_startReading();
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to open port')),
);
}
}
void _closePort() {
_serialPort.close();
setState(() {});
}
void _sendData() {
if (_serialPort.isOpen) {
final data = _sendDataController.text.codeUnits;
_serialPort.write(data);
_sendDataController.clear();
}
}
void _startReading() {
Future.doWhile(() async {
await Future.delayed(Duration(milliseconds: 100));
if (_serialPort.isOpen) {
final data = _serialPort.read(1024);
if (data.isNotEmpty) {
setState(() {
_receivedData += String.fromCharCodes(data);
});
}
return true;
}
return false;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Windows Serial Port'),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
controller: _portController,
decoration: InputDecoration(labelText: 'Port Name'),
),
TextField(
controller: _baudRateController,
decoration: InputDecoration(labelText: 'Baud Rate'),
keyboardType: TextInputType.number,
),
SizedBox(height: 16),
Row(
children: [
ElevatedButton(
onPressed: _serialPort.isOpen ? _closePort : _openPort,
child: Text(_serialPort.isOpen ? 'Close Port' : 'Open Port'),
),
SizedBox(width: 16),
Text(_serialPort.isOpen ? 'Connected' : 'Disconnected'),
],
),
SizedBox(height: 16),
TextField(
controller: _sendDataController,
decoration: InputDecoration(labelText: 'Data to Send'),
),
ElevatedButton(
onPressed: _sendData,
child: Text('Send Data'),
),
SizedBox(height: 16),
Text('Received Data:'),
Expanded(
child: SingleChildScrollView(
child: Text(_receivedData),
),
),
],
),
),
);
}
}
这个示例提供了以下功能:
输入端口名称和波特率
打开和关闭串口
发送数据
接收并显示数据
上一篇:Dart基本语法