先说废话:
经过一段时间对Qt的学习,想在Android上搞点事情,中途遇到了一个大坑,就是打开Android的文件系统进行文件选择,别以为这个功能简单,直接使用一个什么FileDialog就可以完事了,你要是这样想,兄弟,你把这个页面关了吧。
一、实现功能:
本博客的功能是讲解在Qml在Android平台下进行文件选择,网上的人说了一大堆,缺胳膊少腿的,还是官方给的案列比较靠谱点。
二、运行效果:
备注,这个是在我安卓手机上横屏运行的,在其他平台也应该是这个样子的,望周知。
三、开发环境:
1、Qt5.12,应该Qt5.9之后的平台都是支持的,你可以自己试试,我比较懒的。
2、Android运行版本是8.0, 6.0之后的版本需要进行权限申请,这个骚操作会在后面描述的。
3、还需要你在你的工程中,配置一个图标字体,FontAwsome,这样看起来才舒服,不然就很阔怕的丑。如果你不知道咋个配置,请看这里,照着做就可以了。
四、代码编写:
4.1、首先编写文件选择qml代码,有点多,自己复制粘贴就好了,这个文件命名为: FileBrowser.qml:
/*
* Note:this file base on Qt Company.
* if you want to use this file, you
* need configure font fontawesome-webfont.ttf
* in your project and some icon will be show normal.
*
*/
import QtQuick 2.0
import Qt.labs.folderlistmodel 2.0
Rectangle {
id: fileBrowser
color: "transparent"
// 当前文件夹路径
property string folder
// 选择文件信号
signal fileSelected(string file)
// 文件图标显示字体库
property string iconfamily: "FontAwsome"
property int itemHeight: Math.min(parent.width, parent.height) / 15
property int buttonHeight: Math.min(parent.width, parent.height) / 12
function selectFile(file) {
if (file !== "") {
folder = loader.item.folders.folder
fileBrowser.fileSelected(file)
}
loader.sourceComponent = undefined
}
Loader {
id: loader
}
function show() {
loader.sourceComponent = fileBrowserComponent
loader.item.parent = fileBrowser
loader.item.anchors.fill = fileBrowser
loader.item.folder = fileBrowser.folder
}
Component {
id: fileBrowserComponent
Rectangle {
id: root
color: "black"
property bool showFocusHighlight: false
property variant folders: folders1
property variant view: view1
property alias folder: folders1.folder
property color textColor: "white"
FolderListModel {
id: folders1
folder: folder
}
FolderListModel {
id: folders2
folder: folder
}
SystemPalette {
id: palette
}
Component {
id: folderDelegate
Rectangle {
id: wrapper
function launch() {
var path = "file://";
if (filePath.length > 2 && filePath[1] === ':') // Windows drive logic, see QUrl::fromLocalFile()
path += '/';
path += filePath;
if (folders.isFolder(index))
down(path);
else
fileBrowser.selectFile(path)
}
width: root.width
height: folderImage.height
color: "transparent"
Rectangle {
id: highlight
visible: false
anchors.fill: parent
anchors.leftMargin: 5
anchors.rightMargin: 5
color: "#212121"
}
Item {
id: folderImage
width: itemHeight
height: itemHeight
Image {
id: folderPicture
width: itemHeight * 0.9
height: itemHeight * 0.9
anchors.left: parent.left
anchors.margins: 5
visible: folders.isFolder(index)
Text {
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font.family: iconfamily
text: "\uf115"
color: "white"
Component.onCompleted: font.pointSize = parent.width / 2
}
}
Image {
width: itemHeight * 0.9
height: itemHeight * 0.9
anchors.left: parent.left
anchors.margins: 5
visible: !folders.isFolder(index)
Text {
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font.family: iconfamily
text: "\uf0f6"
color: "white"
Component.onCompleted: font.pointSize = parent.width / 2
}
}
}
Text {
id: nameText
anchors.fill: parent;
verticalAlignment: Text.AlignVCenter
text: fileName
anchors.leftMargin: itemHeight + 10
color: (wrapper.ListView.isCurrentItem && root.showFocusHighlight) ? palette.highlightedText : textColor
elide: Text.ElideRight
}
MouseArea {
id: mouseRegion
anchors.fill: parent
onPressed: {
root.showFocusHighlight = false;
wrapper.ListView.view.currentIndex = index;
}
onClicked: { if (folders === wrapper.ListView.view.model) launch() }
}
states: [
State {
name: "pressed"
when: mouseRegion.pressed
PropertyChanges { target: highlight; visible: true }
PropertyChanges { target: nameText; color: palette.highlightedText }
}
]
}
}
ListView {
id: view1
anchors.top: titleBar.bottom
anchors.bottom: cancelButton.top
width: parent.width
model: folders1
delegate: folderDelegate
highlight: Rectangle {
color: "#212121"
visible: root.showFocusHighlight && view1.count != 0
width: view1.currentItem == null ? 0 : view1.currentItem.width
}
highlightMoveVelocity: 1000
pressDelay: 100
focus: true
state: "current"
states: [
State {
name: "current"
PropertyChanges { target: view1; x: 0 }
},
State {
name: "exitLeft"
PropertyChanges { target: view1; x: -root.width }
},
State {
name: "exitRight"
PropertyChanges { target: view1; x: root.width }
}
]
transitions: [
Transition {
to: "current"
SequentialAnimation {
NumberAnimation { properties: "x"; duration: 250 }
}
},
Transition {
NumberAnimation { properties: "x"; duration: 250 }
NumberAnimation { properties: "x"; duration: 250 }
}
]
Keys.onPressed: root.keyPressed(event.key)
}
ListView {
id: view2
anchors.top: titleBar.bottom
anchors.bottom: parent.bottom
x: parent.width
width: parent.width
model: folders2
delegate: folderDelegate
highlight: Rectangle {
color: "#212121"
visible: root.showFocusHighlight && view2.count != 0
width: view1.currentItem == null ? 0 : view1.currentItem.width
}
highlightMoveVelocity: 1000
pressDelay: 100
states: [
State {
name: "current"
PropertyChanges { target: view2; x: 0 }
},
State {
name: "exitLeft"
PropertyChanges { target: view2; x: -root.width }
},
State {
name: "exitRight"
PropertyChanges { target: view2; x: root.width }
}
]
transitions: [
Transition {
to: "current"
SequentialAnimation {
NumberAnimation { properties: "x"; duration: 250 }
}
},
Transition {
NumberAnimation { properties: "x"; duration: 250 }
}
]
Keys.onPressed: root.keyPressed(event.key)
}
Rectangle {
width: parent.width
height: buttonHeight + 10
anchors.bottom: parent.bottom
color: "black"
}
Rectangle {
id: cancelButton
width: parent.width
height: buttonHeight
color: "#212121"
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 5
radius: buttonHeight / 15
Text {
anchors.fill: parent
text: "Cancel"
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
MouseArea {
anchors.fill: parent
onClicked: fileBrowser.selectFile("")
}
}
Keys.onPressed: {
root.keyPressed(event.key);
if (event.key === Qt.Key_Return || event.key === Qt.Key_Select || event.key === Qt.Key_Right) {
view.currentItem.launch();
event.accepted = true;
} else if (event.key === Qt.Key_Left) {
up();
}
}
Rectangle {
id: titleBar
width: parent.width
height: buttonHeight + 10
anchors.top: parent.top
color: "black"
Rectangle {
width: parent.width;
height: buttonHeight
color: "#212121"
anchors.margins: 5
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
radius: buttonHeight / 15
Rectangle {
id: upButton
width: buttonHeight
height: buttonHeight
color: "transparent"
Image {
width: itemHeight
height: itemHeight
anchors.centerIn: parent
Text {
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font.family: iconfamily
text: "\uf112"
color: "white"
Component.onCompleted: font.pointSize = parent.width / 2
}
}
MouseArea { id: upRegion; anchors.centerIn: parent
width: buttonHeight
height: buttonHeight
onClicked: up()
}
states: [
State {
name: "pressed"
when: upRegion.pressed
PropertyChanges { target: upButton; color: palette.highlight }
}
]
}
Text {
anchors.left: upButton.right; anchors.right: parent.right; height: parent.height
anchors.leftMargin: 5; anchors.rightMargin: 5
text: folders.folder
color: "white"
elide: Text.ElideLeft;
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignVCenter
}
}
}
function down(path) {
if (folders == folders1) {
view = view2
folders = folders2;
view1.state = "exitLeft";
} else {
view = view1
folders = folders1;
view2.state = "exitLeft";
}
view.x = root.width;
view.state = "current";
view.focus = true;
folders.folder = path;
}
function up() {
var path = folders.parentFolder;
if (path.toString().length === 0 || path.toString() === 'file:')
return;
if (folders == folders1) {
view = view2
folders = folders2;
view1.state = "exitRight";
} else {
view = view1
folders = folders1;
view2.state = "exitRight";
}
view.x = -root.width;
view.state = "current";
view.focus = true;
folders.folder = path;
}
function keyPressed(key) {
switch (key) {
case Qt.Key_Up:
case Qt.Key_Down:
case Qt.Key_Left:
case Qt.Key_Right:
root.showFocusHighlight = true;
break;
default:
// do nothing
break;
}
}
}
}
}
4.2、配置Android平台权限:
1)在你的pro工程文件中添加如下代码:
android{
QT += androidextras
DEFINES += Android_Platform
}
2)在你的main.cpp中添加如下代码:
#include <QtCore/QStandardPaths>
#ifdef Android_Platform
#include <QtAndroid>
#endif
int main(){
#ifdef Android_Platform
// 添加读取权限,Android 6.0 版本后需要申请,不然的话,啥都看不到。
QString strPermission = "android.permission.READ_EXTERNAL_STORAGE";
QtAndroid::requestPermissionsSync(QStringList() << strPermission); // ok
#endif
QQmlApplicationEngine engine;
// 获取默认文件的路径并进行设置
const QStringList moviesLocation = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation);
const QUrl videoPath =
QUrl::fromLocalFile(moviesLocation.isEmpty() ?
app.applicationDirPath() :
moviesLocation.front());
engine.rootContext()->setContextProperty("videoPath", videoPath);
}
3)经过上诉的配置后,即可在qml中进行使用了:
a、实例化对象:
FileBrowser{
id: filebrowser
anchors.fill: parent
folder: videoPath
onFileSelected:{
console.log(file)
}
}
b、打开文件选择框:
onClicked: {
filebrowser.show()
}
OK,到这里就已经完美了,请仔细看我的代码,我可以,你也可以的。
有问题可以邮件我。
543985125@qq.com