Flutter桌面端開發——選擇讀取本地文件


注意:查看本文章前請先查看更新日志,以至於該文章適合插件的最新版本

更新日志
詳情 日期
更新了插件的版本和file_selector的部分用法 2023-08-13
更新了插件的版本,用法不變 2022-12-10
更新了file_selector在0.8.4+3版本存儲文件的用法 2022-06-13
修正了file_picker存儲文件的用法 2022-05-24

上一節講了怎么拖動文件到應用中,從應用中點擊按鈕彈出選擇框上傳文件,也是必不可少的一個功能。

file_selector

安裝🛠

點擊file_selector獲取最新版本。以下是在編寫本文章時的最新版本:

file_selector: ^1.0.0

👻注意:在開發 macOS 端的程序時,還需要額外的操作,具體可以查看這里

了解🧩

在 file_selector 插件中,我們需要了解對象 XTypeGroup 和方法 openFiles。

XTypeGroup的作用是使用給定的標簽和文件擴展名創建一個新組,沒有提供任何類型選項的組表示允許任何類型。

XTypeGroup 可以傳入5個參數:

  • String? label:用來自己作區分
  • List<String>? extensions:過濾非描述的后綴文件,默認加載全部類型的文件
  • List<String>? mimeTypes:主要針對 Linux 系統下的文件類型
  • List<String>? macUTIs:主要針對 mac 系統下的文件類型
  • List<String>? webWildCards:主要針對網頁開發時的類型

openFile 方法返回一個 XFile 對象,可以傳入3個參數:

  • List acceptedTypeGroups = const []:接受的類型組,傳入一個XTypeGroup列表
  • String? initialDirectory:初始化文件夾。打開文件對話框以加載文件並返回文件路徑
  • String? confirmButtonText:彈窗“打開”按鈕的顯示文字

openFiles 方法返回 XFile 對象列表,傳入的參數和 openFile 一樣。

使用🥩

選擇的文件我們最后需要讀取出來,讀取需要接受一個 String 類型的路徑:

String path = '';

這里先以選擇圖片為例,我們需要先定義一個 XTypeGroup 對象:

final xType = XTypeGroup(label: '圖片', extensions: ['jpg', 'png']);

選擇單張圖片

打開彈窗選取單個文件,使用 openFile 方法:

final XFile? file = await openFile(acceptedTypeGroups: [xType]);

將獲取到的 XFile 對象的路徑值傳給 path。當然,並不是每次打開彈窗都會選擇圖片,所以需要判斷一下:

if (file != null) {
  path = file.path;
  setState((){});
} else {
  BotToast.showText(text: '你不選擇圖片打開干啥😤');
}

image

openFile 方法中還有兩個屬性,我們修改試一下:

final XFile? file = await openFile(
  acceptedTypeGroups: [xType],
  initialDirectory: r'C:\Users\ilgnefz\Pictures',
  confirmButtonText: '嘿嘿嘿',
);

image

initialDirectory 屬性貌似沒用😕去看了官方的例子,也沒用到過這個參數,以后就忽略它吧。

選擇多張圖片

選取多張圖片,我們就需要定義一個路徑的數組了:

final List<String> paths = [];

XTypeGroup 對象和剛才的一樣就行,重要的是使用 openFiles 方法:

final List<XFile> files = await openFiles(acceptedTypeGroups: [xType]);

將獲取到的文件路徑列表賦值給 paths:

if (file != null) {
  paths.addAll(files.map((e) => e.path).toList());
  setState((){});
} else {
  BotToast.showText(text: '你不選擇圖片打開干啥😤');
}

好了,來看看效果如何

image

讀取文本文件

讀取文本文件,我們需要獲取文件的名稱和內容:

final String title = '';
final String content = '';

再更改一下 XTypeGroup 對象就行:

final XTypeGroup xType = XTypeGroup(label: '文本', extensions: ['txt']);
final XFile? file = await openFile(acceptedTypeGroups: [xType]);

將獲取到的 XFile 對象的屬性賦值給我們定義的對象:

if (file != null) {
  title = file.name;
  content = await file.readAsString();
  setState((){});
} else {
  BotToast.showText(text: '打開了個寂寞🙄');
}

image

存儲文本文件

存儲文本需要用到 XFile 對象中的 fromData 方法。讓我們來看看這個方法中需要傳入什么參數:

  • Uint8List bytes:存儲的主要內容
  • String? mimeType:文件的 mine 類型
  • String? name:文件名?測試了毫無用處😑
  • int? length:不知道是什么的長度,反正無法截取內容😑
  • DateTime? lastModified:最后修改文件的時間
  • String? path:文件保存的路徑?測試了毫無效果😑
  • CrossFileTestOverrides? overrides:覆蓋CrossFile的某些方法用來測試

(以上幾個參數要是有朋友測試出來了,可以告知一下😁)

在存儲文本文件前,我們需要先知道應該存儲在哪個文件夾:

final FileSaveLocation? path = await getSaveLocation();

getSavePath有以下幾個可選參數:

  • List acceptedTypeGroups:可以在對話框中選擇的文件類型組的列表。其顯示方式取決於 pltaform,例如:
    • 在 Windows 和 Linux 上,每個組將是過濾器選項列表中的一個條目
    • 在 macOS 上,將允許所有組允許的所有類型的並集
  • String? initialDirectory:打開對話框時將顯示的目錄的完整路徑。如果未提供,平台將選擇一個初始位置
  • String? suggestedName:文件名的初始值
  • String? confirmButtonText:對話框的確認按鈕中的文本。如果未提供,則使用默認操作系統標簽(例如,“保存”)

以下是沒傳任何參數的效果:

image

以下是傳了參數的效果:

final FileSaveLocation? path = await getSaveLocation(
  acceptedTypeGroups: [
    XTypeGroup(label: '圖片', extensions: ['jpg', 'png']),
    XTypeGroup(label: '視頻', extensions: ['mp4', 'avi'])
  ],
  initialDirectory: r'C:\Users\ilgnefz\Pictures',
  suggestedName: '新建文件',
  confirmButtonText: '點我吧!',
);

image

getSaveLocation方法返回的FileSaveLocation對象有一個重要的屬性:path,記錄了文件的保存路徑。

XFile.fromData 雖然有很多參數可以使用,但是大部分沒有啥效果,通過上面的示例,我們可以直接使用getSaveLocation方法來是指必要的內容:

final String title = widget.provider.title;
final String content = widget.provider.txtContent;
final Uint8List fileData = const Utf8Encoder().convert(content);
// final Uint8List fileData = Uint8List.fromList(content.codeUnits);
final FileSaveLocation? saveLocation = await getSaveLocation(
  acceptedTypeGroups: [
    const XTypeGroup(label: '文本', extensions: ['txt']),
  ],
  initialDirectory: r'C:\Users\ilgnefz\Pictures',
  suggestedName: title,
);
debugPrint('存儲路徑:${saveLocation?.path}');
if (saveLocation != null) {
  const String fileMimeType = 'text/plain';
  final XFile xFile = XFile.fromData(
    fileData,
    mimeType: fileMimeType,
  );
  await xFile.saveTo(saveLocation.path);
} else {
  BotToast.showText(text: '給你個眼神自己體會😑');
}

image

現在去選擇的文件夾就能找到我們存儲的文件了。initialDirectory是路徑的初始值,我打開文本改變了路徑,所以就沒生效。

選擇文件夾

選擇文件夾使用 getDirectoryPath 方法:

final String? path = await getDirectoryPath();
if (path != null) {
  title = '目錄';
  content = path;
  setState((){});
}

6

選擇多文件夾

選擇多文件夾需要使用 getDirectoryPaths 方法:

final List<String?> paths = await getDirectoryPaths();
if (paths.isNotEmpty) {
  widget.provider.setTitle('多目錄');
  widget.provider.setTextContent(paths.join('\n'));
  widget.provider.setFileType('text');
}

image

file_picker

安裝🛠

點擊file_picker獲取最新版本。以下是在編寫本文章時的最新版本:

file_picker: ^5.3.3

使用🥩

先定義一個默認的路徑:

String path = '';

選擇單個文件

選擇單個文件需要用到 pickFiles 方法,該方法可以傳入10個參數:

  • String? dialogTitle:彈窗的標題
  • String? initialDirectory:初始化的文件夾
  • FileType type = FileType.any:文件的類型
  • List<String>? allowedExtensions:允許的文件后綴名稱,需配合FileType.custom使用
  • dynamic Function(FilePickerStatus)? onFileLoading:監聽文件選擇的狀態
  • bool allowCompression = true:是否允許壓縮
  • bool allowMultiple = false:是否允許選擇多個文件
  • bool withData = false:如果為true,選取的文件將在內存中立即以“Uint8List”的形式提供其字節數據,如果您選擇它進行服務器上傳或類似操作,這將很有用。但是,請記住,如果您允許多個選擇或選擇大文件,則在 IO(iOS 和 Android)上啟用此功能可能會導致內存不足問題。請改用 [withReadStream]。在 web 上默認為 true,其他為 false
  • bool withReadStream = false:拾取的文件將以 [Stream<List >] 的形式提供其字節數據,這對於上傳和處理大文件很有用
  • bool lockParentWindow = false:是否將子窗口(文件選擇器窗口)一直停留在 Flutter 窗口的前面,直到它關閉(如模態窗口)。此參數僅適用於 Windows
FilePickerResult? result = await FilePicker.platform.pickFiles();
if (result != null) {
  File file = File(result.files.single.path!);
  path = file.path;
  setState((){});
}

image

我們試着添加一些參數:

FilePickerResult? result = await FilePicker.platform.pickFiles(
  dialogTitle: '我的地盤我做主',
  initialDirectory: r'C:\Users\ilgnefz\Pictures\Saved Pictures',
  type: FileType.image,
);

8

initialDirectory 又沒起作用😑

選擇多個文件

定義一個接受所有路徑的數組:

final List<String> paths = [];
FilePickerResult? result = await FilePicker.platform.pickFiles(
  allowMultiple: true,
);
if (result != null) {
  paths = result.files.map((e) => e.path!).toList();
 	setState((){});
}

讀取文件信息

通過以上的方法,我們會得到一個 PlatformFile 對象:

FilePickerResult? result = await FilePicker.platform.pickFiles();
PlatformFile file = result.files.single;

該對象有以下幾個屬性:

  • name:文件名稱
  • size:文件大小,以字節為單位
  • bytes:此文件的字節數據。如果您想操作其數據或輕松上傳到其他地方,則特別有用。 在常見問題解答中查看此處 一個關於如何使用它在網絡上上傳的示例。
  • extension:文件后綴
  • path:文件路徑
  • identifier:原始文件的平台標識符,是指 Android 上的 Uri 和 iOS 上的 NSURL。其他為null
  • readStream:將文件內容轉換成流讀取

1

存儲文件

存儲文件需要使用 saveFile 方法,該方法有可以傳入6個參數:

  • String? dialogTitle:同 pickFiles 方法
  • String? fileName:存儲文件的名字
  • String? initialDirectory:同 pickFiles 方法
  • FileType type = FileType.any:同 pickFiles 方法
  • List ? allowedExtensions:同 pickFiles 方法
  • bool lockParentWindow = false:同 pickFiles 方法
String? outputFile = await FilePicker.platform.saveFile(fileName: 'hello.txt');

image

這個時候我們去選擇的文件夾查看,並不能發現我們存儲的文件。這是因為這個方法並不能直接存儲文件,我們把outputFile打印一下會發現以下結果:

outputFile: C:\Users\ilgnefz\Pictures\hello.txt

誒😀雖然它不能幫我們直接存儲一個文件,但是會返回一個文件的路徑。我們可以使用File對象來將需要的內容存儲下來。

if (outputFile != null) {
  File file = File(outputFile);
  await file.writeAsString('Hello World');
  await file.create();
  BotToast.showText(text: '文件存儲成功');
}

image

現在我們打開文件存儲時選擇的文件夾查看

image

獲取文件夾路徑

獲取文件夾需要使用 getDirectoryPath 方法,可以傳入3個參數:

  • String? dialogTitle:同 pickFiles 方法
  • bool lockParentWindow = false:同 pickFiles 方法
  • String? initialDirectory:同 pickFiles 方法
final String title = '';
final String content = '';
String? dir = await FilePicker.platform.getDirectoryPath();
if (path != null) {
  title = '目錄';
  content = path;
  setState((){});
}

9

🛫OK,以上就是這篇文章的全部內容,僅針對插件的當前版本,並不能保證適用於以后插件用法的更新迭代。

最后,感謝 flutter 團隊和 miguelpruivo 對以上插件的開發和維護😁。本應用代碼已上傳至 githubgitee,有需要的可以下載下來查看學習。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM