kotlin实现的简单个人账户管理APP(二) 文件选择浏览/文件导入导出
2017-12-22 10:37
399 查看
转载请注明出处:http://blog.csdn.net/a512337862/article/details/78870646
2.本篇博客是介绍利用kotlin指定账户的导入文件/导出到文件。
3.因为本人是kotlin初学者,博客如果有任何问题请指出。
FileExploreFragment主要实现文件/文件夹的选择,并通过接口将选中的文件路径回调,相当于一个文件浏览选择器。这里是通过DialogFragment以Dialog的形式表现。代码如下:
这里主要解释以下几点:
1.因为这里涉及到导入导出两种情况,导入需要选中文件,而导出则需要选择文件夹。这里以DialogFragment.show(FragmentManager manager, String tag)中的tag(Import/Export)来判断。主要在DialogFragment的onResume中来进行判断:
2.ensureExport是左上角的一个确定按钮,在导出到文件夹时,可以通过点击ensureExport来触发将文件夹路径回调。导入时只需选择文件即可,所以无需ensureExport。
3.originalPath则是原始路径,每次显示DialogFragment都会默认显示originalPath下文件,setFilePath方法是用来显示指定文件夹下的所有未隐藏文件,并通知adapter更新ListView:
4.回退按钮没有太多的可以介绍,主要的一点就是当返回到最原始的路径originalPath时,直接隐藏FileExploreFragment。
这里没什么可以讲的东西,唯一能提的就是通过文件类型显示不同的图标,截图如下:
这里导入的文件的格式进行了严格的限定,账户类型不能为空,且必须以空行作为两个账户分隔符,不然就会解析失败,大概的文件格式如下图所示:
这里简单分析一下部分代码:
在run()里面文件解析的代码是关键部分,截图如下:
这部分代码的思路就是:通过BufferedReader按行读取指定文件,读取完一行之后,通过判断该行:
1.不为空行的情况下,是否包含中英文的冒号,包含冒号则是“账户”/“密码”/“备注”之一,不包含则是账户类型。
2.空行则认为一个账户解析完成,开始解析下一个。
3.读完文件最后一行不为空行,也认为账户解析完成。
splitData()用于分离数据,即通过中英文冒号来将需要的信息分离出来,这里只是简单的认为冒号右边的数据为有效数据。另外,如果出现备注/账户/密码之外包含冒号的信息,则统一认为备注部分,该行数据直接添加到备注。截图如下:
SelectImportActivity代码如下:
简单分析一下代码:
1.在initData()中通过intent获取文件/文件夹路径,已经当前是导入/导出,并对应修改界面显示:
2.这里只附上了我认为比较关键的代码,布局文件等都未涉及,需要的可以去下载源码。
3.项目源码下载地址:http://download.csdn.net/download/a512337862/10151418
前言
1.本篇博客相关的项目介绍请参考基于kotlin实现的简单个人账户管理APP2.本篇博客是介绍利用kotlin指定账户的导入文件/导出到文件。
3.因为本人是kotlin初学者,博客如果有任何问题请指出。
代码分析
FileExploreFragment
截图如下:FileExploreFragment主要实现文件/文件夹的选择,并通过接口将选中的文件路径回调,相当于一个文件浏览选择器。这里是通过DialogFragment以Dialog的形式表现。代码如下:
/** * Author : BlackHao * Time : 2017/11/16 13:27 * Description : 文件选择框 */ class FileExploreFragment : DialogFragment() { //控件 private lateinit var backIb: ImageButton private lateinit var fileListView: ListView private lateinit var ensureExport: ImageButton private lateinit var showTypeTv: TextView //文件选择回调 var listener: FileExploreClickListener? = null //导入导出标识 private var isImport = false //当前路径 private lateinit var currentPath: String private lateinit var originalPath: String //文件列表adapter private var adapter: FileListAdapter? = null private var fileList: ArrayList<File>? = null override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater?.inflate(R.layout.fragment_file_explore, container, false) fileListView = view?.findViewById(R.id.file_list_lv) as ListView backIb = view.findViewById(R.id.back_ib) as ImageButton ensureExport = view.findViewById(R.id.ensure_ib) as ImageButton showTypeTv = view.findViewById(R.id.show_im_ex_tv) as TextView //设置原始路径 originalPath = Environment.getExternalStorageDirectory().absolutePath fileList = arrayListOf() adapter = FileListAdapter(fileList!!, activity) fileListView.adapter = adapter //点击事件 backIb.setOnClickListener { if (currentPath == Environment.getExternalStorageDirectory().toString()) { //已经是最开始的路径 //隐藏fragment dismiss() } else { //显示父目录下文件 setFilePath(File(currentPath).parentFile.absolutePath) } } fileListView.setOnItemClickListener { _, _, position, _ -> val selectedFile = fileList!![position] if (selectedFile.isDirectory) { setFilePath(selectedFile.absolutePath) } else { //导入模式下,文件可选择 if (isImport) { listener?.selectedFile(selectedFile, true) } } } ensureExport.setOnClickListener { listener?.selectedFile(File(currentPath), false) } return view } override fun onResume() { super.onResume() setFilePath(originalPath) isImport = tag == "Import" if (isImport) { ensureExport.visibility = View.GONE showTypeTv.text = getString(R.string.select_import_file) } else { ensureExport.visibility = View.VISIBLE showTypeTv.text = getString(R.string.select_export_folder) } } /** * 显示指定路径下的文件 * * @param folderPath 文件夹路径 */ private fun setFilePath(folderPath: String) { currentPath = folderPath val file = File(folderPath) //文件存在并且是文件夹 if (file.exists() && file.isDirectory) { val files = file.listFiles() fileList?.clear() files.filterNot { it.isHidden }.forEach { fileList?.add(it) } adapter?.notifyDataSetChanged() } } }
这里主要解释以下几点:
1.因为这里涉及到导入导出两种情况,导入需要选中文件,而导出则需要选择文件夹。这里以DialogFragment.show(FragmentManager manager, String tag)中的tag(Import/Export)来判断。主要在DialogFragment的onResume中来进行判断:
2.ensureExport是左上角的一个确定按钮,在导出到文件夹时,可以通过点击ensureExport来触发将文件夹路径回调。导入时只需选择文件即可,所以无需ensureExport。
3.originalPath则是原始路径,每次显示DialogFragment都会默认显示originalPath下文件,setFilePath方法是用来显示指定文件夹下的所有未隐藏文件,并通知adapter更新ListView:
4.回退按钮没有太多的可以介绍,主要的一点就是当返回到最原始的路径originalPath时,直接隐藏FileExploreFragment。
FileListAdapter
FileListAdapter是用来显示FileExploreFragment的文件列表,直接贴代码:/** * Author : BlackHao * Time : 2017/11/16 14:07 * Description : 文件列表 Adapter */ class FileListAdapter(private var list: ArrayList<File>, private val context: Context) : BaseAdapter() { override fun getItem(position: Int): Any = list[position] override fun getItemId(position: Int): Long = position.toLong() override fun getCount(): Int = list.size override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { val holder: ViewHolder val file = list[position] val view : View if (convertView == null) { view = View.inflate(context, R.layout.item_file_list, null) holder = ViewHolder(view) view.tag = holder } else { view = convertView holder = view.tag as ViewHolder } if (file.isDirectory) { holder.iconIv.setImageResource(R.drawable.dir) } else { if (file.name.endsWith(".txt")) { holder.iconIv.setImageResource(R.drawable.file) } else if (file.name.endsWith(".xls") || file.name.endsWith(".xlxs")) { holder.iconIv.setImageResource(R.drawable.xls) } else if (file.name.endsWith(".pdf")) { holder.iconIv.setImageResource(R.drawable.pdf) } else if (file.name.endsWith(".ppt") || file.name.endsWith(".pptx")) { holder.iconIv.setImageResource(R.drawable.ppt) } else if (file.name.endsWith(".doc") || file.name.endsWith(".docx")) { holder.iconIv.setImageResource(R.drawable.doc) } else if (file.name.endsWith(".png") || file.name.endsWith(".jpg")) { holder.iconIv.setImageResource(R.drawable.photo) } else { holder.iconIv.setImageResource(R.drawable.unknown_file) } } holder.nameTv.text = file.name return view } private inner class ViewHolder(view: View) { var nameTv: TextView = view.findViewById(R.id.file_name_tv) as TextView var iconIv: ImageView = view.findViewById(R.id.file_icon_iv) as ImageView } }
这里没什么可以讲的东西,唯一能提的就是通过文件类型显示不同的图标,截图如下:
导入文件解析
DecodeFileThread线程是用来进行解析文件,并将结果以ArrayList<AccountBean>的形式回调,直接贴代码:/** * Author : BlackHao * Time : 2017/11/16 15:44 * Description : 解析文件 */ class DecodeFileThread(filePath: String, callBack: AccountsCallBack) : Thread() { val path: String = filePath val callback = callBack override fun run() { super.run() val br = BufferedReader(FileReader(path)) val list = arrayListOf<AccountBean>() var bean = AccountBean() var text = br.readLine() while (text != null) { when {text.isEmpty() -> { addBeanToList(bean, list) bean = AccountBean() } text.contains(":") -> { splitData(":", text, bean) } text.contains(":") -> { splitData(":", text, bean) } else -> { bean.name = text } } text = br.readLine() } //是否存在最后一个未保存 addBeanToList(bean, list) callback.searchFinish(list) } //分理数据 private fun splitData(splitTag: String, splitData: String, saveData: AccountBean) { //去掉空格 val texts = splitData.replace(" ", "").split(splitTag) when { texts[0].contains("备注") -> saveData.notes = texts[1].trim() texts[0].contains("账户") -> saveData.account = texts[1].trim() texts[0].contains("密码") -> saveData.psw = texts[1].trim() else -> { if (saveData.notes!!.isEmpty()) { saveData.notes = saveData.notes + splitData } else { saveData.notes = saveData.notes + "\n" + splitData } } } } //将实体类添加到list中 private fun addBeanToList(bean: AccountBean, list: ArrayList<AccountBean>) { if (bean.account.isEmpty() && bean.name.isNotEmpty()) { //当账户为空时,将账户设置为账户类型 bean.account = bean.name list.add(bean) } else if (bean.account.isNotEmpty()) { list.add(bean) } } }
这里导入的文件的格式进行了严格的限定,账户类型不能为空,且必须以空行作为两个账户分隔符,不然就会解析失败,大概的文件格式如下图所示:
这里简单分析一下部分代码:
在run()里面文件解析的代码是关键部分,截图如下:
这部分代码的思路就是:通过BufferedReader按行读取指定文件,读取完一行之后,通过判断该行:
1.不为空行的情况下,是否包含中英文的冒号,包含冒号则是“账户”/“密码”/“备注”之一,不包含则是账户类型。
2.空行则认为一个账户解析完成,开始解析下一个。
3.读完文件最后一行不为空行,也认为账户解析完成。
splitData()用于分离数据,即通过中英文冒号来将需要的信息分离出来,这里只是简单的认为冒号右边的数据为有效数据。另外,如果出现备注/账户/密码之外包含冒号的信息,则统一认为备注部分,该行数据直接添加到备注。截图如下:
文件导出
文件导出没有任何需要介绍的东西,就是将选中的账户信息写入文件,写完一个账户就以空行作为分隔符,代码一眼就能看懂,截图如下:SelectImportActivity
文件导入,以及导出所有的逻辑全部在SelectImportActivity中实现,因为这两个功能除了几个文字不同,界面基本上全部一样,导入界面截图如下:SelectImportActivity代码如下:
/** * Author : BlackHao * Time : 2017/11/16 15:15 * Description : 选择需要导入/导出账户信息 */ class SelectImportActivity : BaseActivity(), AccountsCallBack { private lateinit var loadFragment: LoadFragment //导入实体类 private lateinit var importList: ArrayList<ImportBean> //Adapter private lateinit var adapter: ImportListAdapter //控件 private lateinit var listView: ListView private lateinit var selectAll: CheckBox private lateinit var importBt: Button private lateinit var titleTv: TextView //导入/导出路径 private lateinit var path: String //数据库操作类 private lateinit var dao: DbDao //导入/导出 private var isImport = false override fun initView() { setContentView(R.layout.activity_import) //获取数据库工具类 dao = (this.application as AccountApp).dao loadFragment = LoadFragment() loadFragment.show(supportFragmentManager, "Load") //初始化控件 listView = findViewById(R.id.import_list_view) as ListView importBt = findViewById(R.id.ensure_import) as Button selectAll = findViewById(R.id.select_all_ib) as CheckBox titleTv = findViewById(R.id.title) as TextView //设置监听 selectAll.setOnCheckedChangeListener { _, checked -> if (importList.size > 0) { importList.forEach { it.isSelect = checked } adapter.notifyDataSetChanged() } } importBt.setOnClickListener { loadFragment.show(supportFragmentManager, "import") //开启线程更新数据库 Thread(Runnable { if (isImport) { //筛选选中的账户添加数据库 importList.filter { it.isSelect }.forEach { dao.addNewAccount(it.bean) } } else { //筛选选中的写入文件 val fos = FileOutputStream(path + File.separator + Constant.EXPORT_FILE_NAME) importList.filter { it.isSelect }.forEach { if (it.bean.name.isNotEmpty()) { fos.write((it.bean.name + "\n").toByteArray()) } if (it.bean.account.isNotEmpty()) { fos.write(("账户 : " + it.bean.account + "\n").toByteArray()) } if (it.bean.psw.isNotEmpty()) { fos.write(("密码 : " + it.bean.psw + "\n").toByteArray()) } if (it.bean.notes!!.isNotEmpty()) { fos.write(("备注 : " + it.bean.notes + "\n").toByteArray()) } //换行 fos.write("\n".toByteArray()) fos.flush() } //关闭流 fos.close() } //结束当前activity setResult(200, intent) finish() }).start() } } override fun initData() { path = intent.getStringExtra("path") isImport = intent.getBooleanExtra("isImport", false) //初始化ListView相关 importList = arrayListOf() adapter = ImportListAdapter(importList, this) listView.adapter = adapter if (isImport) { importBt.text = getString(R.string.import_) titleTv.text = getString(R.string.select_import) //导入则解析文件 DecodeFileThread(path, this).start() } else { importBt.text = getString(R.string.export) titleTv.text = getString(R.string.select_export) //导出则获取数据库数据 SearchDataThread("", this, dao).start() } } override fun searchFinish(list: ArrayList<AccountBean>) { runOnUiThread { //导入时去掉已经存在的账户 importList.clear() list.forEach { if (!dao.isAccountExist(it) || !isImport) { importList.add(ImportBean(false, it)) } } adapter.notifyDataSetChanged() loadFragment.dismiss() } } }
简单分析一下代码:
1.在initData()中通过intent获取文件/文件夹路径,已经当前是导入/导出,并对应修改界面显示:
结语
1.因为文字功底有限,所以介绍性的文字不多,但是基本上每句代码都加了注释,理解起来应该不难,如果有任何问题,可以留言。2.这里只附上了我认为比较关键的代码,布局文件等都未涉及,需要的可以去下载源码。
3.项目源码下载地址:http://download.csdn.net/download/a512337862/10151418
相关文章推荐
- kotlin实现的简单个人账户管理APP(一) 数据库的实现
- kotlin实现的简单个人账户管理APP(三) 自定义View仿支付宝的密码输入框/密码相关逻辑
- 基于kotlin实现的简单个人账户管理APP
- Django 一个简单的图书管理程序(六 添加CSV文件导入导出操作)
- 【JavaWeb开发】使用java实现简单的Excel文件的导入与导出(POI)
- C下学生管理系统:从文件中读取30位学生的信息(含邮箱),并实现简单的增、删、查找、统计(邮箱使用人数)。---附程序哦!
- mysql 简单导入导出多种命令方法实现
- 如何实现AD域账户导入导出
- Swing实现文件选择(目录选择)附导出
- Java语言实现简单FTP软件------>远程文件管理模块的实现(十)
- ado.net实现简单的数据导入和导出
- Android把手机作为FTP服务器,在PC端管理手机文件的简单实现
- 在SQL Server 中,如何实现DBF文件和SQL Server表之间的导入或者导出?
- Android把手机作为FTP服务器,在PC端管理手机文件的简单实现
- 教你简单实现PHP文件管理
- 实现一个配置简单功能强大的excel工具类搞定excel导入导出(二)
- 如何实现AD域账户导入导出
- asp.net 导入、导出csv文件简单应用
- Java语言实现简单FTP软件------>本地文件管理模块的实现(九)
- 本文实现了一个基于servlet技术的简单的csv文件导出的程序实例。