如何在Android Fragment中基于MVVM实现从相册或相机选取图片功能
Android 选图功能MVVM重构方案
重构前核心问题梳理
- 原使用的
startActivityForResult已被官方废弃,需要替换为现代的Activity Result API - 业务逻辑与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
相关产品推荐
相关产品推荐

