Решил попробовать написать простенький файловый менеджер, используя при этом модуль «System.IOUtils», чтобы умел перемещаться по папкам, открывать/удалять файлы. (Как вы могли заметить, в заголовке есть номер части, это значит, что будут ещё статьи, где я буду "допиливать" это приложение)
Описывать весь модуль «System.IOUtils» я не буду, т.к. в интернете и официальной справке вы можете найти достаточно подробное описание этого модуля и его классов, методов.
Определяем минимальный функционал приложения:
1) Чтение, перемещение по директориям на устройстве (начинать будем с корня устройства, т.е. «/»)
2) Определять тип пути (папка или файл)
3) Удаление директорий и файлов
4) Открывать файлы с использованием Intent (намерения) в Android
5) Обязательно обрабатывать нажатие на хардварную кнопку «назад», как возврат в предыдущую директорию (до корня устройства)
Структура проекта:
Принцип работы:
Чтение, перемещение по директориям на устройстве
1) TForm1.FormCreate - При запуске приложения будем читать корневую директорию устройства.
2) TForm1.ListBox1ItemClick – При клике по итему, читаем выбранную директорию. (т.е. идём глубже по дереву)
3) TForm1.SpeedButton1Click (и TForm1.FormKeyUp) – Кнопка назад, читаем предыдущую директорию.
Для того чтобы использовать модуль «System.IOUtils», его нужно подключить в Uses.
Т.к. во всех трёх процедурах будет почти идентичный код, вынесем его в отдельную процедуру, я назвал её так «TForm1.TotalWork», а также для удобства создал процедуру для добавления пунктов в ListBox, это «TForm1.AddListItem». Функция «CompareLowerStr» - это условие для сортировки массивов. Ещё нам понадобится глобальная переменная «path», которая будет содержать текущий путь.
Определять тип пути (папка или файл)
Тут всё получилось не очень, но, в конце концов, я сделал всё красиво (как мне кажется…) :).
Я не обнаружил (в модуле System.IOUtils) методов для определения типа (файл или папка) входящего пути, решил попробовать написать решение сам. Прочитал в справке про то, что можно получать атрибуты папки/файла и подумал, что всё классно. Нужно всего-то реагировать на «faDirectory» и «faNormal». Чтобы запросить атрибуты папки используем «TDirectory.GetAttributes» и для файла «TFile.GetAttributes». Далее используя условный оператор, можно было бы определить тип, но не тут-то было… Почему-то, если выбрана не папка, а файл, то выскакивала табличка с ошибкой и дальше ни чего не происходило. Сейчас даже кода не осталось, чтобы показать вам, да и писал я уже ночью, примерно в 2-3 часа, так что может и сам чего накосячил, в итоге я отказался от одного только условного оператора и использовал «try except end;». Что из этого вышло(примерно), смотрите ниже.
uses System.IOUtils; var Attrs: TFileAttributes; // Атрибуты папки или файла LItem: string; // Выбранная папка или файл для удаления begin LItem := ListBox1.ListItems[i].ItemData.Detail try Attrs := TDirectory.GetAttributes(LItem, False); if TFileAttribute.faDirectory in Attrs then begin ... end; except Attrs := TFile.GetAttributes(LItem, False); if TFileAttribute.faNormal in Attrs then begin ... end; end;
Решение получилось совсем некрасивое и "малость" корявое. Поэтому мне приснилось другое решение. :)
Зачем каждый раз читать атрибуты файлов и папок, чтобы определить тип, если можно сделать это один раз. Т.к. я получаю отдельно список папок (GetDirectories) и список файлов (GetFiles), после чего сортирую и добавляю в ListBox, то мне уже заранее известно, где будут папки, а где файлы, поэтому при добавлении в ListBox я сразу записываю тип сюда «LItem.TagString := itype;». В дальнейшем просто считываю значение и всё.
Удаление директорий и файлов
Чтобы удалить папку или файл, нужно знать тип. В предыдущем шаге, я описал, как получаю тип выбранного пункта в ListBox’е. Не забываем проверять выбранные папки и файлы на существование при помощи TDirectory.Exists(path) и TFile.Exists(path). Удаляем при помощи TDirectory.Delete(path, true) и TFile.Delete(path).
Открывать файлы с использованием Intent (намерения) в Android
Объяснять, что такое Intent я не буду, позволю себе привести ссылку (Delphi XE5: использование Intent (намерения) в Android) на статью с описанием. Я использовал один из множества вариантов использования Intent’ов (намерений). Единственное, о чём забывают упомянуть в подобных статьях, как автоматически определить MIME-type (Android API: http://developer.android.com/reference/android/webkit/MimeTypeMap.html).
uses Androidapi.JNI.JavaTypes, Androidapi.JNI.Webkit; var ExtFile: string; mime: JMimeTypeMap; ExtToMime: JString; begin //Определяем расширение файла и его mime тип ExtFile := AnsiLowerCase(StringReplace(TPath.GetExtension(path), '.', '',[])); mime := TJMimeTypeMap.JavaClass.getSingleton(); ExtToMime := mime.getMimeTypeFromExtension(StringToJString(ExtFile));
Код приложения (Delphi XE5 Update 2):
unit Unit1; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.ListBox, FMX.Layouts, FMX.StdCtrls; type TForm1 = class(TForm) ListBox1: TListBox; Label1: TLabel; ToolBar1: TToolBar; SpeedButton1: TSpeedButton; SpeedButton2: TSpeedButton; procedure FormCreate(Sender: TObject); procedure ListBox1ItemClick(const Sender: TCustomListBox; const Item: TListBoxItem); procedure SpeedButton1Click(Sender: TObject); procedure FormKeyUp(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState); procedure ListBox1ChangeCheck(Sender: TObject); procedure SpeedButton2Click(Sender: TObject); private procedure AddListItem(list: array of string; itype: string); procedure TotalWork(path_tr: string; clear: boolean); { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.fmx} uses System.IOUtils, System.Generics.Collections, Generics.Defaults, FMX.Helpers.Android, Androidapi.JNI.JavaTypes, Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.Webkit; var path: string; // Здесь будем хранить путь ItemsCheck: array of string; // Массив с помеченными пунктами function CompareLowerStr(const Left, Right: string): Integer; begin Result := CompareStr(AnsiLowerCase(Left), AnsiLowerCase(Right)); end; {Процедура для вставки массивов в ListBox} procedure TForm1.AddListItem(list: array of string; itype: string); var c: integer; LItem: TListBoxItem; begin ListBox1.BeginUpdate; for c := 0 to Length(list) - 1 do begin LItem := TListBoxItem.Create(ListBox1); LItem.ItemData.Text := ExtractFileName(list[c]); LItem.ItemData.Detail := list[c]; // Помещаем полный путь в Detail LItem.TagString := itype; ListBox1.AddObject(LItem); end; ListBox1.EndUpdate; end; {Загружаем список файлов в корне устройства} procedure TForm1.FormCreate(Sender: TObject); begin // Корень устройства path := '/'; TotalWork(path, False); end; {Обрабатываем кнопку Hardware Back} procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState); begin if Key = vkHardwareBack then begin if path <> '/' then begin SpeedButton1Click(Self); Key := 0; end; end; end; {Пункт пометили галкой} procedure TForm1.ListBox1ChangeCheck(Sender: TObject); var i: integer; begin SetLength(ItemsCheck, 0); for i := 0 to ListBox1.Count-1 do begin if ListBox1.ListItems[i].IsChecked then begin SetLength(ItemsCheck, i + 1); ItemsCheck[i] := ListBox1.ListItems[i].Text; end; end; end; {Клик по Item'у, вперёд} procedure TForm1.ListBox1ItemClick(const Sender: TCustomListBox; const Item: TListBoxItem); var ExtFile: string; mime: JMimeTypeMap; ExtToMime: JString; Intent: JIntent; begin // Сохраняем выбранный путь path := Item.ItemData.Detail; if Item.TagString = 'folder' then begin if TDirectory.Exists(path) then begin TotalWork(path, True); end else begin ListBox1.Items.Delete(Item.Index); ShowMessage('Папка не найдена'); end; end else if Item.TagString = 'file' then begin try //Определяем расширение файла и его mime тип ExtFile := AnsiLowerCase(StringReplace(TPath.GetExtension(path), '.', '',[])); mime := TJMimeTypeMap.JavaClass.getSingleton(); ExtToMime := mime.getMimeTypeFromExtension(StringToJString(ExtFile)); //Запрашиваем открытие файла Intent := TJIntent.Create; Intent.setAction(TJIntent.JavaClass.ACTION_VIEW); Intent.setDataAndType(StrToJURI('file:' + path), ExtToMime); SharedActivity.startActivity(Intent); except ShowMessage('Невозможно открыть файл!'); end; end; end; {Кнопка назад} procedure TForm1.SpeedButton1Click(Sender: TObject); begin // Определяем предыдущую папку path := ExtractFileDir(path); if path = '/' then path := '/' else path := path; TotalWork(path, True); end; {Кнопка удалить} procedure TForm1.SpeedButton2Click(Sender: TObject); var i: integer; // Устанавливаем счётчик LItem: TListBoxItem; // Выбранная папка или файл для удаления LItemPath: string; begin i := 0; // Обнуляем счётчик while i < Length(ItemsCheck) do begin if ItemsCheck[i]<>'' then begin // Получаем выбранный пункт LItem := ListBox1.ListItems[i]; LItemPath := LItem.ItemData.Detail; if LItem.TagString = 'folder' then begin if TDirectory.Exists(LItemPath) then begin TDirectory.Delete(LItemPath, True); // Удаляем папку и подпапки end; ListBox1.Items.Delete(i); // удаляяем пункт из ListBox'a ListBox1ChangeCheck(Self); // обновляем массив выбранных пунктов i := 0; // Обнуляем счётчик end else if LItem.TagString = 'file' then begin if TFile.Exists(LItemPath) then begin TFile.Delete(LItemPath); // Удаляем файл end; ListBox1.Items.Delete(i); // удаляяем пункт из ListBox'a ListBox1ChangeCheck(Self); // обновляем массив выбранных пунктов i := 0; // Обнуляем счётчик end; end; i := i + 1; end; end; {Основа для обработки перемещений по файлам} procedure TForm1.TotalWork(path_tr: string; clear: boolean); var folders, files: TStringDynArray; begin // Выводим текущий путь Label1.Text := path_tr; //*****Папки***** // Ищем папки folders := TDirectory.GetDirectories(path_tr); // Сортируем папки TArray.Sort(folders, TComparer .Construct(CompareLowerStr)); if clear then begin // Очищаем Листбокс ListBox1.Clear; end; // Заполняем Листбокс списком отсортированных папок AddListItem(folders, 'folder'); //*************** //*****Файлы***** // Ищем файлы files := TDirectory.GetFiles(path_tr); // Сортируем файлы TArray.Sort (files, TComparer .Construct(CompareLowerStr)); // Дополняем Листбокс списком отсортированных файлов AddListItem(files, 'file'); //*************** end; end.
Скриншоты:
Видео:
Итог: Приложение отлично работает. Провёл тест, на папке содержащей 400 папок и 570 файлов, общим весом 4,5 Гб. Приложение справилось на отлично.
На скорость чтения папки: По сравнению со стандартным файловым менеджером в самсунге – работают почти одинаково. По сравнению с ES проводником, моё приложение работает заметно медленнее…
В следующей статье добавлю функции создания/переименования файлов и папок, сделаю более дружелюбный вид приложения, реализую копирование/перемещение файлов и папок.
На скорость чтения папки: По сравнению со стандартным файловым менеджером в самсунге – работают почти одинаково. По сравнению с ES проводником, моё приложение работает заметно медленнее…
В следующей статье добавлю функции создания/переименования файлов и папок, сделаю более дружелюбный вид приложения, реализую копирование/перемещение файлов и папок.
Исходный код (Delphi XE5 Update 2): Скачать с Google Drive
Исходный код (Delphi XE6): Скачать с Google Drive
APK файл для установки на ваш девайс: Скачать с Google Drive
На этом всё. Спасибо за внимание.
Надеюсь, вам понравилась/пригодится данная статья и приложение.