先說廢話:
經過一段時間對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,到這裏就已經完美了,請仔細看我的代碼,我可以,你也可以的。
有問題可以郵件我。
[email protected]