#屠龙之技 Cocos Creator 项目中如何使用系统的文件选择对话框?

问题背景

文件选择对话框,在App开发中是很常用的一个组件,但是,在游戏开发中并不常见,所以Cocos Creator也没有提供这个组件。

但是,有没有办法在Cocos Creator的项目中使用这个组件呢?

答案当然是:有!

解决方法

方法1,直接调用操作系统的文件对话框组件。

不管是windows还是mac os,甚至android和ios,文件选择对话框,都是系统自带的组件。

所以,如果我们把这个组件的调用代码封装好,提供js接口,那么,就可以直接在Cocos Creator中使用了。

但是,这个方法就比较麻烦,需要针对不同的系统进行封装,费时费力。

方法2,使用HTML的文件对话框组件。

在Web编程时,我们可以使用file 类型的input组件,来作为文件选择对话框的入口。

<input type="file">

参考:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file

在线示例:https://www.w3schools.com/tags/tryit.asp?filename=tryhtml5_input_type_file

Cocos Creator项目使用的是JavaScript语言,如果能直接调用HTML的接口,那么就可以省去了很多封装的工作。

有几个选择

选项 1). 静态添加

在index.html里添加file input组件,然后在Creator项目的JavaScript代码中调用。

选项 2). 动态添加

直接在Creator项目的JavaScript代码中创建file input组件,并使用。
选项2) 看起来更简洁一点,不需要修改index.html,而且这意味着不需要打包也可以进行测试。

步骤:

  1. 创建file input
// typescript 代码
function getInputBox(inputBoxId:string, containerId:string){
    let inputBox = document.getElementById(inputBoxId) as HTMLInputElement;
    if(!inputBox){
        let container = document.getElementById(containerId);
        if(!container){
            container = document.createElement('div');
            document.body.appendChild(container);
            container.id = containerId;
        }

        inputBox = document.createElement("input") as HTMLInputElement;
        inputBox.id = inputBoxId;

        inputBox.type = "file";

        container.appendChild(inputBox);
    }
    return inputBox;
}
  1. 添加监听
    每当用户选择了一个新文件,input会触发onchange事件,所以可以在这个事件里对选择的文件进行处理。
    有两种方式可以处理,
    • 1). 读取event的参数: event.target.files
    • 2). 直接访问input的value: inputBox.value
let inputBox = this.getInputBox(this.inputBoxId, this.containerId);
if(inputBox){
    inputBox.onchange = (evt)=>{
        console.info("===> input value change: ", evt);
        const fileList = evt.target.files;
        console.info("===> file list: ", fileList);
        console.info("===> value: ", inputBox.value);

        let fileUrl = fileList[0];
        if(!fileUrl){
            console.info("===> No file selected");
            return;
        }
        this.readFile(fileUrl);
    };
}else{
    console.warn("Can't get or create input box");
}
  1. 读取文件
    获取文件的路径后,可以用fileReader读取文件信息
readFile(fileUrl){
    let reader = new FileReader();
    reader.onload = function(e) {
        let content = e.target.result;
        // Display file content
        console.info("===> file content: ", content);
    };
    // 这里用文本方式读,也可以用别的方式
    reader.readAsText(fileUrl);
}
  1. 触发点击
    现在,一切准备就绪,只需要触发file input的click方法,就可以打开文件选择对话框了。
    注意 需要把click方法的调用,绑定到Cocos Creator按钮组件的点击事件中。否则,浏览器会提示以下这个错误警告,并拒绝打开对话框:

File choose dialog can only be shown with a user activation

let inputBox = this.getInputBox(this.inputBoxId, this.containerId);
if(inputBox){
    // 模拟点击按钮,打开对话框。
    inputBox.click();
}

注意事项

使用HTML的组件,会有一些限制,比如

  • 1). 只能在浏览器中使用。对于原生App,由于Creator打包的原生应用,运行底层不是浏览器组件,第二种方法应该是不可行的(未验证)。
  • 2). 必须绑定到玩家的点击操作,不能在没有任何操作时,直接用代码弹窗。

完成

方法2的完整代码

https://gist.github.com/zhangzhibin/a21ec65b434f45efec2103f03b29ba57.js

const {ccclass, property} = cc._decorator;

@ccclass
export default class FileBox extends cc.Component {
    // LIFE-CYCLE CALLBACKS:
    @property
    containerId = "_filebox_container_";
    @property
    inputBoxId = "_filebox_input_";

    inputBox = null;
    start () {
        this.initInputBox();
    }

    initInputBox(){
        let inputBox = this.getInputBox(this.inputBoxId, this.containerId);
        if(inputBox){
            inputBox.onchange = (evt)=>{
                console.info("===> input value change: ", evt);
                const fileList = evt.target.files;
                console.info("===> file list: ", fileList);
                console.info("===> value: ", inputBox.value);

                let file = fileList[0];
                if(!file){
                    console.info("===> No file selected");
                    return;
                }
                this.readFile(file);
            };
        }else{
            console.warn("Can't get or create input box");
        }
    }

    getInputBox(inputBoxId:string, containerId:string){
        if(!this.inputBox){
            let inputBox = document.getElementById(inputBoxId) as HTMLInputElement;
            if(!inputBox){
                let container = document.getElementById(containerId);
                if(!container){
                    container = document.createElement('div');
                    document.body.appendChild(container);
                    container.id = containerId;
                }

                inputBox = document.createElement("input") as HTMLInputElement;
                inputBox.id = inputBoxId;

                inputBox.type = "file";

                container.appendChild(inputBox);
            }
            this.inputBox = inputBox;
        }
        return this.inputBox;
    }

    onClick(){
        if(this.inputBox){
            console.info("click start");
            this.inputBox.click();
            console.info("click done")

        }
    }

    readFile(fileUrl){
        let reader = new FileReader();
        reader.onload = function(e) {
            let content = e.target.result;
            // Display file content
            console.info("===> file content: ", content);
        };
        // 这里用文本方式读,也可以用别的方式
        reader.readAsText(fileUrl);
    }
}