Android 图片选择库 MatisseKotlin 版

资源展示 相册选择
圆形裁剪 方形裁剪

[图片上传失败...(image-c9ea40-1576230531404)]

Matisse-Kotlin代码地址

一、需求来源

初识知乎团队Matisse,很是喜欢。但,由于与本身项目UI风格差异较大,于是便基于知乎团队Matisse稍作改动,稍作扩展,最终推出Matisse-Kotlin方便自己使用。

本项目为知乎原项目kotlin改写版本(2018/9月版本),对知乎团队Matisse进行Kotlin翻译,主要对原项目进行部分UI层面改写、已发现bug的修改、新功能添加。

二、感谢

Matisse核心功能:https://github.com/zhihu/Matisse
裁剪提供者:廖子尧 github地址:https://github.com/jeasonlzy
图片压缩提供者:https://github.com/nanchen2251

三、主要新增功能

  • 优化相册选择UI - 改变原有顶部弹出相册选择方式,底部弹出操作区域更大
  • 优化单选策略 - 原项目未明确区分单选/多选,此处单选可替换选中
  • 优化选中刷新 - 去除原notifyDataSetChanged列表刷新方式
  • 添加圆形与方形裁剪
  • 修复视频、图片混合选择
  • 添加图片选择后压缩,不失真条件下高比率压缩,支持外部实现
  • 增加主题修改,基本可保证定制成与自身项目风格一致
  • 支持设置状态栏颜色 需依赖ImmersionBar支持外部实现
  • 迁移到androidx、Kotlin
  • 抽取拍照功能,可单独使用
  • 提示方式可外部定制样式 - Matisse中提示方式可使用自身项目中自定义提示样式
  • 支持拍照完后停留在照片选择界面界面
  • 支持进入界面默认选中指定资源

四、引入、配置

第1步. 网络依赖

​ 主项目build.gradle中添加 implementation 'com.nfleo:MatisseKotlin:2.0.2'

第2步. 配置AndroidManifest.xml
<font color=red>注:注意provider name androidx的差别</font>
AndroidManifest.xml中添加以下代码:

<provider
       android:name="androidx.core.content.FileProvider"
       android:authorities="${applicationId}.fileprovider"
       android:exported="false"
       android:grantUriPermissions="true">
       <meta-data
             android:name="android.support.FILE_PROVIDER_PATHS"
             android:resource="@xml/file_paths_public"/>
</provider>
第3步. 6.0+需处理权限

The library requires two permissions:

  • android.permission.READ_EXTERNAL_STORAGE
  • android.permission.WRITE_EXTERNAL_STORAGE
  • android.permission.CAMERA
第4步. 适配7.0

项目manifest的privider标签下 paths文件中添加文件名称为file_paths_public(名字随意取,但需与AndroidManifest.xml中引用保持一致),以共享文件目录

<external-path  name="my_images" path="Pictures"/>
第5步. 配置gradle
// 具体版本需自行配置(最外层build.gradle),可修改对应版本号
ext {
    compileSdkVersion = 28

    minSdkVersion = 19
    targetSdkVersion = 28

    appcompat = '1.1.0'
    material = '1.0.0'
    recyclerview = '1.0.0'
    constraintlayout = '1.1.3'
}

五、更新记录

2019-12-10 (2.0.2)
  1. 修复mimeType为空情况
  2. 修复spanSize和gridExceptedSize同时设置冲突
    注:同时设置时,读取gridExceptedSize值
2019-11-10 (2.0.1)
  1. 修复裁剪结果尺寸异常
2019-11-4 (2.0)
  1. 相机单独提取
  2. 支持默认选中,可传入上次选中的项(通过图片cursor id或uri string对比)
    注:不支持裁剪带回的图片,裁剪带回的图片无id和uri
.setLastChoosePicturesIdOrUri(selectedPathIds as ArrayList<String>?)
  1. 修复压缩为空带回崩溃
2019-10-29 (1.2.3)
  1. 修复相册弹窗高度不准确问题
  2. 支持压缩配置,外部添加开关 api:[isInnerCompress]
  3. 完善未选中资源时各按钮点击添加提示
  4. 修复不同设备返回的媒体类型表示不一致(如:JPEG image/jpeg)
  5. 去除[api setStatusIsDark]。外部处理状态栏,见[api setStatusBarFuture]
2019-10-28 (持续更新 待发布)
  1. 支持相机拍照完成后多选
  2. 扩展提示方法,支持使用外部自定义弹窗
  3. 支持外部处理状态栏,去除项目中原[ImmersionBar]库
.setStatusBarFuture(object : MFunction<BaseActivity> {
                override fun accept(params: BaseActivity, view: View?) {
                    // view为顶部标题栏
                    // 外部设置状态栏
                    ImmersionBar.with(params)?.run {
                        statusBarDarkFont(isDarkStatus)
                        view?.apply { titleBar(this) }
                        init()
                    }

                    // 外部可隐藏Matisse界面中的View
                    view?.visibility = if (isDarkStatus) View.VISIBLE else View.GONE
                }
            })
  1. 按官方方式适配Android Q(未真机测试
2019-10-21 (1.2.2)
  1. 修复方形裁剪图片变形问题
  2. 优化单选/多选刷新问题
2019-10-18 (1.2.0)
  1. 迁移到androidx
  2. 修复并支持图片与视频混合选择
设置选择单一类型媒体,示例如下
SelectionCreator.choose(MimeTypeManager.ofAll())
                .maxSelectable(3)
或者
SelectionCreator.choose(MimeTypeManager.ofAll(), true)
                .maxSelectable(3)

设置选择混合类型媒体,示例如下
SelectionCreator.choose(MimeTypeManager.ofAll(), false)
                .maxSelectablePerMediaType(4, 2)

说明:
mediaTypeExclusive true 单一媒体类型选择
     读取maxSelectable属性作为最大值
mediaTypeExclusive false
     读取maxImageSelectable和maxVideoSelectable属性分别作为最大值
  1. 修改单/多选逻辑
    • 单选支持重新选定,不支持计数方式
    • 多选不支持重新选定,选满外部给出提示方式,支持计数与选中方式
  2. 提示方式外部实现
SelectionCreator.setNoticeConsumer(object : Consumer<String> {
                            override fun accept(params: String) {
                                // 提示方式。 可使用与自己项目相同样式的Toast或其他
                                showToast(params)
                            }
                        })
2019-10-16
  1. 完善主题扩展,并提供图片说明

六、重点Api详细说明

入口:Matisse, 配置类:SelectionCreator

.from(activity/fragment):绑定到当前activity/fragment

.choose(...):

  1. @params matisse:用于获得绑定的activity/fragment
  2. @params mimeTypes:获取所需展示的媒体类型。提供类型有三种MimeTypeManager.ofAll()、MimeTypeManager.ofImage()、MimeTypeManager.ofVideo()或自定义类型MimeTypeManager.of(MimeType.PNG...)
  3. @params mediaTypeExclusive:是否选择单一资源
    false = 可同时选择视频和图片
    ture = 仅能选择视频或者仅能选择图片

.countable(countable: Boolean)

选中控件是否按数字编号,<font color=red>说明:单选模式下不支持数字编号</font>

.maxSelectable(maxSelectable: Int)

单类型选择模式下,最多可选中的数量,<font color=red>说明:maxSelectable属性在单类型模式下才生效(即mediaTypeExclusive = true)</font>

.maxSelectablePerMediaType(maxImage: Int, maxVideo: Int)

混合类型选择模式下,视频和图片各自最大选择数量。<font color=red>说明:对应两属性在混合类型选择模式下才生效(即mediaTypeExclusive = false)</font>

.addFilter(filter: Filter)

添加选中过滤器,需外部继承实现Filter类。指定类型资源结合指定规则给出相应提示。例如:不可选择大于5M的图片,并吐司提示

.capture(enable: Boolean)

是否包含拍照功能。<font color=red>设置包含拍照功能时,需设置strategy</font>

.captureStrategy(captureStrategy: CaptureStrategy)

包含拍照功能时需设置strategy,7.0必须添加,同时需在Androidmanifest.xml中注册

.restrictOrientation(@ScreenOrientation orientation: Int)

强制设置横竖屏显示

.spanCount(spanCount: Int)

图片显示列数。当设置图片尺寸属性(gridExpectedSize)时,则忽略该属性

.gridExpectedSize(size: Int)

设置图片期望尺寸。根据图片期望尺寸计算图片展示列数

.thumbnailScale(scale: Float)

设置图片期望展示压缩比

.imageEngine(imageEngine: ImageEngine)

设置图片加载器,目前暂不支持Fresco

.isCrop(crop: Boolean)

是否开启裁剪

.isCropSaveRectangle(cropSaveRectangle: Boolean)

圆形裁剪模式下,裁剪结果是否以正方形保存

.cropFocusWidthPx(cropFocusWidth: Int)

裁剪框px宽度。圆形裁剪宽高取较小值作为直径

.cropFocusHeightPx(cropFocusHeight: Int)

裁剪框px高度。圆形裁剪宽高取较小值作为直径

.cropStyle(cropStyle: CropImageView.Style)

裁剪类型。圆形裁剪(CropImageView.Style.CIRCLE),方形裁剪(CropImageView.Style.RECTANGLE)

.cropCacheFolder(cropCacheFolder: File)

可设置裁剪后图片保存的文件

.setNoticeConsumer(noticeConsumer: Consumer<String>?)

接口形式实现提示方式,外部提示方式可任意定制

.forResult(requestCode: Int)

最后通过activityForResult方式拿到图片选择结果

.setStatusBarFuture(statusBarFunction: MFunction<BaseActivity>?)

设置状态栏相关,设置状态栏透明、修改状态栏字体颜色等

.setLastChoosePicturesIdOrUri(preSelected: ArrayList<String>?)

指定资源,打开Matisse后默认选中

七、部分功能点解读

* 资源混合选择

入口处通过Matisse.choose方法指定需展示的资源,同时指定资源选择方式,最终初始化SelectionCreator构造器,设置selectionSpec.mediaTypeExclusive

Matisse.kt
/**
 * @params mimeTypes 需展示的媒体类型 
 * @params mediaTypeExclusive 设置资源混合选择开关
 */
fun choose(mimeTypes: Set<MimeType>, mediaTypeExclusive: Boolean): SelectionCreator {
    return SelectionCreator(this, mimeTypes, mediaTypeExclusive)
}

此处主要讲支持资源混合选择的情况,即 selectionSpec.mediaTypeExclusive = false
判断逻辑主要在SelectedItemCollection.kt中,为支持资源混合选择,同时两类资源分别计数,此处引入两个集合分别装载图片与视频资源imageItems、videoItems
具体操作分为三个阶段:

阶段一:选中前判断是否达到上限。混合选择模式下需分开判断已选资源数量,代码如下:

/**
 * @params item 当前选定的资源item
 **/
fun maxSelectableReached(item: Item?): Boolean {
    // 是否为混合选择模式
    if (!spec.isMediaTypeExclusive()) {
        // 根据当前选定资源类型,分别取对应集合与对应最大值对比
        if (item?.isImage() == true) {
            return spec.maxImageSelectable == imageItems?.size
        } else if (item?.isVideo() == true) {
            return spec.maxVideoSelectable == videoItems?.size
        }
    }
    // 非混合选择模式,直接对比总集合items
    return spec.maxSelectable == items.size
}

// 是否为单一资源选择模式
fun isMediaTypeExclusive() = mediaTypeExclusive && (maxImageSelectable + maxVideoSelectable == 0)

阶段二:将选中的资源添加到总集合items中,同时根据类型添加到视频/图片imageItems/videoItems集合

fun add(item: Item?): Boolean {
    ...
    val added = items.add(item)
    addImageOrVideoItem(item)
    ...
}

// 根据当前选定的item记录到指定集合
private fun addImageOrVideoItem(item: Item) {
     if (item.isImage()) {
         if (imageItems == null)
             imageItems = linkedSetOf()
         imageItems?.add(item)
     } else if (item.isVideo()) {
         if (videoItems == null)
             videoItems = linkedSetOf()
         videoItems?.add(item)
     }
}

阶段三:取消选中时,从总集合items中移除,同时,根据选择模式判断从图片/视频集合中移除

private fun removeImageOrVideoItem(item: Item) {
    if (item.isImage()) {
         imageItems?.remove(item)
    } else if (item.isVideo()) {
         videoItems?.remove(item)
    }
}

* 单选/多选策略

首先定义两个方法:

// 是否可单选,混合选择模式与单一选择模式均判断
fun isSingleChoose() = maxSelectable == 1 || (maxImageSelectable == 1 && maxVideoSelectable == 1)

// 是否可计数
fun isCountable() = countable && !isSingleChoose()

选中操作逻辑如下:

单选模式,由于只会选中一个Item,因此去除计数支持

单选:
     a.选中:刷新当前项与上次选择项
     b.取消选中:刷新当前项

多选:
     1. 按序号计数
          a.选中:仅刷新选中的item
          b.取消选中:
                 取消最后一位:仅刷新当前操作的item
                 取消非最后一位:刷新所有选中的item
     2. 无序号计数
          a.选中:仅刷新选中的item
          b.取消选中:仅刷新选中的item

override fun onCheckViewClicked(
    checkView: CheckView, item: Item, holder: RecyclerView.ViewHolder
) {
   if (selectionSpec.isSingleChoose()) {
       notifySingleChooseData(item)
   } else {
       notifyMultiChooseData(item)
   }
}
    /**
     * 单选刷新数据
     */
    private fun notifySingleChooseData(item: Item) {
        if (selectedCollection.isSelected(item)) {
            // 选中时,再次点击则取消选中,只刷新当前item位置即可
            selectedCollection.remove(item)
            notifyItemChanged(item.positionInList)
        } else {
            // 未选中时,再次点击选中,刷新当前item,与上次选中的Item
            
            // 刷新上次选中Item,存在已选中Item则去除选中并刷新
            notifyLastItem()

            // 添加Item 并刷新当前Item位置
            if (!addItem(item)) return
            notifyItemChanged(item.positionInList)
        }
    }

多选的选中与反选稍复杂些,需支持选中计数。计数条件下,选中操作只需添加数据刷新列表当前Item即可;取消选中操作时,计数顺序已经改变,需根据当前取消Item在选中数据集中位置决定需要刷新的列表条目,当取消选中Item为最后一个Item时,此时计数顺序未改变,因此只需移除Item并刷新最后一个item即可。反之,计数顺序已经改变,则需刷新所有选中Item,重新设置序号。具体代码如下:

/**
 * 多选刷新数据
 */
private fun notifyMultiChooseData(item: Item) {
    if (selectionSpec.isCountable()) {
        // 计数模式处理选中刷新
        if (notifyMultiCountableItem(item)) return
    } else {
        if (selectedCollection.isSelected(item)) {
            selectedCollection.remove(item)
        } else {
            if (!addItem(item)) return
        }
        notifyItemChanged(item.positionInList)
    }
}

/**
 * @return 是否拦截 true=拦截  false=不拦截
 */
private fun notifyMultiCountableItem(item: Item): Boolean {
    val checkedNum = selectedCollection.checkedNumOf(item)
    if (checkedNum == CheckView.UNCHECKED) {
        if (!addItem(item)) return true
        notifyItemChanged(item.positionInList)
    } else {
        selectedCollection.remove(item)
        // 取消选中中间序号时,刷新所有选中item
        if (checkedNum != selectedCollection.count() + 1) {
            selectedCollection.asList().forEach {
                notifyItemChanged(it.positionInList)
            }
        }

        // 别忘了刷新当前Item
        notifyItemChanged(item.positionInList)
    }
    return false
}

* 过滤器

通过设置过滤器,可在点击时根据过滤定义的条件决定是否可选中资源。
过滤器实现主要继承Filter类。类中提供两个抽象方法,一个供子类调用的是否拦截方法。

  1. abstract fun constraintTypes(): Set<MimeType>:设置需过滤资源的类型,返回资源集合
  2. abstract fun filter(context: Context, item: Item?): IncapableCause?:设定过滤条件,及提示方式
  3. open fun needFiltering(context: Context, item: Item?): Boolean:判断item集合是否属于方法1中定义的集合

例如:添加过滤器,限定只能选择小于500kb的图片

首先需定义一个过滤器

/**
 * desc:不允许选择大于itemSize字节的图片</br>
 * time: 2019/10/23-10:12</br>
 * author:Leo </br>
 * since V 1.2.2 </br>
 */
class ImageSizeFilter(private var itemSize: Long) : Filter() {

    // 设置需过滤的资源类型,此处过滤类型为图片
    override fun constraintTypes() = MimeTypeManager.ofImage()

    override fun filter(context: Context, item: Item?): IncapableCause? {
        // 1. 判断当前选中的item是否属于constraintTypes中定义的图片资源
        if (!needFiltering(context, item)) return null

        if (item?.size ?: 0 > itemSize) {
            // IncapableCause具体实现在setNoticeConsumer方法中定义
            return IncapableCause("需选择小于${PhotoMetadataUtils.getSizeInMB(itemSize)}M的图片")
        }

        return null
    }
}

拦截原理:
选中item时,会调用SelectedItemCollection.kt类中的isAcceptable(item: Item?)方法,该方法有三类拦截:选中数量达到最大值、单一资源选择类型下选择混合资源、遍历外部定义过滤器拦截

    fun isAcceptable(item: Item?): IncapableCause? {
        if (maxSelectableReached(item)) {
            // 类型1,选中数量达到最大
            ...
            return IncapableCause(causeString)
        } else if (typeConflict(item)) {
            // 类型2,单一资源选择类型下选择混合资源,给出提示
            return IncapableCause(context.getString(R.string.error_type_conflict))
        }

        // 类型3,遍历外部定义的过滤器
        return PhotoMetadataUtils.isAcceptable(context, item)
    }

    /**
     * 遍历外部自定义过滤器
     * PhotoMetadataUtils.kt
     */
    fun isAcceptable(context: Context, item: Item?): IncapableCause? {
        if (!isSelectableType(context, item))
            return IncapableCause(context.getString(R.string.error_file_type))

        // 遍历外部自定义的过滤器
        if (SelectionSpec.getInstance().filters != null) {
            SelectionSpec.getInstance().filters?.forEach {
                return it.filter(context, item)
            }
        }
        return null
    }

* 主题

为保证Matisse UI风格完全符合主项目,由此扩展主题,可在style.xml中修改Matisse库中更多控件属性。库内提供了一个完整的Style作为父类,具体如下:

<style name="Matisse.Default" parent="Theme.AppCompat.Light.NoActionBar">
        <!--状态栏相关-->
        <item name="colorPrimary">@color/primary</item>
        <item name="colorPrimaryDark">@color/primary_dark</item>

        <!--顶部导航条高度-->
        <item name="navigation.height">@dimen/navigation_height</item>
        <!--顶部导航条背景色-->
        <item name="navigation.background">@color/navigation_bg</item>
        <!--顶部导航条返回按钮资源图-->
        <item name="navigation.backRes">@drawable/icon_arrow_right_white</item>
        <!--图片预览界面背景色-->
        <item name="preview.background">@color/preview_bg</item>

        <!--底部工具栏背景色-->
        <item name="bottomToolbar.bg">@color/bottomTool_bg</item>
        <!--底部工具栏高度-->
        <item name="bottomToolbar.height">@dimen/bottom_tool_height</item>

        <!--返回按钮文字 当无文字是可设置为空字符串 如下-->
        <item name="Media.Back.text">@string/button_null</item>
        <!--返回按钮颜色-->
        <item name="Media.Back.textColor">@color/color_base</item>
        <!--返回按钮文字大小-->
        <item name="Media.Back.textSize">@dimen/text_back</item>

        <item name="Media.Sure.text">@string/button_sure</item>
        <!--确定按钮颜色-->
        <item name="Media.Sure.textColor">@color/color_base</item>
        <!--确认按钮文字大小-->
        <item name="Media.Sure.textSize">@dimen/text_sure</item>

        <item name="Media.Preview.text">@string/button_preview</item>
        <!--预览按钮颜色-->
        <item name="Media.Preview.textColor">@color/text_preview</item>
        <!--预览按钮文字大小-->
        <item name="Media.Preview.textSize">@dimen/text_preview</item>

        <item name="Media.Original.text">@string/button_original</item>
        <!--原图选择控件文字颜色-->
        <item name="Media.Original.textColor">@color/text_original</item>
        <!--原图按钮文字大小-->
        <item name="Media.Original.textSize">@dimen/text_original</item>

        <item name="Media.Album.text">@string/album_name_all</item>
        <!--查看全部相册文件夹按钮颜色-->
        <item name="Media.Album.textColor">@color/text_album</item>
        <!--查看全部相册按钮文字大小-->
        <item name="Media.Album.textSize">@dimen/text_album</item>
        <!--相册文件夹内部列表item颜色-->
        <item name="Media.Album.Item.textColor">@color/text_item_album</item>
        <!--查看全部相册内item文字大小-->
        <item name="Media.Album.Item.textSize">@dimen/text_item_album</item>

        <!--列表中可拍照状态下相机item文字颜色-->
        <item name="Media.Camera.textColor">@color/text_camera</item>
        <!--列表中可拍照状态下相机item文字大小-->
        <item name="Media.Camera.textSize">@dimen/text_camera</item>

        <item name="Preview.Back.text">@string/button_back</item>
        <item name="Preview.Confirm.text">@string/button_sure_default</item>

        <!--选中控件背景色-->
        <item name="item.checkCircle.backgroundColor">@color/item_checkCircle_backgroundColor</item>
        <!--选中控件圆环边框颜色-->
        <item name="item.checkCircle.borderColor">@color/item_checkCircle_borderColor</item>
        <!--原图radio控件颜色-->
        <item name="item.checkRadio">@color/item_checkRadio</item>

        <!--空状态文字颜色-->
        <item name="Media.Empty.textColor">@color/text_empty</item>
        <!--空状态文字大小-->
        <item name="Media.Empty.textSize">@dimen/text_empty</item>
        <item name="Media.Empty.text">@string/empty_text</item>
        <!--空状态资源图-->
        <item name="Media.Empty.resource">@drawable/ic_empty_zhihu</item>

        <!--图片加载占位图-->
        <item name="item.placeholder">@color/zhihu_item_placeholder</item>
    </style>

需自定义其他属性,可另写style,修改所需属性即可:

    // parent为Matisse.Default
    <style name="CustomMatisseStyle" parent="Matisse.Default">
        <item name="Media.Back.text">@string/back</item>
        <item name="Media.Sure.text">@string/sure</item>
        <item name="Media.Preview.text">@string/preview</item>
        <item name="Media.Original.text">@string/original</item>
        <item name="Media.Album.text">@string/album</item>
        <item name="Media.Camera.text">@string/camera</item>
    </style>
属性对照图1 属性对照图2

* 裁剪

裁剪功能提供了裁剪开关(isCrop)、裁剪类型(cropStyle)、裁剪框尺寸(cropFocusWidthPx、cropFocusHeightPx)、圆形裁剪支持保存正方形图(isCropSaveRectangle)、裁剪图保存地址(cropCacheFolder)等几个属性。

注:裁剪有效的前提必须是开启裁剪同时最大选择数量为1。

// 是否可单选
fun isSingleChoose() =
    maxSelectable == 1 || (maxImageSelectable == 1 && maxVideoSelectable == 1)
// 是否可裁剪
fun openCrop() = isCrop && isSingleChoose()

裁剪后不返回uri地址,只返回file path路径

八、具体调用

Matisse api众多,须根据需要自行调用,大致例子如下

kotlin项目调用
Matisse.from(MainActivity.this)
        .choose(MimeTypeManager.ofAll(), true)
        .countable(true)
        .maxSelectable(9)
        .addFilter(new GifSizeFilter(320, 320, 5 * Filter.K * Filter.K))
        .gridExpectedSize(getResources().getDimensionPixelSize(R.dimen.grid_expected_size))
        .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
        .thumbnailScale(0.85f)
        .imageEngine(new GlideEngine())
        .forResult(REQUEST_CODE_CHOOSE);

java项目调用
Matisse.Companion.from(MainActivity.this)
        .choose(MimeTypeManager.Companion.ofAll(), false)
        .countable(true)
        .maxSelectablePerMediaType(3, 3)
        .addFilter(new GifSizeFilter(320, 320, 5 * Filter.K * Filter.K))
        .gridExpectedSize(getResources().getDimensionPixelSize(R.dimen.grid_expected_size))
        .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
        .thumbnailScale(0.85f)
        .imageEngine(new GlideEngine())
        .forResult(REQUEST_CODE_CHOOSE);

Matisse.from(SampleActivity.this)
        .choose(MimeTypeManager.ofAll(), true)      // 展示所有类型文件(图片 视频 gif)
        .capture(true)                              // 可拍照
        .countable(true)                            // 记录文件选择顺序
        .captureStrategy(CaptureStrategy(true, "${Platform.getPackageName(this@ExampleActivity)}.fileprovider"))
        .maxSelectable(1)                           // 最多选择一张
        .isCrop(true)                               // 开启裁剪
        .cropFocusWidthPx(400)                      // 设置裁剪后保存图片的宽高
        .cropFocusHeightPx(400)                     // 设置裁剪后保存图片的宽高
        .cropStyle(CropImageView.Style.RECTANGLE)   // 方形裁剪CIRCLE为圆形裁剪
        .isCropSaveRectangle(true)                  // 裁剪后保存方形(只对圆形裁剪有效)
        .addFilter(new GifSizeFilter(320, 320, 5 * Filter.K * Filter.K))  // 筛选数据源可选大小限制
        .gridExpectedSize(getResources().getDimensionPixelSize(R.dimen.grid_expected_size))
        .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
        .thumbnailScale(0.8f)
        .setStatusIsDark(true)                      // 设置状态栏文字颜色 需依赖ImmersionBar库
        .imageEngine(new GlideEngine())             // 加载库需外部实现
        .forResult(REQUEST_CODE_CHOOSE);

返回结果获得:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) 
{
    super.onActivityResult(requestCode, resultCode, data)
    if (data == null) return

    if (requestCode == ConstValue.REQUEST_CODE_CHOOSE && resultCode == Activity.RESULT_OK) {
        var string = ""
            
        // 获取uri返回值  裁剪结果不返回uri
        val uriList = Matisse.obtainResult(data)
        // 获取文件路径返回值
        val strList = Matisse.obtainPathResult(data)
            
        // 原文件
        val file = FileUtil.getFileByPath(Matisse.obtainPathResult(data)[0])

        Glide.with(this).load(file).into(iv_image)
        // 压缩后的文件         (多个文件压缩可以循环压缩)
        val file1 =                  CompressHelper.getDefault(applicationContext)?.compressToFile(file)
        string += PhotoMetadataUtils.getSizeInMB(file.length()).toString() + " PK " + PhotoMetadataUtils.getSizeInMB(file1?.length() ?: 0)
        string = "\n\n$string"

        text.text = "\n\n$string"
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,368评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,680评论 2 59
  • afinalAfinal是一个android的ioc,orm框架 https://github.com/yangf...
    wgl0419阅读 6,263评论 1 9
  • afinalAfinal是一个android的ioc,orm框架 https://github.com/yangf...
    passiontim阅读 15,392评论 2 45
  • 凌晨,五点,一路奔跑向西,四公里处已是龙湖公园,油漆铺就而成的路,路边的垂柳,让我想起了赵雷那首火遍天下的《城都》...
    一个俗人001阅读 134评论 0 0