Kotlin - 处理Android-WebView文件上传的工具类

最近开发上遇到需要处理WebView进行文件上传的问题,但是由于原生的WebView并不支持文件上传,只能我们重写WebChromeClient类中的onShowFileChooser方法,下面我们就来实现这个操作。

Kotlin - 处理Android-WebView文件上传的工具类_第1张图片
效果图,文件已选择
Kotlin - 处理Android-WebView文件上传的工具类_第2张图片
选择文件
Kotlin - 处理Android-WebView文件上传的工具类_第3张图片
切换存储设备

注意:该代码不兼容低版本设备。

object : WebChromeClient() {
            override fun onShowFileChooser(
                webView: WebView?,
                filePathCallback: ValueCallback>?,
                fileChooserParams: FileChooserParams?
            ): Boolean {
                /// 此次处理文件上传
                return true
            }
        }
  • 准备工作,处理图片上传和文件上传

    1. 图片选择对话框,这里我们使用第三方库

      implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.3.4'
      
    2. 文件选择对话框,我们手动实现一个简单的文件选择页面

      -- 后面贴出具体代码。

  • WebViewFileUploader,处理WebView文件上传的工具类

    1. 由于需要处理Activity的返回值,因此需要接收onActivityResult的数据。

    2. 需要处理接收到的文件的返回或者取消文件选择的操作(置空ValueCallback>的对象)

      class WebViewFileUploader(
           private val activity: Activity,
           filePathCallback: ValueCallback>?,
           acceptType: String?
      ) {
      
      private var fileUploadCallback: ValueCallback>? = 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
              }
          }
       }
      
      }
      
  • 文件管理器的简单实现

    需求:

    • 支持文件选择,因此需要展示文件夹、文件(支持记忆上一次目录的位置)
    • 支持切换存储设备选择(有内存卡的)
    • 基本的额文件图标展示
    • 支持展示路径面包屑,面包屑支持点击操作

    实现步骤:

    1. 获取手机中内存设备

      /// 获取内存储备地址,由于方法getVolumePaths被隐藏,因此需要通过反射获取。
      @Suppress("unchecked_cast")
      fun Context.getStoragePaths(): Array? = try {
          with(StorageManager::class.java.getMethod("getVolumePaths")) {
              isAccessible = true
              invoke((getSystemService(Context.STORAGE_SERVICE) as StorageManager)) as? Array
          }
      } 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)
          }
      
    2. 列出目录中的文件夹及文件

      object FileScannerUtils {
      
          /**
           * 列出某个目录下的文件目录和文件夹
           * @param folderPath 文件夹路径
           * @param fileFilter 要匹配的文件类型
           */
          @JvmStatic
          fun list(
              folderPath: String,
              fileFilter: FileFilter,
              onCallback: (folders: List, files: List) -> Unit,
              onError: (message: String) -> Unit
          ) {
              val f = File(folderPath)
              if (!f.canRead()) {
                  onError("文件不可读取!")
                  return
              }
              val files = mutableListOf()
              val folders = mutableListOf()
              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? = 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
          }
      }
      
    3. 展示文件及文件夹的Adapter类的实现

      abstract class BaseListAdapter @JvmOverloads constructor(
          val context: Context,
          @LayoutRes private val layoutId: Int,
          var dataSource: MutableList? = mutableListOf()
      ) : BaseAdapter() {
      
          /**  获取适配器数据源  **/
          fun getAdapterDataSource(): MutableList? = 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, 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, 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  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(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(id)
                  imageViewInstance?.let {
                      ImageLoader.displayImage(imageViewInstance, uri = url, target = imageViewInstance)
                  }
                  return this
              }
      
          }
      
      }
      
      
      class FileManagerAdapter(context: Context) :
          BaseListAdapter(context, R.layout.item_for_file_folder) {
      
          override fun bindValues(holder: ViewHolder, position: Int, itemData: File) {
              val tv = holder.getViewById(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(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
          }
      
      }
      
    4. 面包屑的Adapter的实现

      /**
       * Created by Jbtm on 2017/4/24.
       * RecyclerAdapter通用数据适配器
       */
      abstract class BaseRecyclerAdapter
      /**
       * 构造函数
       * @param context 上下文对象
       * *
       * @param itemId 布局ItemId
       * *
       * @param dataSource 数据源
       */
      @JvmOverloads constructor(
          val context: Context, @LayoutRes val layoutId: Int,
          dataSource: MutableList? = null
      ) : RecyclerView.Adapter() {
      
          protected var inflater: LayoutInflater = LayoutInflater.from(context)
      
          protected var dataSource: MutableList? = null
      
          var onItemClickListener: OnItemClickListener? = null
      
          init {
              this.dataSource = dataSource ?: mutableListOf()
          }
      
          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, 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, 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 {
              fun onItemClick(holder: GenericViewHolder, viewPosition: Int, dataPosition: Int, item: E)
          }
      
      }
      
      
      /// 面包屑类的实现
      class BreadCrumbsAdapter(context: Context) :
          BaseRecyclerAdapter>(context, R.layout.item_for_breadcrumbs) {
      
          var onItemClick: ((data: Map, relativePath: String, position: Int) -> Unit)? = null
      
          override fun bindValues(
              holder: GenericViewHolder,
              viewPosition: Int,
              dataPosition: Int,
              item: Map
          ) {
              val tv = holder.itemView.findViewById(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
                  )
              }
          }
      
      }
      
    5. 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()
      
          private val lsPositionCache = mutableMapOf()
      
          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 =
                      [email protected](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>?,
                    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
        }
    
    }
    

项目下载地址:查看,转载请声明出处,谢谢!

你可能感兴趣的:(Kotlin - 处理Android-WebView文件上传的工具类)