Создание удобного OpenFileDialog для Android
Наверное, как и многие разработчики под Android, столкнулся на днях с необходимостью реализовать в своем приложении выбор файла пользователем. Так как изначально в Android такого функционала нет, обратился к великому и ужасному. Это показалось мне странным, но из вороха вопросов на stackoverflow и небольшого числа отечественных форумов можно выделить всего три основных источника:
- Android File Dialog – почти все ссылки из stackoverflow ведут сюда. В принципе, неплохое решение, но реализовано через отдельную activity, а хотелось чего-то в духе OpenFileDialog’а из .Net.
- В данной статье речь идет вообще об отдельном файл-менеджере, и почерпнуть какие-то идеи из неё не удалось.
- Идея же отсюда очень понравилась, однако, как мне показалось реализовать все это можно несколько красивее.
В результате, начав реализовывать своё решение, я столкнулся с некоторыми трудностями решать которые показалось очень интересно. А посему, решил описать в данной статье не просто готовое решение, а все шаги, которые к нему привели. Желающие пройти их вместе –
Итак, приступим! В любой привычной среде (я использую IntelliJ IDEA) создадим новое приложение. На главной activity расположим одну единственную кнопку и напишем к ней, пока пустой, обработчик нажатия:
Создадим новый класс с конструктором:
а в обработчике кнопки вызовем диалог:
Кнопки показались, теперь надо бы и сами файлы найти. Начнем поиск с корня sdcard, для чего определим поле:
и реализуем следующий метод:
(так как главное требование к классу – работать сразу у любого разработчика, без подключения дополнительных библиотек, — то никаких google-collections использовать не будем, и с массивами приходится работать по старинке), а в конструкторе к вызову setNegativeButton добавим .setItems(getFiles(currentPath), null).
Что же, неплохо, однако файлы не отсортированы. Реализуем для этого дела Adapter как внутренний класс, заменим setItems на setAdapter и немного перепишем getFiles:
Еще лучше, но нам надо по клику на папке идти внутрь. Можно достучаться до встроенного listview, но я просто подменил его собственным (это потом пригодится). Плюс, изменения adapter’а внутри обработчика listview вызывало exception, и список файлов пришлось вынести в отдельное поле:
Отлично, вот только нажав на папку Android мы получаем список всего из одного каталога data, и наше окно тут же уменьшается в размере.
Возможно это нормально, но мне это не понравилось, и я стал искать возможности размер сохранить. Единственный найденный мною вариант – это установка setMinimumHeight. Установка этого свойства для listview вызвала дополнительные проблемы, но они решились оберткой его в LinearLayout:
Результат, все равно получился немного не таким, каким хотелось бы: при старте диалог развернут на весь экран, а после перехода в каталог Android – уменьшается до 750px. Да еще и экраны разных устройств имеют разную высоту. Решим сразу обе этих проблемы, установив setMinimumHeight в максимально возможную для текущего экрана:
Не нужно пугаться того, что мы устанавливаем в setMinimumHeight полный размер экрана, сама система уменьшит значение до максимально допустимого.
Теперь появляется проблема понимания пользователя, в каком каталоге он сейчас находится, и возврата вверх. Давайте разберемся с первой. Вроде все легко — установить значение title в currentPath и менять его при изменении последнего. Добавим в конструктор и в метод RebuildFiles вызов setTitle(currentPath).
А нет – заголовок не изменился. Почему не срабатывает setTitle после показа диалога, документация молчит. Однако мы может это исправить, создав свой заголовок и подменив им стандартный:
И снова не все ладно: если пройти достаточно далеко, то строка в заголовок влезать не будет
Решение с установкой setMaximumWidth не верно, так как пользователь будет видеть только начало длинного пути. Не знаю, насколько верно мое решение, но я сделал так:
Решим теперь проблему с возвратом. Это достаточно легко, учитывая, что у нас есть LinearLayout. Добавим в него еще один TextView и немного отрефракторим код:
Возможность возвращаться на шаг вверх, может привести пользователя в каталоги, к которым ему доступ запрещен, поэтому изменим функцию RebuildFiles:
(cообщение пока не очень информативное, но вскоре мы добавим разработчику возможность исправить это).
Ни один OpenFileDialog не обходится без фильтра. Добавим и его:
Обратите внимание — фильтр принимает регулярное выражение. Казалось бы – все хорошо, но первая выборка файлов сработает в конструкторе, до присвоения фильтра. Перенесем её в переопределенный метод show:
Осталось совсем чуть-чуть: вернуть выбранный файл. Опять же, я так и не понял зачем нужно устанавливать CHOICE_MODE_SINGLE, а потом все равно писать лишний код для подсветки выбранного элемента, когда он (код) и так будет работать без CHOICE_MODE_SINGLE, а потому обойдемся без него:
Источник
Осуществление выбора файлов в Android и копирование выбранного файла в другое место
я пытаюсь реализовать средство выбора файлов в своем проекте Android. Что я смог сделать до сих пор-это :
а затем в onActivityResult()
это открытие выбора файла, но это не то, что я хочу. Например, я хочу выбрать файл (.txt), а затем сделать что File и затем использовать его. С этим кодом я думал, что получу полный путь но этого не происходит; например я: /document/5318/ . Но с этим путем я не могу получить . файл. Я создал метод под названием PathToFile() что возвращает File :
что я пытаюсь сделать, это позволить пользователю выбрать File из любого места означает DropBox , Drive , SDCard , Mega , etc. И я не нахожу способ сделать это правильно, я попытался получить Path затем получить File этой Path . но это не работает, поэтому я думаю, что лучше получить , а потом с этим File программно я Copy этот и Delete .
EDIT (текущий код)
там у меня есть вопрос, потому что я не знаю, что поддерживается как text/plain , но я собираюсь расследовать это, но на данный момент это не имеет значения.
на onActivityResult() я использовал то же самое, что @Lukas Knuth ответ, но я не знаю, смогу ли с ним Copy этой File в другую часть моего SDcard я waitting для его ответ.
редактировать с помощью кода работа благодаря @Ю. С., @Lukas Knuth и @CommonsWare.
это Intent где я принимаю только файлы text/plain .
на onActivityResult() создать URI где я получаю данные Intent я создаю File где я сохранить абсолютный путь делаешь content_describer.getPath(); , а затем я сохраняю имя пути, чтобы использовать его в TextView С content_describer.getLastPathSegment(); (это было потрясающе @Y. S. Не знал об этой функции), и я создаю второй File который я назвал destination и я пошлю AbsolutePath чтобы создать этот File .
также я создал функцию, которую вы должны отправить source file и destination file что мы создали ранее, чтобы скопировать в новую папку.
также я создал функцию, которая говорит Мне, существует ли эта папка или нет (я должен отправить destination file , если он не существует, я создаю эту папку, и если это произойдет не я ничего не делаю.
еще раз спасибо за вашу помощь, надеюсь, вам понравится этот код, сделанный со всеми вами, ребята:)
5 ответов
Шаг 1-Используйте неявное Intent :
выбрать файл из устройства, вы должны использовать неявный Intent
Шаг 2 — получить абсолютный путь к файлу:
чтобы получить путь к файлу из Uri , сначала попробуйте использовать
здесь data — это Intent вернулся в onActivityResult() .
если это не работает, используйте следующий метод:
At хотя бы один из этих двух методов должен дать вам правильный, полный путь.
Шаг 3-скопируйте файл:
что вы хотите, я считаю, это скопировать файл из одного места в другое.
для этого абсолютно необходимо иметь абсолютный путь к файлу как источника и места назначения.
во-первых, получить абсолютный путь к файлу, используя либо getPath() способ или uri.getPath() :
затем создайте два File объекты следующим образом:
здесь CustomFolder — каталог на внешнем диске, куда вы хотите скопировать файл.
затем используйте следующий метод для копирования файла из одного места в другое:
попробуйте это. Это должно сработать.
Примечание: Vis-a-vis Lukas’ ответ — то, что он сделал, это использовать метод под названием openInputStream() возвращает контент of a Uri , ли Uri представляет файл или URL-адрес.
еще один перспективный подход — FileProvider :
есть еще один способ, с помощью которого можно получить файл из другого приложения. Если приложение делится своими файлами через FileProvider , тогда можно достать FileDescriptor объект, который содержит конкретную информацию об этом файл.
для этого используйте следующее Intent :
и в onActivityResult() :
здесь mInputPFD это ParcelFileDescriptor .
ссылки:
Я сделал то же самое, чтобы позволить пользователю выбрать изображение из папки :
1) есть кнопка Открыть:
2) Функция открыть папку изображений:
3) результат деятельности, где я получаю путь к файлу изображения и делаю все, что хочу с путем изображения:
4) и теперь самая важная часть, класс W_ImgFilePathUtil, код не от меня, но он позволяет вам получить полный путь любого выбранного файла, будь то на sd-карте, Google drive.
открытый класс W_ImgFilePathUtil <
заключение: код работает с путем изображения, но уверен, работает с любым типом файла.
надеюсь, что это поможет решить вашу проблему.
As @CommonsWare уже отмечалось, Android возвращает вам Uri , что является более абстрактным понятием, чем путь к файлу.
он также может описать простой путь к файлу, но он также может описать ресурс, к которому обращаются через приложения (например, content://media/external/audio/media/710 ).
если вы хотите, чтобы ваш пользователь выбрал любой файл с телефона, чтобы прочитать его из вашего приложения, вы можете сделать это, попросив файл (как вы сделали правильно) , а затем использовать ContentResolver для получения InputStream на Uri это возвращается сборщиком.
важно: некоторые поставщики (например, Dropbox) хранят/кэшируют свои данные на внешнем хранилище. Вам нужно будет иметь android.permission.READ_EXTERNAL_STORAGE -разрешение объявлено в вашем манифесте, иначе вы получите FileNotFoundException , хотя файл есть.
обновление: Да, вы можете скопировать файл, прочитав его из одного потока и пишу другому:
удаление файла, вероятно, невозможно, так как файл не принадлежат для вас он принадлежит приложению, которое поделилось им с вашим. Таким образом, приложение-владелец отвечает за удаление файла.
A Uri не является файлом. А Uri ближе к URL веб-сервера. Это непрозрачный адрес, который имеет значение только для » сервера «(или в этом случае ContentProvider ).
так же, как вы используете InputStream для чтения в байтах, представленных веб-URL, вы используете InputStream для чтения в байтах, представленных Uri . Вы получаете такой поток, позвонив openInputStream() на ContentResolver .
передайте URI, возвращенный в onActivityResult в этом методе
Источник
Работа с файловой системой
Чтение и сохранение файлов
Работа с настройками уровня activity и приложения позволяет сохранить небольшие данные отдельных типов (string, int), но для работы с большими массивами данных, такими как графически файлы, файлы мультимедиа и т.д., нам придется обращаться к файловой системе.
ОС Android построена на основе Linux. Этот факт находит свое отражение в работе с файлами. Так, в путях к файлам в качестве разграничителя в Linux использует слеш «/», а не обратный слеш «\» (как в Windows). А все названия файлов и каталогов являются регистрозависимыми, то есть «data» это не то же самое, что и «Data».
Приложение Android сохраняет свои данные в каталоге /data/data/ / и, как правило, относительно этого каталога будет идти работа.
Для работы с файлами абстрактный класс android.content.Context определяет ряд методов:
boolean deleteFile (String name) : удаляет определенный файл
String[] fileList () : получает все файлы, которые содержатся в подкаталоге /files в каталоге приложения
File getCacheDir() : получает ссылку на подкаталог cache в каталоге приложения
File getDir(String dirName, int mode) : получает ссылку на подкаталог в каталоге приложения, если такого подкаталога нет, то он создается
File getExternalCacheDir() : получает ссылку на папку /cache внешней файловой системы устройства
File getExternalFilesDir(String type) : получает ссылку на каталог /files внешней файловой системы устройства
File getFileStreamPath(String filename) : возвращает абсолютный путь к файлу в файловой системе
FileInputStream openFileInput(String filename) : открывает файл для чтения
FileOutputStream openFileOutput (String name, int mode) : открывает файл для записи
Все файлы, которые создаются и редактируются в приложении, как правило, хранятся в подкаталоге /files в каталоге приложения.
Для непосредственного чтения и записи файлов применяются также стандартные классы Java из пакета java.io.
Итак, применим функционал чтения-записи файлов в приложении. Пусть у нас будет следующая примитивная разметка layout:
Поле EditText предназначено для ввода текста, а TextView — для вывода ранее сохраненного текста. Для сохранения и восстановления текста добавлены две кнопки.
Теперь в коде Activity пропишем обработчики кнопок с сохранением и чтением файла:
При нажатии на кнопку сохранения будет создаваться поток вывода FileOutputStream fos = openFileOutput(FILE_NAME, MODE_PRIVATE)
В данном случае введенный текст будет сохраняться в файл «content.txt». При этом будет использоваться режим MODE_PRIVATE
Система позволяет создавать файлы с двумя разными режимами:
MODE_PRIVATE : файлы могут быть доступны только владельцу приложения (режим по умолчанию)
MODE_APPEND : данные могут быть добавлены в конец файла
Поэтому в данном случае если файл «content.txt» уже существует, то он будет перезаписан. Если же нам надо было дописать файл, тогда надо было бы использовать режим MODE_APPEND:
Для чтения файла применяется поток ввода FileInputStream :
Подробнее про использование потоков ввода-вывода можно прочитать в руководстве по Java: https://metanit.com/java/tutorial/6.3.php
В итоге после нажатия кнопки сохранения весь текст будет сохранен в файле /data/data/название_пакета/files/content.txt
Где физически находится созданный файл? Чтобы увидеть его на подключенном устройстве перейдем в Android Stud в меню к пункту View -> Tool Windows -> Device File Explorer
После этого откроектся окно Device File Explorer для просмотра файловой системы устройства. И в папке data/data/[название_пакета_приложения]/files мы сможем найти сохраненный файл.
Источник