最近開發上遇到需要處理WebView進行文件上傳的問題,但是由于原生的WebView并不支持文件上傳,只能我們重寫WebChromeClient類中的onShowFileChooser方法,下面我們就來實現這個操作。
效果圖,文件已選擇
選擇文件
切換存儲設備
注意:該代碼不兼容低版本設備。
object : WebChromeClient() {
override fun onShowFileChooser(
webView: WebView?,
filePathCallback: ValueCallback<Array<Uri>>?,
fileChooserParams: FileChooserParams?
): Boolean {
/// 此次處理文件上傳
return true
}
}
-
準備工作,處理圖片上傳和文件上傳
-
圖片選擇對話框,這里我們使用第三方庫
implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.3.4'
-
文件選擇對話框,我們手動實現一個簡單的文件選擇頁面
-- 后面貼出具體代碼。
-
-
WebViewFileUploader,處理WebView文件上傳的工具類
由于需要處理Activity的返回值,因此需要接收onActivityResult的數據。
-
需要處理接收到的文件的返回或者取消文件選擇的操作(置空ValueCallback<Array<Uri>>的對象)
class WebViewFileUploader( private val activity: Activity, filePathCallback: ValueCallback<Array<Uri>>?, acceptType: String? ) { private var fileUploadCallback: ValueCallback<Array<Uri>>? = filePathCallback init { when { acceptType?.toLowerCase(Locale.getDefault())?.startsWith("image/*") == true -> pickPicture() else -> activity.startActivityForResult( Intent( activity, FileManagerActivity::class.java ), FileManagerActivity.CHOOSE_REQUEST ) } } /** 從相冊獲取圖片 **/ private fun pickPicture(): Unit = PictureSelector.create(activity) .openGallery(PictureMimeType.ofImage())//全部.PictureMimeType.ofAll()、圖片.ofImage()、視頻.ofVideo()、音頻.ofAudio() .loadImageEngine(GlideEngine.createGlideEngine()) .maxSelectNum(1)// 最大圖片選擇數量 int .minSelectNum(1)// 最小選擇數量 int .imageSpanCount(4)// 每行顯示個數 int .selectionMode(PictureConfig.SINGLE)// 多選 or 單選 PictureConfig.MULTIPLE or PictureConfig.SINGLE .previewImage(true)// 是否可預覽圖片 true or false .isCamera(true)// 是否顯示拍照按鈕 true or false .imageFormat(PictureMimeType.JPEG)// 拍照保存圖片格式后綴,默認jpeg .isZoomAnim(true)// 圖片列表點擊 縮放效果 默認true .compress(true)// 是否壓縮 true or false .enableCrop(false) .isGif(true)// 是否顯示gif圖片 true or false .openClickSound(false)// 是否開啟點擊聲音 true or false .previewEggs(true)// 預覽圖片時 是否增強左右滑動圖片體驗(圖片滑動一半即可看到上一張是否選中) true or false .minimumCompressSize(100)// 小于100kb的圖片不壓縮 .synOrAsy(true)//同步true或異步false 壓縮 默認同步 .forResult(PictureConfig.CHOOSE_REQUEST)//結果回調onActivityResult code /** * 處理Activity返回結果 * @param requestCode 請求碼 * @param resultCode 結果碼 * @param data 數據包 */ fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { when (requestCode) { PictureConfig.CHOOSE_REQUEST -> { //圖片選擇 when (resultCode) { Activity.RESULT_OK -> { // 圖片、視頻、音頻選擇結果回調 val selectList = PictureSelector.obtainMultipleResult(data) val imgPath = with(selectList.firstOrNull()) { when { this == null -> null else -> compressPath ?: path } } if (!imgPath.isNullOrEmpty()) { val imgFile = File(imgPath) if (imgFile.exists()) { val uris = arrayOf(activity.getUriFromFileProvider(imgFile)) fileUploadCallback?.onReceiveValue(uris) fileUploadCallback = null return } } } } fileUploadCallback?.onReceiveValue(null) fileUploadCallback = null } FileManagerActivity.CHOOSE_REQUEST -> { //文件選擇,具體實現請看下面的內容 when (resultCode) { Activity.RESULT_OK -> { val filePath = data?.getStringExtra("file") if (!filePath.isNullOrEmpty()) { val file = File(filePath) if (file.exists()) { val uris = arrayOf(activity.getUriFromFileProvider(file)) fileUploadCallback?.onReceiveValue(uris) fileUploadCallback = null return } } } } //沒有接收到文件時,需要返回為空,不然不支持第二次文件的選擇【切記切記】 fileUploadCallback?.onReceiveValue(null) fileUploadCallback = null } } } }
-
文件管理器的簡單實現
需求:
- 支持文件選擇,因此需要展示文件夾、文件(支持記憶上一次目錄的位置)
- 支持切換存儲設備選擇(有內存卡的)
- 基本的額文件圖標展示
- 支持展示路徑面包屑,面包屑支持點擊操作
實現步驟:
-
獲取手機中內存設備
/// 獲取內存儲備地址,由于方法getVolumePaths被隱藏,因此需要通過反射獲取。 @Suppress("unchecked_cast") fun Context.getStoragePaths(): Array<String>? = try { with(StorageManager::class.java.getMethod("getVolumePaths")) { isAccessible = true invoke((getSystemService(Context.STORAGE_SERVICE) as StorageManager)) as? Array<String> } } catch (e: Exception) { e.printStackTrace() null } /** * 獲取文件得Uri * @param file 文件對象 */ fun Context.getUriFromFileProvider(file: File): Uri = when (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { true -> FileProvider.getUriForFile(this, "$packageName.FileProvider", file) else -> Uri.fromFile(file) }
-
列出目錄中的文件夾及文件
object FileScannerUtils { /** * 列出某個目錄下的文件目錄和文件夾 * @param folderPath 文件夾路徑 * @param fileFilter 要匹配的文件類型 */ @JvmStatic fun list( folderPath: String, fileFilter: FileFilter, onCallback: (folders: List<File>, files: List<File>) -> Unit, onError: (message: String) -> Unit ) { val f = File(folderPath) if (!f.canRead()) { onError("文件不可讀取!") return } val files = mutableListOf<File>() val folders = mutableListOf<File>() f.listFiles(fileFilter)?.forEach { when { it.isFile -> files.add(it) it.isDirectory -> folders.add(it) } } files.sortBy { it.name.toLowerCase(Locale.getDefault()) } folders.sortBy { it.name.toLowerCase(Locale.getDefault()) } onCallback(folders, files) } } /** 文件篩選類型 **/ enum class FileFilterType { None, Picture, Document } /** 文件篩選器工廠類 **/ object FileFilterFactory { @JvmStatic fun getFileFilter(type: FileFilterType = FileFilterType.None): MyFileFilter = when (type) { FileFilterType.None -> noneFileFilter FileFilterType.Picture -> pictureFileFilter FileFilterType.Document -> documentFileFilter } /** 不篩選 **/ private val noneFileFilter get() = MyFileFilter() /** 圖片篩選 **/ private val pictureFileFilter get() = MyFileFilter( mutableListOf( "jpg", "jpeg", "png", "bmp", "webp" ) ) /** 文檔篩選 **/ private val documentFileFilter get() = MyFileFilter( mutableListOf( "txt", "doc", "docx", "xls", "xlsx", "pdf", "ppt", "wps", "java", "cs", "kt", "sql", "cpp", "c", "h" ) ) } /** * 我的文件篩選器 * @param fileExtensions 文件擴展集合 */ class MyFileFilter(private val fileExtensions: List<String>? = null) : FileFilter { override fun accept(f: File?): Boolean { if (fileExtensions?.isNotEmpty() != true || f?.isDirectory == true) return true if (f?.isFile == true) return f.extension.toLowerCase(Locale.getDefault()) in fileExtensions return false } }
-
展示文件及文件夾的Adapter類的實現
abstract class BaseListAdapter<E : Any> @JvmOverloads constructor( val context: Context, @LayoutRes private val layoutId: Int, var dataSource: MutableList<E>? = mutableListOf() ) : BaseAdapter() { /** 獲取適配器數據源 **/ fun getAdapterDataSource(): MutableList<E>? = dataSource /** * 添加數據 * * @param e 數據項 * @param isRefresh 是否刷新數據,默認為false */ @Synchronized @JvmOverloads fun add(e: E, isRefresh: Boolean = false) { dataSource?.add(e) if (isRefresh) notifyDataSetChanged() } /** * 添加數據 * * @param e 數據項 * @param index 要插入的索引位置 * @param isRefresh 是否刷新數據,默認為false */ @Synchronized @JvmOverloads fun add(e: E, index: Int, isRefresh: Boolean = false) { dataSource?.add(index, e) if (isRefresh) notifyDataSetChanged() } /** * 添加多個數據 * * @param elements 數據項集合 * @param isRefresh 是否刷新數據,默認為false */ @Synchronized @JvmOverloads fun add(elements: MutableList<E>, isRefresh: Boolean = false) { dataSource?.addAll(elements) if (isRefresh) notifyDataSetChanged() } /** * 添加數據集合 * @param es 數據集合 * @param isRefresh 是否刷新 */ @Synchronized @JvmOverloads fun add(vararg es: E, isRefresh: Boolean = false) { dataSource?.addAll(es.toMutableList()) if (isRefresh) notifyDataSetChanged() } /** * 刪除數據項 * * @param e 要刪除的數據項 * @param isRefresh 是否刷新數據,默認為false */ @Synchronized @JvmOverloads fun remove(e: E, isRefresh: Boolean = false) { dataSource?.remove(e) if (isRefresh) notifyDataSetChanged() } /** * 刪除某一索引位置的數據 * * @param index 數據索引位置 * @param isRefresh 是否刷新數據,默認為false */ @Synchronized @JvmOverloads fun remove(index: Int, isRefresh: Boolean = false) { dataSource?.removeAt(index) if (isRefresh) notifyDataSetChanged() } /** * 刪除數據集合 * * @param elements 數據集合 * @param isRefresh 是否刷新數據,默認為false */ @Synchronized @JvmOverloads fun remove(elements: MutableList<E>, isRefresh: Boolean = false) { dataSource?.removeAll(elements) if (isRefresh) notifyDataSetChanged() } /** * 更新某處的數據 * * @param index 要更新的數據的索引 * @param e 數據內容 * @param isRefresh 是否刷新數據,默認為false */ @Synchronized @JvmOverloads fun update(index: Int, e: E, isRefresh: Boolean = false) { dataSource?.set(index, e) if (isRefresh) notifyDataSetChanged() } /** * 清空所有數據 * * @param isRefresh 是否刷新數據,默認為false */ @Synchronized @JvmOverloads fun clear(isRefresh: Boolean = false) { dataSource?.clear() if (isRefresh) notifyDataSetChanged() } override fun getCount(): Int = dataSource?.size ?: 0 override fun getItem(position: Int): Any? = dataSource?.get(position) override fun getItemId(position: Int): Long = position.toLong() override fun getView(position: Int, convertView: View?, parent: ViewGroup): View? { val viewHolder = ViewHolder.getInstance(context, position, convertView, parent, layoutId) bindValues(viewHolder, position, dataSource?.get(position)!!) return viewHolder.convertView } /** * 綁定數據 * * @param holder * @param position 數據索引 * @param itemData 數據項 */ abstract fun bindValues(holder: ViewHolder, position: Int, itemData: E) /** * ViewHolder對象 * * @param context 上下文對象 * @param position 數據索引 * @param convertView itemView * @param parent Group-Root * @param layoutId item布局ID */ @Suppress("unused") class ViewHolder( val context: Context, val position: Int, convertView: View?, parent: ViewGroup?, @LayoutRes val layoutId: Int ) { companion object { /** * 獲取ViewHolder實例 * * @param context 上下文對象 * @param position 數據索引 * @param convertView itemView對象 * @param parent RootParent * @param layoutId Item布局ID */ @JvmStatic fun getInstance( context: Context, position: Int, convertView: View?, parent: ViewGroup?, layoutId: Int ): ViewHolder = if (convertView != null) convertView.tag as ViewHolder else ViewHolder(context, position, convertView, parent, layoutId) } var convertView: View? private set init { this.convertView = convertView ?: View.inflate(context, layoutId, null) this.convertView?.tag = this } /** * 根據ID獲取控件對象 * * @param id 控件ID */ @Suppress("UNCHECKED_CAST") fun <T : View> getViewById(@IdRes id: Int): T? { val result: View? = convertView?.findViewById(id) return result as? T? } /** * 設置文本控件的文本 * * @param id 控件ID * @param text 文本內容 */ fun setText(@IdRes id: Int, text: CharSequence): ViewHolder { getViewById<TextView>(id)?.text = text return this } /** * 設置文本控件的文本 * * @param id 控件ID * @param textId 數據String ID */ fun setText(@IdRes id: Int, @StringRes textId: Int): ViewHolder { setText(id, context.getString(textId)) return this } /** * 設置控件圖片 * * @param id 圖片控件ID * @param url 圖片數據URL對象 */ fun setImage(@IdRes id: Int, url: Any): ViewHolder { val imageViewInstance = getViewById<ImageView>(id) imageViewInstance?.let { ImageLoader.displayImage(imageViewInstance, uri = url, target = imageViewInstance) } return this } } } class FileManagerAdapter(context: Context) : BaseListAdapter<File>(context, R.layout.item_for_file_folder) { override fun bindValues(holder: ViewHolder, position: Int, itemData: File) { val tv = holder.getViewById<TextView>(android.R.id.text1) tv?.text = itemData.name ContextCompat.getDrawable( context, when { itemData.isDirectory -> R.drawable.ic_folder itemData.isFile -> FileIconProvider.getDrawableId(itemData.extension) else -> R.drawable.ic_unknown_file } )?.apply { setBounds(0, 0, intrinsicWidth, intrinsicHeight) holder.getViewById<ImageView>(R.id.imgIcon)?.setImageDrawable(this) } } } /// 文件圖標提供類 object FileIconProvider { @JvmStatic fun getDrawableId(extension: String): Int = when (extension.toLowerCase(Locale.getDefault())) { "txt" -> R.drawable.ic_file_txt "ppt" -> R.drawable.ic_file_ppt "doc" -> R.drawable.ic_file_doc "docx" -> R.drawable.ic_file_docx "xls" -> R.drawable.ic_file_xls "xlsx" -> R.drawable.ic_file_xls "png" -> R.drawable.ic_file_png "jpg", "jpeg" -> R.drawable.ic_file_jpg "java" -> R.drawable.ic_file_java "xml" -> R.drawable.ic_file_xml "html", "htm" -> R.drawable.ic_file_html "js" -> R.drawable.ic_file_js "mp3" -> R.drawable.ic_file_mp3 "mp4" -> R.drawable.ic_file_mp4 "dat" -> R.drawable.ic_file_dat "rmvb" -> R.drawable.ic_file_rmvb "avi" -> R.drawable.ic_file_avi "log" -> R.drawable.ic_file_log else -> R.drawable.ic_unknown_file } }
-
面包屑的Adapter的實現
/** * Created by Jbtm on 2017/4/24. * RecyclerAdapter通用數據適配器 */ abstract class BaseRecyclerAdapter<E> /** * 構造函數 * @param context 上下文對象 * * * @param itemId 布局ItemId * * * @param dataSource 數據源 */ @JvmOverloads constructor( val context: Context, @LayoutRes val layoutId: Int, dataSource: MutableList<E>? = null ) : RecyclerView.Adapter<BaseRecyclerAdapter.GenericViewHolder>() { protected var inflater: LayoutInflater = LayoutInflater.from(context) protected var dataSource: MutableList<E>? = null var onItemClickListener: OnItemClickListener<E>? = null init { this.dataSource = dataSource ?: mutableListOf<E>() } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GenericViewHolder { val itemView = inflater.inflate(layoutId, parent, false) return GenericViewHolder(itemView) } override fun onBindViewHolder(holder: GenericViewHolder, position: Int) { val item = dataSource!![position] if (onItemClickListener != null) holder.itemView.setOnClickListener { onItemClickListener!!.onItemClick( holder, holder.adapterPosition, position, item ) } bindValues(holder, holder.adapterPosition, position, item) } abstract fun bindValues( holder: GenericViewHolder, viewPosition: Int, dataPosition: Int, item: E ) override fun getItemCount(): Int = dataSource?.size ?: 0 /** * 添加一條數據 * @param item 數據項 * @param isRefresh 是否刷新數據 */ @JvmOverloads @Synchronized fun add(item: E, isRefresh: Boolean = false) { dataSource?.add(item) if (isRefresh) notifyDataSetChanged() } /** * 指定位置添加一條數據 * @param item 數據項 * @param position 要添加數據的位置 * @param isRefresh 是否刷新 */ @JvmOverloads @Synchronized fun add(item: E, position: Int, isRefresh: Boolean = false) { dataSource?.add(position, item) if (isRefresh) notifyDataSetChanged() } /** * 添加一條數據 * @param items 數據源 * @param isRefresh 是否刷新數據 */ @JvmOverloads @Synchronized fun add(items: MutableList<E>, isRefresh: Boolean = false) { dataSource?.addAll(items) if (isRefresh) notifyDataSetChanged() } /** * 添加一些數據 * @param items 數據集合 * @param isRefresh 是否刷新數據 */ @JvmOverloads @Synchronized fun add(vararg items: E, isRefresh: Boolean = false) { dataSource?.addAll(items) if (isRefresh) notifyDataSetChanged() } /** * 刪除一條數據 * @param item 數據項 * @param isRefresh 是否刷新數據 */ @JvmOverloads @Synchronized fun remove(item: E, isRefresh: Boolean = false) { dataSource?.remove(item) if (isRefresh) notifyDataSetChanged() } /** * 刪除一條指定位置的數據 * @param position 數據位置 * @param isRefresh 是否刷新數據 */ @JvmOverloads @Synchronized fun remove(position: Int, isRefresh: Boolean = false) { dataSource?.removeAt(position) if (isRefresh) notifyItemChanged(position) } /** * 刪除一些數據 * @param items 要刪除的數據 * @param isRefresh 是否刷新數據 */ @JvmOverloads @Synchronized fun remove(items: MutableList<E>, isRefresh: Boolean = false) { dataSource?.removeAll(items) if (isRefresh) notifyDataSetChanged() } /** * 更新數據源 * @param item 數據Item * @param position 要更新的位置 * @param isRefresh 是否刷新數據 */ @JvmOverloads @Synchronized fun update(item: E, position: Int, isRefresh: Boolean = false) { dataSource?.set(position, item) if (isRefresh) notifyItemChanged(position) } /** * 清空數據源 * @param isRefresh 是否刷新數據,默認:false */ @JvmOverloads @Synchronized fun clear(isRefresh: Boolean = false) { dataSource?.clear() if (isRefresh) notifyDataSetChanged() } /** * 獲取數據項 * @param position 數據position */ fun getItem(position: Int) = dataSource?.get(position) class GenericViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun setText(@IdRes id: Int, text: String) { val txt: TextView? = itemView.findViewById(id) txt?.text = text } fun setText(@IdRes id: Int, @StringRes textId: Int, vararg params: Any?) { val txt: TextView? = itemView.findViewById(id) txt?.text = itemView.context.getString(textId, params) } } interface OnItemClickListener<E> { fun onItemClick(holder: GenericViewHolder, viewPosition: Int, dataPosition: Int, item: E) } } /// 面包屑類的實現 class BreadCrumbsAdapter(context: Context) : BaseRecyclerAdapter<Map<String, Any>>(context, R.layout.item_for_breadcrumbs) { var onItemClick: ((data: Map<String, Any>, relativePath: String, position: Int) -> Unit)? = null override fun bindValues( holder: GenericViewHolder, viewPosition: Int, dataPosition: Int, item: Map<String, Any> ) { val tv = holder.itemView.findViewById<TextView>(android.R.id.text1) tv.text = item["text"].toString() if (item["hasNavigation"] != null && item["hasNavigation"] is Boolean && item["hasNavigation"] == true) { ContextCompat.getDrawable( context, R.drawable.ic_navigation_breadcrumbs_forward )?.apply { setBounds(0, 0, intrinsicWidth, intrinsicHeight) tv.setCompoundDrawables(this, null, null, null) tv.compoundDrawablePadding = DensityUtils.dip2px(context, 1f) } } else { tv.compoundDrawablePadding = 0 tv.setCompoundDrawables(null, null, null, null) } val padding = DensityUtils.dip2px(context, 5f) tv.setPadding(padding, 0, padding, 0) tv.setOnClickListener { onItemClick?.invoke( item, if (dataPosition == 0) "" else dataSource!!.subList( 1, dataPosition + 1 ).joinToString(File.separator) { it["text"].toString() }, dataPosition ) } } }
-
FileManagerActivity的實現
class FileManagerActivity : AppCompatActivity(), AdapterView.OnItemClickListener { companion object { /** 選擇請求 **/ const val CHOOSE_REQUEST = 19999 } private val fileManagerAdapter by lazy { FileManagerAdapter(this) } private val breadCrumbsAdapter by lazy { initializerBreadCrumbsAdapter() } private val sdcardPaths = mutableListOf<String>() private val lsPositionCache = mutableMapOf<String, Int>() private lateinit var currentSDCardPath: String private var currentFolder: File? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_file_manager) toolbar.setNavigationOnClickListener { if (!isFolderCanBack()) return@setNavigationOnClickListener else finish() } lvFiles.apply { onItemClickListener = this@FileManagerActivity adapter = fileManagerAdapter } rvBreadCrumbs.apply { layoutManager = LinearLayoutManager(this@FileManagerActivity, LinearLayoutManager.HORIZONTAL, false) adapter = breadCrumbsAdapter } sdcardPaths.apply { clear() addAll(getStoragePaths()?.toMutableList() ?: mutableListOf()) } if (!sdcardPaths.isNullOrEmpty()) { currentSDCardPath = sdcardPaths.first() currentFolder = File(currentSDCardPath) currentSDCardPath.listFoldersAndFiles() } } override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { val item = fileManagerAdapter.getItem(position) as File if (item.isDirectory) { if (!item.canRead()) { Toast.makeText(this, "該文件夾不可讀取", Toast.LENGTH_SHORT).show() return } if (item.parentFile?.exists() == true) lsPositionCache[item.parentFile!!.absolutePath] = lvFiles.firstVisiblePosition currentFolder = item item.absolutePath.listFoldersAndFiles() } if (item.isFile) { setResult(Activity.RESULT_OK, Intent().apply { putExtra("file", item.absolutePath) }) finish() } } override fun onBackPressed() { if (!isFolderCanBack()) return super.onBackPressed() } /** 是否是頂級目錄 **/ private fun String.isTopLevelFolder(): Boolean = isNullOrEmpty() || !File(this).exists() || this in sdcardPaths /** 是否是頂級目錄 **/ private fun File.isTopLevelFolder(): Boolean { if (!exists() || absolutePath.isNullOrEmpty()) return true return absolutePath in sdcardPaths } /** 文件夾是否支持返回 **/ private fun isFolderCanBack(): Boolean { if (currentFolder?.isTopLevelFolder() != false) return true val pf = currentFolder?.parentFile if (pf?.exists() == true) { currentFolder = pf pf.absolutePath.listFoldersAndFiles() return false } return true } /** 執行文件搜索 **/ private fun String.listFoldersAndFiles() = Thread { runOnUiThread { pbLoading.isVisible = true } FileScannerUtils.list( this, FileFilterFactory.getFileFilter(), { folders, files -> runOnUiThread { setCurrentBreadCrumbs() fileManagerAdapter.apply { clear() add(folders.toMutableList()) add(files.toMutableList()) notifyDataSetChanged() lvFiles.setSelection(lsPositionCache[this@listFoldersAndFiles] ?: 0) } pbLoading.isVisible = false } }) { runOnUiThread { pbLoading.isVisible = false } } }.start() /** 設置文件夾當前的面包屑 **/ private fun String.setCurrentBreadCrumbs() { val sdcardOrderIndex = sdcardPaths.indexOf(currentSDCardPath) + 1 val deviceName = "存儲設備${if (sdcardPaths.size > 1) sdcardOrderIndex.toString() else ""}" breadCrumbsAdapter.apply { clear() add(mapOf("text" to deviceName, "hasNavigation" to false)) val pathParts = this@setCurrentBreadCrumbs.replaceFirst(Regex.fromLiteral(currentSDCardPath), "") .split(File.separator).filter { v -> !TextUtils.isEmpty(v) } pathParts.forEach { item -> add(mapOf("text" to item, "hasNavigation" to true)) } notifyDataSetChanged() } } /** 初始化面包屑適配器 **/ private fun initializerBreadCrumbsAdapter() = BreadCrumbsAdapter(this).apply { onItemClick = here@{ _, relativePath, _ -> if (relativePath.isEmpty()) { if ((currentFolder?.isTopLevelFolder() != false)) { if (sdcardPaths.size <= 1) return@here showSDCardChooseDialog() } else { currentFolder = File(currentSDCardPath) } } else { currentFolder = File("$currentSDCardPath/$relativePath") } currentFolder?.absolutePath?.listFoldersAndFiles() } } /** 顯示SDCard的選擇對話框 **/ private fun showSDCardChooseDialog() = AlertDialog.Builder(this) .setTitle("請選擇存儲設備") .setItems(mutableListOf(*sdcardPaths.toTypedArray()).mapIndexed { index, item -> "存儲設備${index + 1}${if (item == currentSDCardPath) "(當前)" else ""}" }.toTypedArray()) { dialog, which -> currentSDCardPath = sdcardPaths[which] currentFolder = File(currentSDCardPath) currentSDCardPath.listFoldersAndFiles() dialog.dismiss() } .create() .show() }
-
在WebView中調用示例
class MainActivity : AppCompatActivity() { private var fileUploader: WebViewFileUploader? = null @SuppressLint("SetJavaScriptEnabled") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) browser.settings.apply { javaScriptEnabled = true cacheMode = WebSettings.LOAD_NO_CACHE } browser.webChromeClient = object : WebChromeClient() { override fun onShowFileChooser( webView: WebView?, filePathCallback: ValueCallback<Array<Uri>>?, fileChooserParams: FileChooserParams? ): Boolean { fileUploader = WebViewFileUploader( this@MainActivity, filePathCallback, fileChooserParams?.acceptTypes?.firstOrNull() ) return true } } browser.loadUrl("http://192.168.0.102:3000/") } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) fileUploader?.onActivityResult(requestCode, resultCode, data) fileUploader = null } }
項目下載地址:查看,轉載請聲明出處,謝謝!