Решил попробовать написать простенький файловый менеджер, используя при этом модуль «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
На этом всё. Спасибо за внимание.
Надеюсь, вам понравилась/пригодится данная статья и приложение.

