You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Android Fragment中基于MVVM实现从相册或相机选取图片功能

Android 选图功能MVVM重构方案

重构前核心问题梳理

  1. 原使用的startActivityForResult已被官方废弃,需要替换为现代的Activity Result API
  2. 业务逻辑与UI逻辑耦合,图片读取、状态处理等代码全部写在Fragment中,不符合MVVM分层规范

重构整体分层原则

  • UI层(Fragment):仅处理用户交互、系统弹窗、权限申请、调用系统相机/相册、视图更新,不承载业务逻辑
  • ViewModel层:持有图片状态、处理图片校验/转换等业务逻辑、调度耗时任务
  • Repository层(可选):封装图片压缩、存储、上传等通用能力,供ViewModel调用

第一步:替换废弃API,实现系统调用能力

在Fragment中定义Activity Result启动器,替代原startActivityForResult和onActivityResult逻辑:

// 相册选图启动器
private val galleryLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
    uri ?: return@registerForActivityResult
    viewModel.processGalleryResult(uri, requireContext().contentResolver)
}

// 相机拍照启动器
private val cameraLauncher = registerForActivityResult(ActivityResultContracts.TakePicturePreview()) { bitmap: Bitmap? ->
    bitmap ?: return@registerForActivityResult
    viewModel.processCameraResult(bitmap)
}

原打开相机/相册的方法简化为调用启动器:

private fun choosePhotoFromGallery() {
    galleryLauncher.launch("image/*")
}

private fun takePhotoFromCamera() {
    // 可自行补充相机权限校验逻辑
    cameraLauncher.launch(null)
}

弹窗逻辑保留在Fragment即可,属于纯UI交互逻辑:

private fun showPictureDialog() {
    val pictureDialog = AlertDialog.Builder(requireContext())
    pictureDialog.setTitle("Select Action")
    val pictureDialogItems = arrayOf("Select photo from gallery", "Capture photo from camera")
    pictureDialog.setItems(pictureDialogItems) { _, which ->
        when (which) {
            0 -> choosePhotoFromGallery()
            1 -> takePhotoFromCamera()
        }
    }
    pictureDialog.show()
}

第二步:实现ViewModel层,承载业务逻辑

ViewModel中通过LiveData/StateFlow持有状态,供UI层观察,所有耗时操作放在子线程执行:

class ProfileViewModel : ViewModel() {
    // 头像Bitmap状态
    private val _avatarBitmap = MutableLiveData<Bitmap>()
    val avatarBitmap: LiveData<Bitmap> = _avatarBitmap

    // 全局提示状态
    private val _toastMessage = MutableLiveData<String>()
    val toastMessage: LiveData<String> = _toastMessage

    // 处理相册返回结果
    fun processGalleryResult(uri: Uri, contentResolver: ContentResolver) {
        viewModelScope.launch(Dispatchers.IO) {
            try {
                val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)
                // 此处可扩展添加图片压缩、格式校验、本地缓存等业务逻辑
                _avatarBitmap.postValue(bitmap)
                _toastMessage.postValue("Image Saved!")
            } catch (e: IOException) {
                e.printStackTrace()
                _toastMessage.postValue("Failed!")
            }
        }
    }

    // 处理相机返回结果
    fun processCameraResult(bitmap: Bitmap) {
        viewModelScope.launch(Dispatchers.IO) {
            // 此处可扩展添加图片上传到服务器等业务逻辑
            _avatarBitmap.postValue(bitmap)
            _toastMessage.postValue("Image Saved!")
        }
    }
}

第三步:Fragment观察状态更新视图

在Fragment中仅需要观察ViewModel的状态,响应式更新UI即可:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    // 观察头像状态更新
    viewModel.avatarBitmap.observe(viewLifecycleOwner) { bitmap ->
        binding.profileImage.setImageBitmap(bitmap)
    }

    // 观察提示消息
    viewModel.toastMessage.observe(viewLifecycleOwner) { msg ->
        Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show()
    }

    // 按钮点击事件
    binding.ProfileChangeImage.setOnClickListener {
        showPictureDialog()
    }
}

可选优化

如果需要实现图片上传、持久化存储等能力,将这部分逻辑封装到Repository层,ViewModel仅调用Repository的对应方法即可,进一步降低耦合。

内容的提问来源于stack exchange,提问作者Osama Sayed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 14:06:03