Kotlin - 處理Android-WebView文件上傳的工具類

最近開發(fā)上遇到需要處理WebView進(jìn)行文件上傳的問題,但是由于原生的WebView并不支持文件上傳,只能我們重寫WebChromeClient類中的onShowFileChooser方法,下面我們就來實(shí)現(xiàn)這個(gè)操作。

效果圖,文件已選擇
選擇文件
切換存儲(chǔ)設(shè)備

注意:該代碼不兼容低版本設(shè)備。

object : WebChromeClient() {
            override fun onShowFileChooser(
                webView: WebView?,
                filePathCallback: ValueCallback<Array<Uri>>?,
                fileChooserParams: FileChooserParams?
            ): Boolean {
                /// 此次處理文件上傳
                return true
            }
        }
  • 準(zhǔn)備工作,處理圖片上傳和文件上傳

    1. 圖片選擇對(duì)話框,這里我們使用第三方庫(kù)

      implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.3.4'
      
    2. 文件選擇對(duì)話框,我們手動(dòng)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的文件選擇頁(yè)面

      -- 后面貼出具體代碼。

  • WebViewFileUploader,處理WebView文件上傳的工具類

    1. 由于需要處理Activity的返回值,因此需要接收onActivityResult的數(shù)據(jù)。

    2. 需要處理接收到的文件的返回或者取消文件選擇的操作(置空ValueCallback<Array<Uri>>的對(duì)象)

      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
              )
          }
      }
      
      /** 從相冊(cè)獲取圖片 **/
      private fun pickPicture(): Unit = PictureSelector.create(activity)
          .openGallery(PictureMimeType.ofImage())//全部.PictureMimeType.ofAll()、圖片.ofImage()、視頻.ofVideo()、音頻.ofAudio()
          .loadImageEngine(GlideEngine.createGlideEngine())
          .maxSelectNum(1)// 最大圖片選擇數(shù)量 int
          .minSelectNum(1)// 最小選擇數(shù)量 int
          .imageSpanCount(4)// 每行顯示個(gè)數(shù) int
          .selectionMode(PictureConfig.SINGLE)// 多選 or 單選 PictureConfig.MULTIPLE or PictureConfig.SINGLE
          .previewImage(true)// 是否可預(yù)覽圖片 true or false
          .isCamera(true)// 是否顯示拍照按鈕 true or false
          .imageFormat(PictureMimeType.JPEG)// 拍照保存圖片格式后綴,默認(rèn)jpeg
          .isZoomAnim(true)// 圖片列表點(diǎn)擊 縮放效果 默認(rèn)true
          .compress(true)// 是否壓縮 true or false
          .enableCrop(false)
          .isGif(true)// 是否顯示gif圖片 true or false
          .openClickSound(false)// 是否開啟點(diǎn)擊聲音 true or false
          .previewEggs(true)// 預(yù)覽圖片時(shí) 是否增強(qiáng)左右滑動(dòng)圖片體驗(yàn)(圖片滑動(dòng)一半即可看到上一張是否選中) true or false
          .minimumCompressSize(100)// 小于100kb的圖片不壓縮
          .synOrAsy(true)//同步true或異步false 壓縮 默認(rèn)同步
          .forResult(PictureConfig.CHOOSE_REQUEST)//結(jié)果回調(diào)onActivityResult code
      
      /**
       * 處理Activity返回結(jié)果
       * @param requestCode 請(qǐng)求碼
       * @param resultCode 結(jié)果碼
       * @param data 數(shù)據(jù)包
       */
      fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
          when (requestCode) {
              PictureConfig.CHOOSE_REQUEST -> { //圖片選擇
                  when (resultCode) {
                      Activity.RESULT_OK -> {
                          // 圖片、視頻、音頻選擇結(jié)果回調(diào)
                          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 -> { //文件選擇,具體實(shí)現(xiàn)請(qǐng)看下面的內(nèi)容
                  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
                              }
                          }
                      }
                  }
                  //沒有接收到文件時(shí),需要返回為空,不然不支持第二次文件的選擇【切記切記】
                  fileUploadCallback?.onReceiveValue(null)
                  fileUploadCallback = null
              }
          }
       }
      
      }
      
  • 文件管理器的簡(jiǎn)單實(shí)現(xiàn)

    需求:

    • 支持文件選擇,因此需要展示文件夾、文件(支持記憶上一次目錄的位置)
    • 支持切換存儲(chǔ)設(shè)備選擇(有內(nèi)存卡的)
    • 基本的額文件圖標(biāo)展示
    • 支持展示路徑面包屑,面包屑支持點(diǎn)擊操作

    實(shí)現(xiàn)步驟:

    1. 獲取手機(jī)中內(nèi)存設(shè)備

      /// 獲取內(nèi)存儲(chǔ)備地址,由于方法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 文件對(duì)象
       */
      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 {
      
          /**
           * 列出某個(gè)目錄下的文件目錄和文件夾
           * @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 文件擴(kuò)展集合
       */
      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
          }
      }
      
    3. 展示文件及文件夾的Adapter類的實(shí)現(xiàn)

      abstract class BaseListAdapter<E : Any> @JvmOverloads constructor(
          val context: Context,
          @LayoutRes private val layoutId: Int,
          var dataSource: MutableList<E>? = mutableListOf()
      ) : BaseAdapter() {
      
          /**  獲取適配器數(shù)據(jù)源  **/
          fun getAdapterDataSource(): MutableList<E>? = dataSource
      
          /**
           * 添加數(shù)據(jù)
           *
           * @param e 數(shù)據(jù)項(xiàng)
           * @param isRefresh 是否刷新數(shù)據(jù),默認(rèn)為false
           */
          @Synchronized
          @JvmOverloads
          fun add(e: E, isRefresh: Boolean = false) {
              dataSource?.add(e)
              if (isRefresh)
                  notifyDataSetChanged()
          }
      
          /**
           * 添加數(shù)據(jù)
           *
           * @param e 數(shù)據(jù)項(xiàng)
           * @param index 要插入的索引位置
           * @param isRefresh 是否刷新數(shù)據(jù),默認(rèn)為false
           */
          @Synchronized
          @JvmOverloads
          fun add(e: E, index: Int, isRefresh: Boolean = false) {
              dataSource?.add(index, e)
              if (isRefresh)
                  notifyDataSetChanged()
          }
      
          /**
           * 添加多個(gè)數(shù)據(jù)
           *
           * @param elements 數(shù)據(jù)項(xiàng)集合
           * @param isRefresh 是否刷新數(shù)據(jù),默認(rèn)為false
           */
          @Synchronized
          @JvmOverloads
          fun add(elements: MutableList<E>, isRefresh: Boolean = false) {
              dataSource?.addAll(elements)
              if (isRefresh)
                  notifyDataSetChanged()
          }
      
          /**
           * 添加數(shù)據(jù)集合
           * @param es 數(shù)據(jù)集合
           * @param isRefresh 是否刷新
           */
          @Synchronized
          @JvmOverloads
          fun add(vararg es: E, isRefresh: Boolean = false) {
              dataSource?.addAll(es.toMutableList())
              if (isRefresh)
                  notifyDataSetChanged()
          }
      
          /**
           * 刪除數(shù)據(jù)項(xiàng)
           *
           * @param e 要?jiǎng)h除的數(shù)據(jù)項(xiàng)
           * @param isRefresh 是否刷新數(shù)據(jù),默認(rèn)為false
           */
          @Synchronized
          @JvmOverloads
          fun remove(e: E, isRefresh: Boolean = false) {
              dataSource?.remove(e)
              if (isRefresh)
                  notifyDataSetChanged()
          }
      
          /**
           * 刪除某一索引位置的數(shù)據(jù)
           *
           * @param index 數(shù)據(jù)索引位置
           * @param isRefresh 是否刷新數(shù)據(jù),默認(rèn)為false
           */
          @Synchronized
          @JvmOverloads
          fun remove(index: Int, isRefresh: Boolean = false) {
              dataSource?.removeAt(index)
              if (isRefresh)
                  notifyDataSetChanged()
          }
      
          /**
           * 刪除數(shù)據(jù)集合
           *
           * @param elements 數(shù)據(jù)集合
           * @param isRefresh 是否刷新數(shù)據(jù),默認(rèn)為false
           */
          @Synchronized
          @JvmOverloads
          fun remove(elements: MutableList<E>, isRefresh: Boolean = false) {
              dataSource?.removeAll(elements)
              if (isRefresh)
                  notifyDataSetChanged()
          }
      
          /**
           * 更新某處的數(shù)據(jù)
           *
           * @param index 要更新的數(shù)據(jù)的索引
           * @param e 數(shù)據(jù)內(nèi)容
           * @param isRefresh 是否刷新數(shù)據(jù),默認(rèn)為false
           */
          @Synchronized
          @JvmOverloads
          fun update(index: Int, e: E, isRefresh: Boolean = false) {
              dataSource?.set(index, e)
              if (isRefresh)
                  notifyDataSetChanged()
          }
      
          /**
           * 清空所有數(shù)據(jù)
           *
           * @param isRefresh 是否刷新數(shù)據(jù),默認(rèn)為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
          }
      
          /**
           * 綁定數(shù)據(jù)
           *
           * @param holder
           * @param position 數(shù)據(jù)索引
           * @param itemData 數(shù)據(jù)項(xiàng)
           */
          abstract fun bindValues(holder: ViewHolder, position: Int, itemData: E)
      
          /**
           * ViewHolder對(duì)象
           *
           * @param context 上下文對(duì)象
           * @param position 數(shù)據(jù)索引
           * @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實(shí)例
                   *
                   * @param context 上下文對(duì)象
                   * @param position 數(shù)據(jù)索引
                   * @param convertView itemView對(duì)象
                   * @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
              }
      
              /**
               * 根據(jù)ID獲取控件對(duì)象
               *
               * @param id 控件ID
               */
              @Suppress("UNCHECKED_CAST")
              fun <T : View> getViewById(@IdRes id: Int): T? {
                  val result: View? = convertView?.findViewById(id)
                  return result as? T?
              }
      
              /**
               * 設(shè)置文本控件的文本
               *
               * @param id 控件ID
               * @param text 文本內(nèi)容
               */
              fun setText(@IdRes id: Int, text: CharSequence): ViewHolder {
                  getViewById<TextView>(id)?.text = text
                  return this
              }
      
              /**
               * 設(shè)置文本控件的文本
               *
               * @param id  控件ID
               * @param textId 數(shù)據(jù)String ID
               */
              fun setText(@IdRes id: Int, @StringRes textId: Int): ViewHolder {
                  setText(id, context.getString(textId))
                  return this
              }
      
              /**
               * 設(shè)置控件圖片
               *
               * @param id 圖片控件ID
               * @param url 圖片數(shù)據(jù)URL對(duì)象
               */
              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)
              }
          }
      
      }
      
      /// 文件圖標(biāo)提供類
      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的實(shí)現(xiàn)

      /**
       * Created by Jbtm on 2017/4/24.
       * RecyclerAdapter通用數(shù)據(jù)適配器
       */
      abstract class BaseRecyclerAdapter<E>
      /**
       * 構(gòu)造函數(shù)
       * @param context 上下文對(duì)象
       * *
       * @param itemId 布局ItemId
       * *
       * @param dataSource 數(shù)據(jù)源
       */
      @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
      
          /**
           * 添加一條數(shù)據(jù)
           * @param item 數(shù)據(jù)項(xiàng)
           * @param isRefresh 是否刷新數(shù)據(jù)
           */
          @JvmOverloads
          @Synchronized
          fun add(item: E, isRefresh: Boolean = false) {
              dataSource?.add(item)
              if (isRefresh)
                  notifyDataSetChanged()
          }
      
          /**
           * 指定位置添加一條數(shù)據(jù)
           * @param item 數(shù)據(jù)項(xiàng)
           * @param position 要添加數(shù)據(jù)的位置
           * @param isRefresh 是否刷新
           */
          @JvmOverloads
          @Synchronized
          fun add(item: E, position: Int, isRefresh: Boolean = false) {
              dataSource?.add(position, item)
              if (isRefresh)
                  notifyDataSetChanged()
          }
      
          /**
           * 添加一條數(shù)據(jù)
           * @param items 數(shù)據(jù)源
           * @param isRefresh 是否刷新數(shù)據(jù)
           */
          @JvmOverloads
          @Synchronized
          fun add(items: MutableList<E>, isRefresh: Boolean = false) {
              dataSource?.addAll(items)
              if (isRefresh)
                  notifyDataSetChanged()
          }
      
          /**
           * 添加一些數(shù)據(jù)
           * @param items 數(shù)據(jù)集合
           * @param isRefresh 是否刷新數(shù)據(jù)
           */
          @JvmOverloads
          @Synchronized
          fun add(vararg items: E, isRefresh: Boolean = false) {
              dataSource?.addAll(items)
              if (isRefresh)
                  notifyDataSetChanged()
          }
      
          /**
           * 刪除一條數(shù)據(jù)
           * @param item 數(shù)據(jù)項(xiàng)
           * @param isRefresh 是否刷新數(shù)據(jù)
           */
          @JvmOverloads
          @Synchronized
          fun remove(item: E, isRefresh: Boolean = false) {
              dataSource?.remove(item)
              if (isRefresh)
                  notifyDataSetChanged()
          }
      
          /**
           * 刪除一條指定位置的數(shù)據(jù)
           * @param position 數(shù)據(jù)位置
           * @param isRefresh 是否刷新數(shù)據(jù)
           */
          @JvmOverloads
          @Synchronized
          fun remove(position: Int, isRefresh: Boolean = false) {
              dataSource?.removeAt(position)
              if (isRefresh)
                  notifyItemChanged(position)
          }
      
          /**
           * 刪除一些數(shù)據(jù)
           * @param items 要?jiǎng)h除的數(shù)據(jù)
           * @param isRefresh 是否刷新數(shù)據(jù)
           */
          @JvmOverloads
          @Synchronized
          fun remove(items: MutableList<E>, isRefresh: Boolean = false) {
              dataSource?.removeAll(items)
              if (isRefresh)
                  notifyDataSetChanged()
          }
      
          /**
           * 更新數(shù)據(jù)源
           * @param item 數(shù)據(jù)Item
           * @param position 要更新的位置
           * @param isRefresh 是否刷新數(shù)據(jù)
           */
          @JvmOverloads
          @Synchronized
          fun update(item: E, position: Int, isRefresh: Boolean = false) {
              dataSource?.set(position, item)
              if (isRefresh)
                  notifyItemChanged(position)
          }
      
          /**
           * 清空數(shù)據(jù)源
           * @param isRefresh 是否刷新數(shù)據(jù),默認(rèn):false
           */
          @JvmOverloads
          @Synchronized
          fun clear(isRefresh: Boolean = false) {
              dataSource?.clear()
              if (isRefresh)
                  notifyDataSetChanged()
          }
      
          /**
           * 獲取數(shù)據(jù)項(xiàng)
           * @param position 數(shù)據(jù)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)
          }
      
      }
      
      
      /// 面包屑類的實(shí)現(xiàn)
      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
                  )
              }
          }
      
      }
      
    5. FileManagerActivity的實(shí)現(xiàn)

      class FileManagerActivity : AppCompatActivity(), AdapterView.OnItemClickListener {
      
          companion object {
      
              /** 選擇請(qǐng)求 **/
              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()
          }
      
          /** 是否是頂級(jí)目錄 **/
          private fun String.isTopLevelFolder(): Boolean =
              isNullOrEmpty() || !File(this).exists() || this in sdcardPaths
      
          /** 是否是頂級(jí)目錄 **/
          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
          }
      
          /** 執(zhí)行文件搜索 **/
          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()
      
          /** 設(shè)置文件夾當(dāng)前的面包屑 **/
          private fun String.setCurrentBreadCrumbs() {
              val sdcardOrderIndex = sdcardPaths.indexOf(currentSDCardPath) + 1
              val deviceName = "存儲(chǔ)設(shè)備${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的選擇對(duì)話框 **/
          private fun showSDCardChooseDialog() = AlertDialog.Builder(this)
              .setTitle("請(qǐng)選擇存儲(chǔ)設(shè)備")
              .setItems(mutableListOf(*sdcardPaths.toTypedArray()).mapIndexed { index, item ->
                  "存儲(chǔ)設(shè)備${index + 1}${if (item == currentSDCardPath) "(當(dāng)前)" else ""}"
              }.toTypedArray()) { dialog, which ->
                  currentSDCardPath = sdcardPaths[which]
                  currentFolder = File(currentSDCardPath)
                  currentSDCardPath.listFoldersAndFiles()
                  dialog.dismiss()
              }
              .create()
              .show()
      
      }
      
  • 在WebView中調(diào)用示例

    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
        }
    
    }
    

項(xiàng)目下載地址:查看,轉(zhuǎn)載請(qǐng)聲明出處,謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容