您现在的位置是:网站首页> Flutter

Flutter项目实用技术收集

  • Flutter
  • 2024-12-18
  • 193人已阅读
摘要

Flutter项目实用技术收集


读写NFC的例子

低功耗蓝牙例子

Android串口通讯

Windows串口通讯



读写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基本语法

Top