返回

Android 长文件名视频选择对话框解决方案

Android

Android长文件名视频选择对话框解决方案

在Android应用开发中,选择视频文件是一个常见需求。当视频文件名过长,特别是文件名只有末尾少数几个字符不同时,系统自带的文件选择对话框(如Gallery)可能会因为显示空间不足而无法完整展示文件名,给用户选择带来困扰。 本文将探讨这个问题,并提供几种解决方案。

问题分析

Android系统提供了两种Intent方式来启动文件选择器:ACTION_OPEN_DOCUMENTACTION_PICK。这两种方式都可以指定文件类型,但它们在UI展示上依赖于系统或第三方应用提供的文件管理器,开发者难以直接控制其文件名显示方式。 当文件名超出显示限制时,被截断的部分可能会丢失重要信息,用户无法有效区分相似文件。

解决方案

针对长文件名视频选择问题,以下提供几种解决方案,兼顾文件名完整显示与视频预览功能。

1. 自定义文件选择对话框

最直接的方案是创建自定义对话框,完全控制文件列表的显示方式。

原理: 通过 ContentResolver 查询媒体库,获取视频文件信息,然后使用 RecyclerView 或其他布局控件构建自定义列表界面。利用 TextView 的滚动特性或调整布局以适应长文件名。同时使用 ThumbnailUtils 或第三方库加载视频缩略图实现预览。

实现:

  1. 权限申请:AndroidManifest.xml 中声明读取外部存储权限:

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    

    并在运行时动态申请权限。

  2. 查询视频: 使用 ContentResolver 查询视频文件信息,包括路径、文件名、大小等:

    fun getVideoFiles(context: Context): List<VideoFile> {
        val videoList = mutableListOf<VideoFile>()
        val projection = arrayOf(
            MediaStore.Video.Media._ID,
            MediaStore.Video.Media.DATA,
            MediaStore.Video.Media.DISPLAY_NAME,
            MediaStore.Video.Media.SIZE
        )
    
        val selection = "${MediaStore.Video.Media.DURATION} >= ?"
        val selectionArgs = arrayOf("0")
        val sortOrder = "${MediaStore.Video.Media.DISPLAY_NAME} ASC"
    
        context.contentResolver.query(
            MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
            projection,
            selection,
            selectionArgs,
            sortOrder
        )?.use { cursor ->
            val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
            val pathColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)
            val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME)
            val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE)
    
            while (cursor.moveToNext()) {
                val id = cursor.getLong(idColumn)
                val path = cursor.getString(pathColumn)
                val name = cursor.getString(nameColumn)
                val size = cursor.getLong(sizeColumn)
                videoList.add(VideoFile(id, path, name, size))
            }
        }
        return videoList
    }
    
    data class VideoFile(val id: Long, val path: String, val name: String, val size: Long)
    
  3. 创建自定义对话框: 使用 AlertDialog.Builder 创建对话框,设置自定义布局,并使用 RecyclerView 显示视频列表。

    fun showVideoPickerDialog(context: Context) {
          val videoFiles = getVideoFiles(context)
    
          val recyclerView = RecyclerView(context).apply {
              layoutManager = LinearLayoutManager(context)
              adapter = VideoAdapter(videoFiles) { videoFile ->
                  // 处理选中视频
                  Toast.makeText(context, "选择了 ${videoFile.name}", Toast.LENGTH_SHORT).show()
              }
          }
          AlertDialog.Builder(context)
              .setTitle("选择视频")
              .setView(recyclerView)
              .setNegativeButton("取消", null)
              .show()
      }
    
    class VideoAdapter(private val videoFiles: List<VideoFile>, val clickListener: (VideoFile) -> Unit) : RecyclerView.Adapter<VideoAdapter.ViewHolder>() {
      class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
          val videoName: TextView = view.findViewById(android.R.id.text1)
          val videoThumb : ImageView = view.findViewById(android.R.id.icon)
       }
    
          override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
               val view = LayoutInflater.from(parent.context).inflate(android.R.layout.two_line_list_item, parent, false)
               return ViewHolder(view)
          }
    
          override fun onBindViewHolder(holder: ViewHolder, position: Int) {
             val videoFile = videoFiles[position]
             holder.videoName.text = videoFile.name
             holder.itemView.setOnClickListener{
               clickListener(videoFile)
             }
    
             val thumb = ThumbnailUtils.createVideoThumbnail(videoFile.path, MediaStore.Images.Thumbnails.MINI_KIND);
             if(thumb != null) {
                holder.videoThumb.setImageBitmap(thumb);
             } else{
                holder.videoThumb.setImageResource(android.R.drawable.ic_media_play)
             }
    
          }
    
          override fun getItemCount() = videoFiles.size
    
}
  ```
  1. 视频预览:RecyclerView 的 Item 布局中添加 ImageView,使用 ThumbnailUtils.createVideoThumbnail() 生成视频缩略图并显示。

            val thumb = ThumbnailUtils.createVideoThumbnail(videoFile.path, MediaStore.Images.Thumbnails.MINI_KIND);
            if(thumb != null) {
               holder.videoThumb.setImageBitmap(thumb);
            } else{
               holder.videoThumb.setImageResource(android.R.drawable.ic_media_play)
            }
    
  2. 安全建议: 为了应用的安全和用户隐私,对于 Android 13 及以上版本,对于读取的图片或者视频需要获取对应的 READ_MEDIA_IMAGES 以及READ_MEDIA_VIDEO运行时权限。同时, 针对通过MediaStore选择的文件进行操作时,需要申请到该文件在外部存储的路径授权才能访问成功,这个也是在Android 10 引入的分区存储机制,可以根据实际场景,在获取用户授权的情况下,通过 ContentResolver 获取该文件的访问 Uri 后使用 takePersistableUriPermission() 方法获取文件的永久访问权限。

  3. 调用代码:

          showVideoPickerDialog(this);
    

2. 使用第三方文件选择器库

现有的一些第三方文件选择器库提供了更灵活的UI定制和文件展示功能。

原理: 集成第三方库,利用其提供的 API 构建文件选择对话框。这些库通常已经处理了长文件名显示、缩略图加载等问题。

实现: (以 Material File Picker 为例)

  1. 添加依赖:build.gradle 文件中添加依赖:

    implementation 'com.github.Dhaval2404:imagepicker:2.1'
    
  2. 启动文件选择器: 使用库提供的 API 启动文件选择器,并设置自定义选项。

    import com.github.dhaval2404.imagepicker.ImagePicker
    
    fun showVideoPickerWithLibrary(activity: Activity) {
        ImagePicker.with(activity)
          .galleryMimeTypes(mimeTypes = arrayOf("video/*"))   // Filter only video
            .cameraOnly(false)                     // Disable camera feature
          .show(123);  // Activity result code, can be any integer
     }
    
     override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
          super.onActivityResult(requestCode, resultCode, data)
    
         if(requestCode == 123 && resultCode == Activity.RESULT_OK){
           val fileUri: Uri? = data?.data
          // 处理返回结果 fileUri
    
          val filePath =  fileUri?.path;
    
          Toast.makeText(this, "选择了 ${filePath}", Toast.LENGTH_SHORT).show();
         }
     }
    

    data.data 将返回选择的文件uri,data.path 将返回选择文件的路径。 你可以通过文件 uri 和路径执行后续的文件操作。

  3. 安全建议: 仔细评估所用第三方库的安全性、稳定性及许可证,选择信誉良好且维护活跃的库。阅读库的使用文档,了解其具体功能及可能涉及的隐私权限问题。

3. 折中方案:缩短显示文件名

如果自定义UI工作量较大,而第三方库无法完全满足需求,可以考虑折中方案,即缩短显示文件名。

**原理