国产chinesehdxxxx野外,国产av无码专区亚洲av琪琪,播放男人添女人下边视频,成人国产精品一区二区免费看,chinese丰满人妻videos

Flutter實(shí)戰(zhàn) Textrue和PlatformView

2021-03-09 14:02 更新

本節(jié)主要介紹原生和 Flutter 之間如何共享圖像,以及如何在 Flutter 中嵌套原生組件。

#12.6.1 Texture(示例:使用攝像頭)

前面說(shuō)過(guò) Flutter 本身只是一個(gè)UI系統(tǒng),對(duì)于一些系統(tǒng)能力的調(diào)用我們可以通過(guò)消息傳送機(jī)制與原生交互。但是這種消息傳送機(jī)制并不能覆蓋所有的應(yīng)用場(chǎng)景,比如我們想調(diào)用攝像頭來(lái)拍照或錄視頻,但在拍照和錄視頻的過(guò)程中我們需要將預(yù)覽畫(huà)面顯示到我們的 Flutter UI 中,如果我們要用 Flutter 定義的消息通道機(jī)制來(lái)實(shí)現(xiàn)這個(gè)功能,就需要將攝像頭采集的每一幀圖片都要從原生傳遞到 Flutter 中,這樣做代價(jià)將會(huì)非常大,因?yàn)閷D像或視頻數(shù)據(jù)通過(guò)消息通道實(shí)時(shí)傳輸必然會(huì)引起內(nèi)存和 CPU 的巨大消耗!為此,F(xiàn)lutter 提供了一種基于 Texture 的圖片數(shù)據(jù)共享機(jī)制。

Texture 可以理解為 GPU 內(nèi)保存將要繪制的圖像數(shù)據(jù)的一個(gè)對(duì)象,F(xiàn)lutter engine 會(huì)將 Texture 的數(shù)據(jù)在內(nèi)存中直接進(jìn)行映射(而無(wú)需在原生和 Flutter 之間再進(jìn)行數(shù)據(jù)傳遞),F(xiàn)lutter 會(huì)給每一個(gè) Texture 分配一個(gè) id,同時(shí) Flutter 中提供了一個(gè)Texture組件,Texture構(gòu)造函數(shù)定義如下:

const Texture({
  Key key,
  @required this.textureId,
})

Texture 組件正是通過(guò)textureId與 Texture 數(shù)據(jù)關(guān)聯(lián)起來(lái);在Texture組件繪制時(shí),F(xiàn)lutter 會(huì)自動(dòng)從內(nèi)存中找到相應(yīng) id 的 Texture 數(shù)據(jù),然后進(jìn)行繪制??梢钥偨Y(jié)一下整個(gè)流程:圖像數(shù)據(jù)先在原生部分緩存,然后在 Flutter 部分再通過(guò)textureId和緩存關(guān)聯(lián)起來(lái),最后繪制由 Flutter 完成。

如果我們作為一個(gè)插件開(kāi)發(fā)者,我們?cè)谠a中分配了textureId,那么在 Flutter 側(cè)使用Texture組件時(shí)要如何獲取textureId呢?這又回到了之前的內(nèi)容了,textureId完全可以通過(guò) MethodChannel 來(lái)傳遞。

另外,值得注意的是,當(dāng)原生攝像頭捕獲的圖像發(fā)生變化時(shí),Texture 組件會(huì)自動(dòng)重繪,這不需要我們寫(xiě)任何 Dart 代碼去控制。

#Texture用法

如果我們要手動(dòng)實(shí)現(xiàn)一個(gè)相機(jī)插件,和前面幾節(jié)介紹的“獲取剩余電量”插件的步驟一樣,需要分別實(shí)現(xiàn)原生部分和 Flutter 部分。考慮到大多數(shù)讀者可能并非同時(shí)既了解 Android 開(kāi)發(fā),又了解 iOS 開(kāi)發(fā),如果我們?cè)倩ù罅科鶃?lái)介紹不同端的實(shí)現(xiàn)可能會(huì)沒(méi)什么意義,另外,由于 Flutter 官方提供的相機(jī)(camera)插件和視頻播放(video_player)插件都是使用 Texture 來(lái)實(shí)現(xiàn)的,它們本身就是 Texture 非常好的示例,所以在本書(shū)中將不會(huì)再介紹使用 Texture 的具體流程了,讀者有興趣查看 camera和video_player 的實(shí)現(xiàn)代碼。下面我們重點(diǎn)介紹一下如何使用 camera 和 video_player。

#相機(jī)示例

下面我們看一下 camera 包自帶的一個(gè)示例,它包含如下功能:

  1. 可以拍照,也可以拍視頻,拍攝完成后可以保存;排號(hào)的視頻可以播放預(yù)覽。
  2. 可以切換攝像頭(前置攝像頭、后置攝像頭、其它)
  3. 可以顯示已經(jīng)拍攝內(nèi)容的預(yù)覽圖。

下面我們看一下具體代碼:

  1. 首先,依賴(lài) camera 插件的最新版,并下載依賴(lài)。

   dependencies:
     ...  //省略無(wú)關(guān)代碼
     camera: ^0.5.2+2

  1. main方法中獲取可用攝像頭列表。

   void main() async {
     // 獲取可用攝像頭列表,cameras為全局變量
     cameras = await availableCameras();
     runApp(MyApp());
   }

  1. 構(gòu)建UI?,F(xiàn)在我們構(gòu)建如圖12-4的測(cè)試界面:

12-4 線面是完整的代碼:

   import 'package:camera/camera.dart';
   import 'package:flutter/material.dart';
   import '../common.dart';
   import 'dart:async';
   import 'dart:io';
   import 'package:path_provider/path_provider.dart';
   import 'package:video_player/video_player.dart'; //用于播放錄制的視頻

   
   /// 獲取不同攝像頭的圖標(biāo)(前置、后置、其它)
   IconData getCameraLensIcon(CameraLensDirection direction) {
     switch (direction) {
       case CameraLensDirection.back:
         return Icons.camera_rear;
       case CameraLensDirection.front:
         return Icons.camera_front;
       case CameraLensDirection.external:
         return Icons.camera;
     }
     throw ArgumentError('Unknown lens direction');
   }

   
   void logError(String code, String message) =>
       print('Error: $code\nError Message: $message');

   
   // 示例頁(yè)面路由
   class CameraExampleHome extends StatefulWidget {
     @override
     _CameraExampleHomeState createState() {
       return _CameraExampleHomeState();
     }
   }

   
   class _CameraExampleHomeState extends State<CameraExampleHome>
       with WidgetsBindingObserver {
     CameraController controller;
     String imagePath; // 圖片保存路徑
     String videoPath; //視頻保存路徑
     VideoPlayerController videoController;
     VoidCallback videoPlayerListener;
     bool enableAudio = true;

   
     @override
     void initState() {
       super.initState();
       // 監(jiān)聽(tīng)APP狀態(tài)改變,是否在前臺(tái)
       WidgetsBinding.instance.addObserver(this);
     }

   
     @override
     void dispose() {
       WidgetsBinding.instance.removeObserver(this);
       super.dispose();
     }

   
     @override
     void didChangeAppLifecycleState(AppLifecycleState state) {
       // 如果APP不在在前臺(tái)
       if (state == AppLifecycleState.inactive) {
         controller?.dispose();
       } else if (state == AppLifecycleState.resumed) {
         // 在前臺(tái)
         if (controller != null) {
           onNewCameraSelected(controller.description);
         }
       }
     }

   
     final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

   
     @override
     Widget build(BuildContext context) {
       return Scaffold(
         key: _scaffoldKey,
         appBar: AppBar(
           title: const Text('相機(jī)示例'),
         ),
         body: Column(
           children: <Widget>[
             Expanded(
               child: Container(
                 child: Padding(
                   padding: const EdgeInsets.all(1.0),
                   child: Center(
                     child: _cameraPreviewWidget(),
                   ),
                 ),
                 decoration: BoxDecoration(
                   color: Colors.black,
                   border: Border.all(
                     color: controller != null && controller.value.isRecordingVideo
                         ? Colors.redAccent
                         : Colors.grey,
                     width: 3.0,
                   ),
                 ),
               ),
             ),
             _captureControlRowWidget(),
             _toggleAudioWidget(),
             Padding(
               padding: const EdgeInsets.all(5.0),
               child: Row(
                 mainAxisAlignment: MainAxisAlignment.start,
                 children: <Widget>[
                   _cameraTogglesRowWidget(),
                   _thumbnailWidget(),
                 ],
               ),
             ),
           ],
         ),
       );
     }

   
     /// 展示預(yù)覽窗口
     Widget _cameraPreviewWidget() {
       if (controller == null || !controller.value.isInitialized) {
         return const Text(
           '選擇一個(gè)攝像頭',
           style: TextStyle(
             color: Colors.white,
             fontSize: 24.0,
             fontWeight: FontWeight.w900,
           ),
         );
       } else {
         return AspectRatio(
           aspectRatio: controller.value.aspectRatio,
           child: CameraPreview(controller),
         );
       }
     }

   
     /// 開(kāi)啟或關(guān)閉錄音
     Widget _toggleAudioWidget() {
       return Padding(
         padding: const EdgeInsets.only(left: 25),
         child: Row(
           children: <Widget>[
             const Text('開(kāi)啟錄音:'),
             Switch(
               value: enableAudio,
               onChanged: (bool value) {
                 enableAudio = value;
                 if (controller != null) {
                   onNewCameraSelected(controller.description);
                 }
               },
             ),
           ],
         ),
       );
     }

   
     /// 顯示已拍攝的圖片/視頻縮略圖。
     Widget _thumbnailWidget() {
       return Expanded(
         child: Align(
           alignment: Alignment.centerRight,
           child: Row(
             mainAxisSize: MainAxisSize.min,
             children: <Widget>[
               videoController == null && imagePath == null
                   ? Container()
                   : SizedBox(
                 child: (videoController == null)
                     ? Image.file(File(imagePath))
                     : Container(
                   child: Center(
                     child: AspectRatio(
                         aspectRatio:
                         videoController.value.size != null
                             ? videoController.value.aspectRatio
                             : 1.0,
                         child: VideoPlayer(videoController)),
                   ),
                   decoration: BoxDecoration(
                       border: Border.all(color: Colors.pink)),
                 ),
                 width: 64.0,
                 height: 64.0,
               ),
             ],
           ),
         ),
       );
     }

   
     /// 相機(jī)工具欄
     Widget _captureControlRowWidget() {
       return Row(
         mainAxisAlignment: MainAxisAlignment.spaceEvenly,
         mainAxisSize: MainAxisSize.max,
         children: <Widget>[
           IconButton(
             icon: const Icon(Icons.camera_alt),
             color: Colors.blue,
             onPressed: controller != null &&
                 controller.value.isInitialized &&
                 !controller.value.isRecordingVideo
                 ? onTakePictureButtonPressed
                 : null,
           ),
           IconButton(
             icon: const Icon(Icons.videocam),
             color: Colors.blue,
             onPressed: controller != null &&
                 controller.value.isInitialized &&
                 !controller.value.isRecordingVideo
                 ? onVideoRecordButtonPressed
                 : null,
           ),
           IconButton(
             icon: const Icon(Icons.stop),
             color: Colors.red,
             onPressed: controller != null &&
                 controller.value.isInitialized &&
                 controller.value.isRecordingVideo
                 ? onStopButtonPressed
                 : null,
           )
         ],
       );
     }

   
     /// 展示所有攝像頭
     Widget _cameraTogglesRowWidget() {
       final List<Widget> toggles = <Widget>[];

   
       if (cameras.isEmpty) {
         return const Text('沒(méi)有檢測(cè)到攝像頭');
       } else {
         for (CameraDescription cameraDescription in cameras) {
           toggles.add(
             SizedBox(
               width: 90.0,
               child: RadioListTile<CameraDescription>(
                 title: Icon(getCameraLensIcon(cameraDescription.lensDirection)),
                 groupValue: controller?.description,
                 value: cameraDescription,
                 onChanged: controller != null && controller.value.isRecordingVideo
                     ? null
                     : onNewCameraSelected,
               ),
             ),
           );
         }
       }

   
       return Row(children: toggles);
     }

   
     String timestamp() => DateTime.now().millisecondsSinceEpoch.toString();

   
     void showInSnackBar(String message) {
       _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text(message)));
     }

   
     // 攝像頭選中回調(diào)
     void onNewCameraSelected(CameraDescription cameraDescription) async {
       if (controller != null) {
         await controller.dispose();
       }
       controller = CameraController(
         cameraDescription,
         ResolutionPreset.high,
         enableAudio: enableAudio,
       );

   
       controller.addListener(() {
         if (mounted) setState(() {});
         if (controller.value.hasError) {
           showInSnackBar('Camera error ${controller.value.errorDescription}');
         }
       });

   
       try {
         await controller.initialize();
       } on CameraException catch (e) {
         _showCameraException(e);
       }

   
       if (mounted) {
         setState(() {});
       }
     }

   
     // 拍照按鈕點(diǎn)擊回調(diào)
     void onTakePictureButtonPressed() {
       takePicture().then((String filePath) {
         if (mounted) {
           setState(() {
             imagePath = filePath;
             videoController?.dispose();
             videoController = null;
           });
           if (filePath != null) showInSnackBar('圖片保存在 $filePath');
         }
       });
     }

   
     // 開(kāi)始錄制視頻
     void onVideoRecordButtonPressed() {
       startVideoRecording().then((String filePath) {
         if (mounted) setState(() {});
         if (filePath != null) showInSnackBar('正在保存視頻于 $filePath');
       });
     }

   
     // 終止視頻錄制
     void onStopButtonPressed() {
       stopVideoRecording().then((_) {
         if (mounted) setState(() {});
         showInSnackBar('視頻保存在: $videoPath');
       });
     }

   
     Future<String> startVideoRecording() async {
       if (!controller.value.isInitialized) {
         showInSnackBar('請(qǐng)先選擇一個(gè)攝像頭');
         return null;
       }

   
       // 確定視頻保存的路徑
       final Directory extDir = await getApplicationDocumentsDirectory();
       final String dirPath = '${extDir.path}/Movies/flutter_test';
       await Directory(dirPath).create(recursive: true);
       final String filePath = '$dirPath/${timestamp()}.mp4';

   
       if (controller.value.isRecordingVideo) {
         // 如果正在錄制,則直接返回
         return null;
       }

   
       try {
         videoPath = filePath;
         await controller.startVideoRecording(filePath);
       } on CameraException catch (e) {
         _showCameraException(e);
         return null;
       }
       return filePath;
     }

   
     Future<void> stopVideoRecording() async {
       if (!controller.value.isRecordingVideo) {
         return null;
       }

   
       try {
         await controller.stopVideoRecording();
       } on CameraException catch (e) {
         _showCameraException(e);
         return null;
       }

   
       await _startVideoPlayer();
     }

   
     Future<void> _startVideoPlayer() async {
       final VideoPlayerController vcontroller =
       VideoPlayerController.file(File(videoPath));
       videoPlayerListener = () {
         if (videoController != null && videoController.value.size != null) {
           // Refreshing the state to update video player with the correct ratio.
           if (mounted) setState(() {});
           videoController.removeListener(videoPlayerListener);
         }
       };
       vcontroller.addListener(videoPlayerListener);
       await vcontroller.setLooping(true);
       await vcontroller.initialize();
       await videoController?.dispose();
       if (mounted) {
         setState(() {
           imagePath = null;
           videoController = vcontroller;
         });
       }
       await vcontroller.play();
     }

   
     Future<String> takePicture() async {
       if (!controller.value.isInitialized) {
         showInSnackBar('錯(cuò)誤: 請(qǐng)先選擇一個(gè)相機(jī)');
         return null;
       }
       final Directory extDir = await getApplicationDocumentsDirectory();
       final String dirPath = '${extDir.path}/Pictures/flutter_test';
       await Directory(dirPath).create(recursive: true);
       final String filePath = '$dirPath/${timestamp()}.jpg';

   
       if (controller.value.isTakingPicture) {
         // A capture is already pending, do nothing.
         return null;
       }

   
       try {
         await controller.takePicture(filePath);
       } on CameraException catch (e) {
         _showCameraException(e);
         return null;
       }
       return filePath;
     }

   
     void _showCameraException(CameraException e) {
       logError(e.code, e.description);
       showInSnackBar('Error: ${e.code}\n${e.description}');
     }
   }

如果代碼運(yùn)行遇到困難,請(qǐng)直接查看camera官方文檔 (opens new window)。

#12.6.2 PlatformView (示例:WebView)

如果我們?cè)陂_(kāi)發(fā)過(guò)程中需要使用一個(gè)原生組件,但這個(gè)原生組件在 Flutter 中很難實(shí)現(xiàn)時(shí)怎么辦(如 webview)?這時(shí)一個(gè)簡(jiǎn)單的方法就是將需要使用原生組件的頁(yè)面全部用原生實(shí)現(xiàn),在 flutter 中需要打開(kāi)該頁(yè)面時(shí)通過(guò)消息通道打開(kāi)這個(gè)原生的頁(yè)面。但是這種方法有一個(gè)最大的缺點(diǎn),就是原生組件很難和 Flutter 組件進(jìn)行組合。

在 Flutter 1.0版本中,F(xiàn)lutter SDK 中新增了AndroidViewUIKitView 兩個(gè)組件,這兩個(gè)組件的主要功能就是將原生的 Android 組件和 iOS 組件嵌入到 Flutter 的組件樹(shù)中,這個(gè)功能是非常重要的,尤其是對(duì)一些實(shí)現(xiàn)非常復(fù)雜的組件,比如 webview,這些組件原生已經(jīng)有了,如果 Flutter 中要用,重新實(shí)現(xiàn)的話成本將非常高,所以如果有一種機(jī)制能讓 Flutter 共享原生組件,這將會(huì)非常有用,也正因如此,F(xiàn)lutter 才提供了這兩個(gè)組件。

由于AndroidViewUIKitView 是和具體平臺(tái)相關(guān)的,所以稱(chēng)它們?yōu)?PlatformView。需要說(shuō)明的是將來(lái) Flutter 支持的平臺(tái)可能會(huì)增多,則相應(yīng)的 PlatformView 也將會(huì)變多。那么如何使用 Platform View 呢?我們以 Flutter 官方提供的webview_flutter插件 (opens new window)為例:

注意,在本書(shū)寫(xiě)作之時(shí),webview_flutter 仍處于預(yù)覽階段,如您想在項(xiàng)目中使用它,請(qǐng)查看一下 webview_flutter 插件最新版本及動(dòng)態(tài)。

  1. 原生代碼中注冊(cè)要被 Flutter 嵌入的組件工廠,如 webview_flutter 插件中 Android 端注冊(cè)webview 插件代碼:

   public static void registerWith(Registrar registrar) {
      registrar.platformViewRegistry().registerViewFactory("webview", 
      WebViewFactory(registrar.messenger()));
   }

WebViewFactory的具體實(shí)現(xiàn)請(qǐng)參考 webview_flutter 插件的實(shí)現(xiàn)源碼,在此不再贅述。

  1. 在 Flutter 中使用;打開(kāi) Flutter 中文社區(qū)首頁(yè)。

   class PlatformViewRoute extends StatelessWidget {
     @override
     Widget build(BuildContext context) {
       return WebView(
         initialUrl: "https://flutterchina.club",
         javascriptMode: JavascriptMode.unrestricted,
       );
     }
   }

運(yùn)行效果如圖12-5所示:

注意,使用 PlatformView 的開(kāi)銷(xiāo)是非常大的,因此,如果一個(gè)原生組件用 Flutter 實(shí)現(xiàn)的難度不大時(shí),我們應(yīng)該首選 Flutter 實(shí)現(xiàn)。

另外,PlatformView 的相關(guān)功能在作者寫(xiě)作時(shí)還處于預(yù)覽階段,可能還會(huì)發(fā)生變化,因此,讀者如果需要在項(xiàng)目中使用的話,應(yīng)查看一下最新的文檔。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)