반응형

1. 주변 BLE Device Scan

2022.07.07 - [Flutter/기능 구현] - [Flutter / BLE] 기능 구현 - SCAN 글을 참고하여

주변 BLE Device Scan을 수행한다.

해당 링크에 첨부된 main.dart를 참고하면 검색된 BLE Device Name을 title로 가지는

ListTile의 trailing을 터치했을 때 수행되는 onTab 속성이 주석 처리 되어있다.

Connect를 수행하기 위해서는 해당 주석을 해제하여 수행하도록 한다.

child: ListTile(
  dense: true,
  enabled: !(!_connected && _scanning),
  trailing: GestureDetector(
    behavior: HitTestBehavior.translucent,
    onTap: () {
      //(!_connected && _scanning) || (!_scanning && _connected)? (){}: onConnectDevice(index);
    },
    child: Container(
      width: 48,
      height: 48,
      padding: const EdgeInsets.symmetric(vertical: 4.0),
      alignment: Alignment.center,
      child: const Icon(Icons.add_link),
    ),
  ),
  subtitle: Text(_foundBleUARTDevices[index].id),
  title: Text("${_foundBleUARTDevices[index].name}",style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
),

2. onConnectDevice 함수 정의

검색된 주변 BLE 장치 이름을 가지는 ListTile을 터치 했을 때 호출되는 onConnectDevice 함수를 정의 한다.

onConnectDevice 에서는 Connect 관련 처리를 수행하고, 송수신되는 데이터의 처리를 담당하는 datadisplay.dart를 

호출하여 화면을 전환 후 관련처리를 하도록한다.

void onConnectDevice(index) {
  Navigator.of(context).push(
    MaterialPageRoute(
      builder: (context) => DataDisplay(
        foundDeviceId: _foundBleUARTDevices[index].id,
        foundDeviceName: _foundBleUARTDevices[index].name,
        bleLib:  flutterReactiveBle,
      ),
    ),
  );
}

3. datadisplay.dart의 Connect 처리

datadisplay 클래스는 호출되자 마자 수행되는 override 함수인 initState 함수에서 Connect 처리를 수행한다.

Connect 처리에 관련된 소스는 2022.07.01 - [Flutter/기능 구현] - [Flutter / BLE] flutter_reactive_ble 라이브러리

글의 5.Establishing Connection 절을 참고하여 작성되었다.

@override
void initState() {
  super.initState();
  //안드로이드의 경우 범위 내에 있지 않은 장치에 연결을하려고 할 때
  //중단되는 문제가 있으므로 connectToDevice가 아닌 connectToAdvertisingDevice로
  //Connect 수행
  _currentConnectionStream = widget.bleLib.connectToAdvertisingDevice(
    id:widget.foundDeviceId,  //장치 SCAN을 통해 검색된 장치ID(안드로이드의 경우 MAC주소)로 연결
    prescanDuration: const Duration(seconds: 10),  //연결을 시도하기 전에 BLE스택이 장치를 검색하는 시간
    withServices: [globalVal.UART_UUID, globalVal.UART_RX, globalVal.UART_TX], //사용할 서비스 UUID
  );
  //BLE 상태 감시
  connection = _currentConnectionStream.listen((event) {
    if(event.failure != null){
      print("Connect Fail${event}\n");
    }
    var id = event.deviceId.toString();
    switch (event.connectionState) {
      case DeviceConnectionState.connecting:
        {
          print("Connecting to ${id} \n");
          break;
        }
      case DeviceConnectionState.connected:
        {
          globalVal.connected = true;
          print("Connected to ${id} \n");
          //수신 특성 등록
          _txCharacteristic = QualifiedCharacteristic(
              serviceId: globalVal.UART_UUID,
              characteristicId: globalVal.UART_TX,
              deviceId: event.deviceId);
          //수신
          _receivedDataStream =
              widget.bleLib.subscribeToCharacteristic(_txCharacteristic);
          _receivedDataStream.listen((data) {
            onNewReceivedData(data);
          });
          _rxCharacteristic = QualifiedCharacteristic(
              serviceId: globalVal.UART_UUID,
              characteristicId: globalVal.UART_RX,
              deviceId: event.deviceId);
          _timer = Timer.periodic(Duration(seconds: 2), (timer) { _sendData(); });
          break;
        }
      case DeviceConnectionState.disconnecting:
        {
          print("Disconnecting from $id\n");
          break;
        }
      case DeviceConnectionState.disconnected:
        {
          _timer.cancel();
          globalVal.connected = false;
          print("Disconnected from $id\n");
          break;
        }
    }
    setState(() {});
  },);
}

datadisplay.dart
0.00MB
global.dart
0.00MB
main.dart
0.01MB

반응형
반응형

Flutter 의 Pull To Refresh는 RefreshIndicator를 사용한다.

onRefresh 속성에 Pull 동작시 콜백되는 함수를 정의한다.

child: RefreshIndicator(
    onRefresh: _startScan,
    child: ListView.builder(
      itemCount: _foundBleUARTDevices.length,
      itemBuilder: (BuildContext context, int index) => Card(
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(16.0),
        ),
        elevation: 4.0,
        child: ListTile(
          dense: true,
          enabled: !(!_connected && _scanning),
          trailing: GestureDetector(
            behavior: HitTestBehavior.translucent,
            onTap: () {
              //(!_connected && _scanning) || (!_scanning && _connected)? (){}: onConnectDevice(index);
            },
            child: Container(
              width: 48,
              height: 48,
              padding: const EdgeInsets.symmetric(vertical: 4.0),
              alignment: Alignment.center,
              child: const Icon(Icons.add_link),
            ),
          ),
          subtitle: Text(_foundBleUARTDevices[index].id),
          title: Text("${_foundBleUARTDevices[index].name}",style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
        ),
      ),
    ),
  ),

콜백되는 함수에서 Refresh 처리를 수행하도록 하며,

콜백되는 함수는 Future 를 return하도록 작성되어야 한다.

Future<void> _startScan() async {
    if (Platform.isAndroid) {
      if(await checkIfPermissionGranted()) {
        if(_scanning==true) {
          _stopScan();
        }
          _foundBleUARTDevices = [];
          _scanning = true;
          setState(() {});
          _scanStream = flutterReactiveBle.scanForDevices(
            withServices: [_UART_UUID]).listen((device) {
            //withServices: []).listen((device) {
              if (_foundBleUARTDevices.every((element) => element.id != device.id)) {
                if(device.name.contains('ACRO')) {
                  _foundBleUARTDevices.add(device);
                  print("${device.name}\n");
                  setState(() {});
                }
              }
            }, onError: (Object error) {
              print("${_logTexts}ERROR while scanning:$error \n");
            }
          );
      } else {
        await showNoPermissionDialog();
        openAppSettings();
      }
    }
    return Future<void>.value();
  }
반응형

'Flutter > 기본' 카테고리의 다른 글

[Flutter / 기본] ListTile  (0) 2022.07.19
[Flutter / 기본] Card  (0) 2022.07.18
[Flutter / 기본] ListView  (0) 2022.07.18
[Flutter / 기본] SingleChildScrollView  (0) 2022.07.14
[Flutter / 기본] 팝업 메세지 - showDialog, AlertDialog  (0) 2022.07.13
반응형

ListTile은 주로 ListView 의 각 항목을 표현하는

Text나 Icon 등을 나열하는데 사용된다.

child: ListView.builder(
    itemCount: _foundBleUARTDevices.length,
    itemBuilder: (BuildContext context, int index) => Card(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(16.0),
      ),
      elevation: 4.0,
      child: ListTile(
        dense: true,
        enabled: !(!_connected && _scanning),
        trailing: GestureDetector(
          behavior: HitTestBehavior.translucent,
          onTap: () {
            //(!_connected && _scanning) || (!_scanning && _connected)? (){}: onConnectDevice(index);
          },
          child: Container(
            width: 48,
            height: 48,
            padding: const EdgeInsets.symmetric(vertical: 4.0),
            alignment: Alignment.center,
            child: const Icon(Icons.add_link),
          ),
        ),
        subtitle: Text(_foundBleUARTDevices[index].id),
        title: Text("${_foundBleUARTDevices[index].name}",style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20, color: Colors.black)),
      ),
    ),
  ),

- dense 속성은 위젯의 밀집 여부를 설정한다. true 또는 false값을 사용하며,

  true로 설정할 경우 해당 ListTile의 높이와 내부의 Text나 icon을 포함한 약간씩 작아진다.

- enabled은 ListTile이 상호작용을 하는 즉, 대화형인지 설정한다.

  true 또는 false 값을 사용하며, false인 경우 ListTile은 현재 ListTile 테마의

  비활성화된 색상으로 스타일이 지정되고, onTap 및 onLongPress 콜백이 작동하지 않는다.

- leading은 ListTile의 앞부분에 배치할 위젯을 설정한다.

- trailing은 ListTile의 뒷부분에 배치할 위젯을 설정한다.

- title은 ListTile의 주요 내용으로 leading의 내용과 trailing 내용 중간에 배치되는 위젯이다.

- subtitle은 title의 아래에 작게 표현되는 위젯이다.

- onTap 사용자가 ListTile을 터치했을 때 호출되는 콜백을 정의한다.

  (위에서 예를 든 onTap은 ListTile을 탭했을 때 수행되는 것이 아니라,

    trailing을 탭했을 때 수행된다.)

반응형
반응형

카드 형태를 표현하기 위한 위젯이다.

보통 ListView나 GridView와 같은 위젯으로 감싸서 사용된다.

주로 사용하는 속성은 shape 속성과 elevation 속성이 있다.

child: ListView.builder(
  itemCount: _foundBleUARTDevices.length,
  itemBuilder: (BuildContext context, int index) => Card(
    shape: RoundedRectangleBorder(	//모서리를 둥글게 설정
      borderRadius: BorderRadius.circular(16.0),
    ),
    elevation: 4.0,	//그림자의 깊이를 설정
    child: ListTile(
      dense: true,
      enabled: !(!_connected && _scanning),
      trailing: GestureDetector(
        behavior: HitTestBehavior.translucent,
        onTap: () {
          //(!_connected && _scanning) || (!_scanning && _connected)? (){}: onConnectDevice(index);
        },
        child: Container(
          width: 48,
          height: 48,
          padding: const EdgeInsets.symmetric(vertical: 4.0),
          alignment: Alignment.center,
          child: const Icon(Icons.add_link),
        ),
      ),
      subtitle: Text(_foundBleUARTDevices[index].id),
      title: Text("${_foundBleUARTDevices[index].name}",style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20, color: Colors.black)),
    ),
  ),
),
반응형
반응형

ListView 는 가장 일반적으로 사용되는 스크롤 위젯이며,

child는 ListView 의 내용을 채우는데 사용된다.

  • ListView : 일반적인 ListView를 명시적으로 호출하고 children을 전달하는 방법
  • ListView.builder : builder를 사용하여 동적으로 item을 추가하는 방법
  • ListView.separated : ListView.builder에서 item을 좀 더 명확하게 구분해서 보여주는 방법

1. ListView

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text(widget.title),
    ),
    body: Center(
      child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Expanded(
          child: ListView(
            padding: const EdgeInsets.all(8),
            children: <Widget>[
              Container(
                height: 50,
                color: Colors.amber[100],
                child:const Center(child: Text('stage 1')),
              ),
              Container(
                height: 50,
                color: Colors.amber[50],
                child:const Center(child: Text('stage 2')),
              ),
            ]),
          ),
      ]),
    ),  
  );
}

위의 예제는 Expanded로 감싸지 않으면, 오류가 난다.

ListView가 가질 수 있는 공간을 알수 없기 때문에 남는 공간을

Expanded에게 준다는 뜻을 명시적으로 해두어야 한다.

Expanded에 대해서는 차후 좀 더 알아보도록 한다.

 

2. ListView.builder

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text(widget.title),
    ),
    body: SingleChildScrollView(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Container(
            margin: EdgeInsets.all(30),
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(10),
              border: Border.all(
                  color: Colors.blue,
                  width:2
              )
            ),
            height: MediaQuery.of(context).size.height*0.8,
            child: ListView.builder(
              itemCount: _foundBleUARTDevices.length,
              itemBuilder: (BuildContext context, int index) => Card(
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(16.0),
                ),
                elevation: 4.0,
                child: ListTile(
                  dense: true,
                  enabled: !(!_connected && _scanning),
                  trailing: GestureDetector(
                    behavior: HitTestBehavior.translucent,
                    onTap: () {
                      //(!_connected && _scanning) || (!_scanning && _connected)? (){}: onConnectDevice(index);
                    },
                    child: Container(
                      width: 48,
                      height: 48,
                      padding: const EdgeInsets.symmetric(vertical: 4.0),
                      alignment: Alignment.center,
                      child: const Icon(Icons.add_link),
                    ),
                  ),
                  subtitle: Text(_foundBleUARTDevices[index].id),
                  title: Text("${_foundBleUARTDevices[index].name}",style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20, color: Colors.black)),
                ),
              ),
            ),
          ),
        ],
      ),
    ),
  );
}

2022.07.07 - [Flutter/기능 구현] - [Flutter / BLE] 기능 구현 - SCAN 에서 사용된 ListView.builder의 예이다.

itemBuilder를 사용하여 item을 itemCount에 맞춰서 ListView를 구성하면 된다.

BLE Device Scan과 같이 BLE Device(item)가 검색될 때마다 호출하여 리스트를 추가한다.

 

3. ListView.separated

child: ListView.separated(
  separatorBuilder: (BuildContext context, int index) => const Divider(
    height: 10,
    color: Colors.blue,
  ),
  itemCount: _foundBleUARTDevices.length,
  itemBuilder: (BuildContext context, int index) => Card(
      shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(16.0),
    ),
    elevation: 4.0,
    child: ListTile(
      dense: true,
      enabled: !(!_connected && _scanning),
      trailing: GestureDetector(
        behavior: HitTestBehavior.translucent,
        onTap: () {
          //(!_connected && _scanning) || (!_scanning && _connected)? (){}: onConnectDevice(index);
        },
        child: Container(
          width: 48,
          height: 48,
          padding: const EdgeInsets.symmetric(vertical: 4.0),
          alignment: Alignment.center,
          child: const Icon(Icons.add_link),
        ),
      ),
      subtitle: Text(_foundBleUARTDevices[index].id),
      title: Text("${_foundBleUARTDevices[index].name}",style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20, color: Colors.black)),
    ),
  ),
),

ListView.separated는 ListView.builder에 구분선이 추가된 것이다.

사용법은 ListView.builder와 동일하며, 추가적으로 separatorBuilder 속성이 추가되어

구분선에 대한 정의를 해준다.

반응형
반응형

SingleChildScrollView 는 Row나 Column 위젯과 같이 쓴다.

Column 이나 Row에 나열하는 위젯이 많아져서 화면에 모두 출력되지 않을 때

SingleChildScrollView로 감싸주면 스크롤이 가능해진다.

Column이나 Row 위젯 외에도 단 한개의 위젯이 너무 커서 화면에 

모두 나타나지 않을 때에도 사용이 가능하다.

body: SingleChildScrollView(
        child: Column(
          children: [
          ...
          ],
        ),
     ),

 

반응형

+ Recent posts