Android 圖片選擇庫(kù) MatisseKotlin 版

資源展示 相冊(cè)選擇
圓形裁剪 方形裁剪

[圖片上傳失敗...(image-c9ea40-1576230531404)]

Matisse-Kotlin代碼地址

一、需求來(lái)源

初識(shí)知乎團(tuán)隊(duì)Matisse伺糠,很是喜歡。但剥啤,由于與本身項(xiàng)目UI風(fēng)格差異較大,于是便基于知乎團(tuán)隊(duì)Matisse稍作改動(dòng)奄喂,稍作擴(kuò)展铐殃,最終推出Matisse-Kotlin方便自己使用。

本項(xiàng)目為知乎原項(xiàng)目kotlin改寫(xiě)版本(2018/9月版本)跨新,對(duì)知乎團(tuán)隊(duì)Matisse進(jìn)行Kotlin翻譯富腊,主要對(duì)原項(xiàng)目進(jìn)行部分UI層面改寫(xiě)、已發(fā)現(xiàn)bug的修改域帐、新功能添加赘被。

二、感謝

Matisse核心功能:https://github.com/zhihu/Matisse
裁剪提供者:廖子堯 github地址:https://github.com/jeasonlzy
圖片壓縮提供者:https://github.com/nanchen2251

三肖揣、主要新增功能

  • 優(yōu)化相冊(cè)選擇UI - 改變?cè)许敳繌棾鱿鄡?cè)選擇方式民假,底部彈出操作區(qū)域更大
  • 優(yōu)化單選策略 - 原項(xiàng)目未明確區(qū)分單選/多選,此處單選可替換選中
  • 優(yōu)化選中刷新 - 去除原notifyDataSetChanged列表刷新方式
  • 添加圓形與方形裁剪
  • 修復(fù)視頻龙优、圖片混合選擇
  • 添加圖片選擇后壓縮羊异,不失真條件下高比率壓縮,支持外部實(shí)現(xiàn)
  • 增加主題修改彤断,基本可保證定制成與自身項(xiàng)目風(fēng)格一致
  • 支持設(shè)置狀態(tài)欄顏色 需依賴ImmersionBar野舶,支持外部實(shí)現(xiàn)
  • 遷移到androidx、Kotlin
  • 抽取拍照功能宰衙,可單獨(dú)使用
  • 提示方式可外部定制樣式 - Matisse中提示方式可使用自身項(xiàng)目中自定義提示樣式
  • 支持拍照完后停留在照片選擇界面界面
  • 支持進(jìn)入界面默認(rèn)選中指定資源

四平道、引入、配置

第1步. 網(wǎng)絡(luò)依賴

? 主項(xiàng)目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+需處理權(quán)限

The library requires two permissions:

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

項(xiàng)目manifest的privider標(biāo)簽下 paths文件中添加文件名稱為file_paths_public(名字隨意取供炼,但需與AndroidManifest.xml中引用保持一致)一屋,以共享文件目錄

<external-path  name="my_images" path="Pictures"/>
第5步. 配置gradle
// 具體版本需自行配置(最外層build.gradle)窘疮,可修改對(duì)應(yīng)版本號(hào)
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. 修復(fù)mimeType為空情況
  2. 修復(fù)spanSize和gridExceptedSize同時(shí)設(shè)置沖突
    注:同時(shí)設(shè)置時(shí)冀墨,讀取gridExceptedSize值
2019-11-10 (2.0.1)
  1. 修復(fù)裁剪結(jié)果尺寸異常
2019-11-4 (2.0)
  1. 相機(jī)單獨(dú)提取
  2. 支持默認(rèn)選中闸衫,可傳入上次選中的項(xiàng)(通過(guò)圖片cursor id或uri string對(duì)比)
    注:不支持裁剪帶回的圖片,裁剪帶回的圖片無(wú)id和uri
.setLastChoosePicturesIdOrUri(selectedPathIds as ArrayList<String>?)
  1. 修復(fù)壓縮為空帶回崩潰
2019-10-29 (1.2.3)
  1. 修復(fù)相冊(cè)彈窗高度不準(zhǔn)確問(wèn)題
  2. 支持壓縮配置轧苫,外部添加開(kāi)關(guān) api:[isInnerCompress]
  3. 完善未選中資源時(shí)各按鈕點(diǎn)擊添加提示
  4. 修復(fù)不同設(shè)備返回的媒體類型表示不一致(如:JPEG image/jpeg)
  5. 去除[api setStatusIsDark]楚堤。外部處理狀態(tài)欄,見(jiàn)[api setStatusBarFuture]
2019-10-28 (持續(xù)更新 待發(fā)布)
  1. 支持相機(jī)拍照完成后多選
  2. 擴(kuò)展提示方法含懊,支持使用外部自定義彈窗
  3. 支持外部處理狀態(tài)欄,去除項(xiàng)目中原[ImmersionBar]庫(kù)
.setStatusBarFuture(object : MFunction<BaseActivity> {
                override fun accept(params: BaseActivity, view: View?) {
                    // view為頂部標(biāo)題欄
                    // 外部設(shè)置狀態(tài)欄
                    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(未真機(jī)測(cè)試
2019-10-21 (1.2.2)
  1. 修復(fù)方形裁剪圖片變形問(wèn)題
  2. 優(yōu)化單選/多選刷新問(wèn)題
2019-10-18 (1.2.0)
  1. 遷移到androidx
  2. 修復(fù)并支持圖片與視頻混合選擇
設(shè)置選擇單一類型媒體衅胀,示例如下
SelectionCreator.choose(MimeTypeManager.ofAll())
                .maxSelectable(3)
或者
SelectionCreator.choose(MimeTypeManager.ofAll(), true)
                .maxSelectable(3)

設(shè)置選擇混合類型媒體岔乔,示例如下
SelectionCreator.choose(MimeTypeManager.ofAll(), false)
                .maxSelectablePerMediaType(4, 2)

說(shuō)明:
mediaTypeExclusive true 單一媒體類型選擇
     讀取maxSelectable屬性作為最大值
mediaTypeExclusive false
     讀取maxImageSelectable和maxVideoSelectable屬性分別作為最大值
  1. 修改單/多選邏輯
    • 單選支持重新選定,不支持計(jì)數(shù)方式
    • 多選不支持重新選定滚躯,選滿外部給出提示方式雏门,支持計(jì)數(shù)與選中方式
  2. 提示方式外部實(shí)現(xiàn)
SelectionCreator.setNoticeConsumer(object : Consumer<String> {
                            override fun accept(params: String) {
                                // 提示方式。 可使用與自己項(xiàng)目相同樣式的Toast或其他
                                showToast(params)
                            }
                        })
2019-10-16
  1. 完善主題擴(kuò)展掸掏,并提供圖片說(shuō)明

六茁影、重點(diǎn)Api詳細(xì)說(shuō)明

入口:Matisse, 配置類:SelectionCreator

.from(activity/fragment):綁定到當(dāng)前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 = 可同時(shí)選擇視頻和圖片
    ture = 僅能選擇視頻或者僅能選擇圖片

.countable(countable: Boolean)

選中控件是否按數(shù)字編號(hào)愿待,<font color=red>說(shuō)明:?jiǎn)芜x模式下不支持?jǐn)?shù)字編號(hào)</font>

.maxSelectable(maxSelectable: Int)

單類型選擇模式下浩螺,最多可選中的數(shù)量,<font color=red>說(shuō)明:maxSelectable屬性在單類型模式下才生效(即mediaTypeExclusive = true)</font>

.maxSelectablePerMediaType(maxImage: Int, maxVideo: Int)

混合類型選擇模式下仍侥,視頻和圖片各自最大選擇數(shù)量要出。<font color=red>說(shuō)明:對(duì)應(yīng)兩屬性在混合類型選擇模式下才生效(即mediaTypeExclusive = false)</font>

.addFilter(filter: Filter)

添加選中過(guò)濾器,需外部繼承實(shí)現(xiàn)Filter類农渊。指定類型資源結(jié)合指定規(guī)則給出相應(yīng)提示患蹂。例如:不可選擇大于5M的圖片,并吐司提示

.capture(enable: Boolean)

是否包含拍照功能砸紊。<font color=red>設(shè)置包含拍照功能時(shí)传于,需設(shè)置strategy</font>

.captureStrategy(captureStrategy: CaptureStrategy)

包含拍照功能時(shí)需設(shè)置strategy,7.0必須添加批糟,同時(shí)需在Androidmanifest.xml中注冊(cè)

.restrictOrientation(@ScreenOrientation orientation: Int)

強(qiáng)制設(shè)置橫豎屏顯示

.spanCount(spanCount: Int)

圖片顯示列數(shù)格了。當(dāng)設(shè)置圖片尺寸屬性(gridExpectedSize)時(shí),則忽略該屬性

.gridExpectedSize(size: Int)

設(shè)置圖片期望尺寸徽鼎。根據(jù)圖片期望尺寸計(jì)算圖片展示列數(shù)

.thumbnailScale(scale: Float)

設(shè)置圖片期望展示壓縮比

.imageEngine(imageEngine: ImageEngine)

設(shè)置圖片加載器盛末,目前暫不支持Fresco

.isCrop(crop: Boolean)

是否開(kāi)啟裁剪

.isCropSaveRectangle(cropSaveRectangle: Boolean)

圓形裁剪模式下弹惦,裁剪結(jié)果是否以正方形保存

.cropFocusWidthPx(cropFocusWidth: Int)

裁剪框px寬度。圓形裁剪寬高取較小值作為直徑

.cropFocusHeightPx(cropFocusHeight: Int)

裁剪框px高度悄但。圓形裁剪寬高取較小值作為直徑

.cropStyle(cropStyle: CropImageView.Style)

裁剪類型棠隐。圓形裁剪(CropImageView.Style.CIRCLE),方形裁剪(CropImageView.Style.RECTANGLE)

.cropCacheFolder(cropCacheFolder: File)

可設(shè)置裁剪后圖片保存的文件

.setNoticeConsumer(noticeConsumer: Consumer<String>?)

接口形式實(shí)現(xiàn)提示方式檐嚣,外部提示方式可任意定制

.forResult(requestCode: Int)

最后通過(guò)activityForResult方式拿到圖片選擇結(jié)果

.setStatusBarFuture(statusBarFunction: MFunction<BaseActivity>?)

設(shè)置狀態(tài)欄相關(guān)助泽,設(shè)置狀態(tài)欄透明、修改狀態(tài)欄字體顏色等

.setLastChoosePicturesIdOrUri(preSelected: ArrayList<String>?)

指定資源嚎京,打開(kāi)Matisse后默認(rèn)選中

七嗡贺、部分功能點(diǎn)解讀

* 資源混合選擇

入口處通過(guò)Matisse.choose方法指定需展示的資源,同時(shí)指定資源選擇方式鞍帝,最終初始化SelectionCreator構(gòu)造器诫睬,設(shè)置selectionSpec.mediaTypeExclusive

Matisse.kt
/**
 * @params mimeTypes 需展示的媒體類型 
 * @params mediaTypeExclusive 設(shè)置資源混合選擇開(kāi)關(guān)
 */
fun choose(mimeTypes: Set<MimeType>, mediaTypeExclusive: Boolean): SelectionCreator {
    return SelectionCreator(this, mimeTypes, mediaTypeExclusive)
}

此處主要講支持資源混合選擇的情況,即 selectionSpec.mediaTypeExclusive = false
判斷邏輯主要在SelectedItemCollection.kt中帕涌,為支持資源混合選擇摄凡,同時(shí)兩類資源分別計(jì)數(shù),此處引入兩個(gè)集合分別裝載圖片與視頻資源imageItems蚓曼、videoItems
具體操作分為三個(gè)階段:

階段一:選中前判斷是否達(dá)到上限亲澡。混合選擇模式下需分開(kāi)判斷已選資源數(shù)量纫版,代碼如下:

/**
 * @params item 當(dāng)前選定的資源item
 **/
fun maxSelectableReached(item: Item?): Boolean {
    // 是否為混合選擇模式
    if (!spec.isMediaTypeExclusive()) {
        // 根據(jù)當(dāng)前選定資源類型床绪,分別取對(duì)應(yīng)集合與對(duì)應(yīng)最大值對(duì)比
        if (item?.isImage() == true) {
            return spec.maxImageSelectable == imageItems?.size
        } else if (item?.isVideo() == true) {
            return spec.maxVideoSelectable == videoItems?.size
        }
    }
    // 非混合選擇模式,直接對(duì)比總集合items
    return spec.maxSelectable == items.size
}

// 是否為單一資源選擇模式
fun isMediaTypeExclusive() = mediaTypeExclusive && (maxImageSelectable + maxVideoSelectable == 0)

階段二:將選中的資源添加到總集合items中捎琐,同時(shí)根據(jù)類型添加到視頻/圖片imageItems/videoItems集合

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

// 根據(jù)當(dāng)前選定的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)
     }
}

階段三:取消選中時(shí)会涎,從總集合items中移除,同時(shí)瑞凑,根據(jù)選擇模式判斷從圖片/視頻集合中移除

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

* 單選/多選策略

首先定義兩個(gè)方法:

// 是否可單選末秃,混合選擇模式與單一選擇模式均判斷
fun isSingleChoose() = maxSelectable == 1 || (maxImageSelectable == 1 && maxVideoSelectable == 1)

// 是否可計(jì)數(shù)
fun isCountable() = countable && !isSingleChoose()

選中操作邏輯如下:

單選模式,由于只會(huì)選中一個(gè)Item籽御,因此去除計(jì)數(shù)支持

單選:
     a.選中:刷新當(dāng)前項(xiàng)與上次選擇項(xiàng)
     b.取消選中:刷新當(dāng)前項(xiàng)

多選:
     1. 按序號(hào)計(jì)數(shù)
          a.選中:僅刷新選中的item
          b.取消選中:
                 取消最后一位:僅刷新當(dāng)前操作的item
                 取消非最后一位:刷新所有選中的item
     2. 無(wú)序號(hào)計(jì)數(shù)
          a.選中:僅刷新選中的item
          b.取消選中:僅刷新選中的item

override fun onCheckViewClicked(
    checkView: CheckView, item: Item, holder: RecyclerView.ViewHolder
) {
   if (selectionSpec.isSingleChoose()) {
       notifySingleChooseData(item)
   } else {
       notifyMultiChooseData(item)
   }
}
    /**
     * 單選刷新數(shù)據(jù)
     */
    private fun notifySingleChooseData(item: Item) {
        if (selectedCollection.isSelected(item)) {
            // 選中時(shí)练慕,再次點(diǎn)擊則取消選中,只刷新當(dāng)前item位置即可
            selectedCollection.remove(item)
            notifyItemChanged(item.positionInList)
        } else {
            // 未選中時(shí)技掏,再次點(diǎn)擊選中铃将,刷新當(dāng)前item,與上次選中的Item
            
            // 刷新上次選中Item哑梳,存在已選中Item則去除選中并刷新
            notifyLastItem()

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

多選的選中與反選稍復(fù)雜些劲阎,需支持選中計(jì)數(shù)。計(jì)數(shù)條件下鸠真,選中操作只需添加數(shù)據(jù)刷新列表當(dāng)前Item即可悯仙;取消選中操作時(shí)龄毡,計(jì)數(shù)順序已經(jīng)改變,需根據(jù)當(dāng)前取消Item在選中數(shù)據(jù)集中位置決定需要刷新的列表?xiàng)l目锡垄,當(dāng)取消選中Item為最后一個(gè)Item時(shí)沦零,此時(shí)計(jì)數(shù)順序未改變,因此只需移除Item并刷新最后一個(gè)item即可货岭。反之路操,計(jì)數(shù)順序已經(jīng)改變,則需刷新所有選中Item千贯,重新設(shè)置序號(hào)屯仗。具體代碼如下:

/**
 * 多選刷新數(shù)據(jù)
 */
private fun notifyMultiChooseData(item: Item) {
    if (selectionSpec.isCountable()) {
        // 計(jì)數(shù)模式處理選中刷新
        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)
        // 取消選中中間序號(hào)時(shí),刷新所有選中item
        if (checkedNum != selectedCollection.count() + 1) {
            selectedCollection.asList().forEach {
                notifyItemChanged(it.positionInList)
            }
        }

        // 別忘了刷新當(dāng)前Item
        notifyItemChanged(item.positionInList)
    }
    return false
}

* 過(guò)濾器

通過(guò)設(shè)置過(guò)濾器搔谴,可在點(diǎn)擊時(shí)根據(jù)過(guò)濾定義的條件決定是否可選中資源祭钉。
過(guò)濾器實(shí)現(xiàn)主要繼承Filter類。類中提供兩個(gè)抽象方法己沛,一個(gè)供子類調(diào)用的是否攔截方法。

  1. abstract fun constraintTypes(): Set<MimeType>:設(shè)置需過(guò)濾資源的類型距境,返回資源集合
  2. abstract fun filter(context: Context, item: Item?): IncapableCause?:設(shè)定過(guò)濾條件申尼,及提示方式
  3. open fun needFiltering(context: Context, item: Item?): Boolean:判斷item集合是否屬于方法1中定義的集合

例如:添加過(guò)濾器,限定只能選擇小于500kb的圖片

首先需定義一個(gè)過(guò)濾器

/**
 * desc:不允許選擇大于itemSize字節(jié)的圖片</br>
 * time: 2019/10/23-10:12</br>
 * author:Leo </br>
 * since V 1.2.2 </br>
 */
class ImageSizeFilter(private var itemSize: Long) : Filter() {

    // 設(shè)置需過(guò)濾的資源類型,此處過(guò)濾類型為圖片
    override fun constraintTypes() = MimeTypeManager.ofImage()

    override fun filter(context: Context, item: Item?): IncapableCause? {
        // 1. 判斷當(dāng)前選中的item是否屬于constraintTypes中定義的圖片資源
        if (!needFiltering(context, item)) return null

        if (item?.size ?: 0 > itemSize) {
            // IncapableCause具體實(shí)現(xiàn)在setNoticeConsumer方法中定義
            return IncapableCause("需選擇小于${PhotoMetadataUtils.getSizeInMB(itemSize)}M的圖片")
        }

        return null
    }
}

攔截原理:
選中item時(shí)垫桂,會(huì)調(diào)用SelectedItemCollection.kt類中的isAcceptable(item: Item?)方法师幕,該方法有三類攔截:選中數(shù)量達(dá)到最大值、單一資源選擇類型下選擇混合資源诬滩、遍歷外部定義過(guò)濾器攔截

    fun isAcceptable(item: Item?): IncapableCause? {
        if (maxSelectableReached(item)) {
            // 類型1霹粥,選中數(shù)量達(dá)到最大
            ...
            return IncapableCause(causeString)
        } else if (typeConflict(item)) {
            // 類型2,單一資源選擇類型下選擇混合資源疼鸟,給出提示
            return IncapableCause(context.getString(R.string.error_type_conflict))
        }

        // 類型3后控,遍歷外部定義的過(guò)濾器
        return PhotoMetadataUtils.isAcceptable(context, item)
    }

    /**
     * 遍歷外部自定義過(guò)濾器
     * PhotoMetadataUtils.kt
     */
    fun isAcceptable(context: Context, item: Item?): IncapableCause? {
        if (!isSelectableType(context, item))
            return IncapableCause(context.getString(R.string.error_file_type))

        // 遍歷外部自定義的過(guò)濾器
        if (SelectionSpec.getInstance().filters != null) {
            SelectionSpec.getInstance().filters?.forEach {
                return it.filter(context, item)
            }
        }
        return null
    }

* 主題

為保證Matisse UI風(fēng)格完全符合主項(xiàng)目,由此擴(kuò)展主題空镜,可在style.xml中修改Matisse庫(kù)中更多控件屬性浩淘。庫(kù)內(nèi)提供了一個(gè)完整的Style作為父類,具體如下:

<style name="Matisse.Default" parent="Theme.AppCompat.Light.NoActionBar">
        <!--狀態(tài)欄相關(guān)-->
        <item name="colorPrimary">@color/primary</item>
        <item name="colorPrimaryDark">@color/primary_dark</item>

        <!--頂部導(dǎo)航條高度-->
        <item name="navigation.height">@dimen/navigation_height</item>
        <!--頂部導(dǎo)航條背景色-->
        <item name="navigation.background">@color/navigation_bg</item>
        <!--頂部導(dǎo)航條返回按鈕資源圖-->
        <item name="navigation.backRes">@drawable/icon_arrow_right_white</item>
        <!--圖片預(yù)覽界面背景色-->
        <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>

        <!--返回按鈕文字 當(dāng)無(wú)文字是可設(shè)置為空字符串 如下-->
        <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>
        <!--確認(rèn)按鈕文字大小-->
        <item name="Media.Sure.textSize">@dimen/text_sure</item>

        <item name="Media.Preview.text">@string/button_preview</item>
        <!--預(yù)覽按鈕顏色-->
        <item name="Media.Preview.textColor">@color/text_preview</item>
        <!--預(yù)覽按鈕文字大小-->
        <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>
        <!--查看全部相冊(cè)文件夾按鈕顏色-->
        <item name="Media.Album.textColor">@color/text_album</item>
        <!--查看全部相冊(cè)按鈕文字大小-->
        <item name="Media.Album.textSize">@dimen/text_album</item>
        <!--相冊(cè)文件夾內(nèi)部列表item顏色-->
        <item name="Media.Album.Item.textColor">@color/text_item_album</item>
        <!--查看全部相冊(cè)內(nèi)item文字大小-->
        <item name="Media.Album.Item.textSize">@dimen/text_item_album</item>

        <!--列表中可拍照狀態(tài)下相機(jī)item文字顏色-->
        <item name="Media.Camera.textColor">@color/text_camera</item>
        <!--列表中可拍照狀態(tài)下相機(jī)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>
        <!--選中控件圓環(huán)邊框顏色-->
        <item name="item.checkCircle.borderColor">@color/item_checkCircle_borderColor</item>
        <!--原圖radio控件顏色-->
        <item name="item.checkRadio">@color/item_checkRadio</item>

        <!--空狀態(tài)文字顏色-->
        <item name="Media.Empty.textColor">@color/text_empty</item>
        <!--空狀態(tài)文字大小-->
        <item name="Media.Empty.textSize">@dimen/text_empty</item>
        <item name="Media.Empty.text">@string/empty_text</item>
        <!--空狀態(tài)資源圖-->
        <item name="Media.Empty.resource">@drawable/ic_empty_zhihu</item>

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

需自定義其他屬性吴攒,可另寫(xiě)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>
屬性對(duì)照?qǐng)D1 屬性對(duì)照?qǐng)D2

* 裁剪

裁剪功能提供了裁剪開(kāi)關(guān)(isCrop)、裁剪類型(cropStyle)洼怔、裁剪框尺寸(cropFocusWidthPx署惯、cropFocusHeightPx)、圓形裁剪支持保存正方形圖(isCropSaveRectangle)镣隶、裁剪圖保存地址(cropCacheFolder)等幾個(gè)屬性极谊。

注:裁剪有效的前提必須是開(kāi)啟裁剪同時(shí)最大選擇數(shù)量為1诡右。

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

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

八怀酷、具體調(diào)用

Matisse api眾多稻爬,須根據(jù)需要自行調(diào)用,大致例子如下

kotlin項(xiàng)目調(diào)用
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項(xiàng)目調(diào)用
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)                               // 開(kāi)啟裁剪
        .cropFocusWidthPx(400)                      // 設(shè)置裁剪后保存圖片的寬高
        .cropFocusHeightPx(400)                     // 設(shè)置裁剪后保存圖片的寬高
        .cropStyle(CropImageView.Style.RECTANGLE)   // 方形裁剪CIRCLE為圓形裁剪
        .isCropSaveRectangle(true)                  // 裁剪后保存方形(只對(duì)圓形裁剪有效)
        .addFilter(new GifSizeFilter(320, 320, 5 * Filter.K * Filter.K))  // 篩選數(shù)據(jù)源可選大小限制
        .gridExpectedSize(getResources().getDimensionPixelSize(R.dimen.grid_expected_size))
        .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
        .thumbnailScale(0.8f)
        .setStatusIsDark(true)                      // 設(shè)置狀態(tài)欄文字顏色 需依賴ImmersionBar庫(kù)
        .imageEngine(new GlideEngine())             // 加載庫(kù)需外部實(shí)現(xiàn)
        .forResult(REQUEST_CODE_CHOOSE);

返回結(jié)果獲得:

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返回值  裁剪結(jié)果不返回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)
        // 壓縮后的文件         (多個(gè)文件壓縮可以循環(huán)壓縮)
        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"
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蜕依,一起剝皮案震驚了整個(gè)濱河市桅锄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌样眠,老刑警劉巖友瘤,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異檐束,居然都是意外死亡辫秧,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)被丧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)盟戏,“玉大人,你說(shuō)我怎么就攤上這事甥桂∈辆浚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵黄选,是天一觀的道長(zhǎng)蝇摸。 經(jīng)常有香客問(wèn)我,道長(zhǎng)办陷,這世上最難降的妖魔是什么貌夕? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮民镜,結(jié)果婚禮上啡专,老公的妹妹穿的比我還像新娘。我一直安慰自己殃恒,他們只是感情好植旧,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著离唐,像睡著了一般病附。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上亥鬓,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天完沪,我揣著相機(jī)與錄音,去河邊找鬼。 笑死覆积,一個(gè)胖子當(dāng)著我的面吹牛听皿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播宽档,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼尉姨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了吗冤?” 一聲冷哼從身側(cè)響起又厉,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎椎瘟,沒(méi)想到半個(gè)月后覆致,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肺蔚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年煌妈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宣羊。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡璧诵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出仇冯,到底是詐尸還是另有隱情腮猖,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布赞枕,位于F島的核電站,受9級(jí)特大地震影響坪创,放射性物質(zhì)發(fā)生泄漏炕婶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一莱预、第九天 我趴在偏房一處隱蔽的房頂上張望柠掂。 院中可真熱鬧,春花似錦依沮、人聲如沸涯贞。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)宋渔。三九已至,卻和暖如春辜限,著一層夾襖步出監(jiān)牢的瞬間皇拣,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留氧急,地道東北人颗胡。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像吩坝,于是被迫代替她去往敵國(guó)和親毒姨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,501評(píng)論 25 707
  • 用兩張圖告訴你钉寝,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料弧呐? 從這篇文章中你...
    hw1212閱讀 12,693評(píng)論 2 59
  • afinalAfinal是一個(gè)android的ioc,orm框架 https://github.com/yangf...
    wgl0419閱讀 6,263評(píng)論 1 9
  • afinalAfinal是一個(gè)android的ioc瘩蚪,orm框架 https://github.com/yangf...
    passiontim閱讀 15,399評(píng)論 2 45
  • 凌晨,五點(diǎn)言沐,一路奔跑向西邓嘹,四公里處已是龍湖公園,油漆鋪就而成的路险胰,路邊的垂柳汹押,讓我想起了趙雷那首火遍天下的《城都》...
    一個(gè)俗人001閱讀 135評(píng)論 0 0