【Android】Matisse图片选择库源码解析

Matisse中主要的模块有Matisse、SelectionCreator、SelectionSpec、MatisseActivity四个类,它们的工作流程如图:

img

我们先看到Matisse的使用代码,通过使用的代码来解析源码

Matisse类

我们从使用时的入口,Matisse类看起。我们进入Matisse的源码,可以看到下面这一部分:

我们可以发现,Matisse中用弱引用保存了Activity及Fragment的引用。它的from方法有两个重载,一个是传入Activity,一个是传入Fragment。也就是它同时支持了Activity及Fragment。它的choose方法有两个重载,最后都创建了一个SelectionCreator类。

SelectionCreator类-配置部分

我们看到SelectionCreator类的源码

可以看到,它内部保存了刚刚创建的Matisse及一个SelectionSpec类。SelectionCreator类采用了一种Builder的设计,比较巧妙的是将它的配置属性都放到了SelectionSpec类中。

SelectionSpec类

我们可以查看SelectionSpec的源码

可以看到,SelectionSpec类采用了一种懒汉式单例模式的设计,使用的时候才会被加载。

看到刚刚获取实例的getCleanInstance方法,会发现它仍然是调用了getInstance方法,然后调用了其reset方法对数据进行清空。保证了每次调用时的配置都是初始配置。

SelectionCreator类-跳转部分

我们可以回到SelectionCreator。当我们对其进行了一系列配置之后,就会调用forResult方法来打开选择图片Activity。我们可以看看forResult的源码。

可以看到,它构建了Intent,然后分别对Activity及Fragment进行不同的跳转处理。最后都是调用了startActivityForResult方法。也就是我们的选择结果会由onActivityResult方法返回。

同时可以看到,它在Intent中,启动了MatisseActivity。

MatisseActivity类

在MatisseActivity类的onCreate方法的开始,我们就可以看到这样一行代码:

由于SelectionSpec是单例模式,所以我们可以通过getInstance方法拿到之前配置过的SelectionSpec。

获取资源及展示

Matisse 中所展示的资源都是用 Loader 机制进行加载的,Loader 机制是 Android 3.0 之后官方推荐的加载 ContentProvider 中资源的最佳方式,不仅能极大地提高我们资源加载的速度,而且还能让我们的代码变得更加的简洁。

下面是它的资源加载的流程图:

img

这里的数据加载使用到了Android的Loader API。详细可以看这篇文章:Android Loader 机制,让你的数据加载更加轻松

createAndInstallLoader方法如下:

文件夹的选择

AlbumSpinner是一个自定义View,位于MainActivity左上角。主要包括了显示文件夹名称的TextView、显示文件夹列表的ListPopupWindow。

在 AlbumCollection 中返回的 Cursor,作为 AlbumsSpinner 的数据源,然后通过 AlbumsAdapter 将资源文件夹显示出来。

当选中文件夹的时候,将所点击的文件夹的 position 回调给 MatisseActivity 中的 onItemSelected() 方法。

通过 AlbumsSpinner 回调出来的 position 拿到对应的文件夹的信息,然后将当前的界面进行刷新,使当前界面显示所选择的文件夹的图片。

可以看到这里做了一些处理,mContainer是有图片时图片列表的布局。而mEmptyView则是没有图片时的布局。在文件夹中没有图片时显示mEmpty。而显示具体图片列表的布局,则是MediaSelectionFragment这个Fragment。

首页照片墙的实现

首页的图片墙非常值得我们学习。图片墙的数据源是通过 Loader 机制来进行加载的 ,它会通过我们选择不同的资源文件夹而展示不同的图片。

因此我们在选择资源文件夹的时候,便将资源文件夹的 id,传给对应的 Loader,让它对相应的资源文件进行加载。

Item实体类

Matisse 把图片和音频的信息封装成了实体类,并实现了 Parcelable 接口,让其序列化,通过外部传入的 Cursor,拿到对应的 Uri、媒体类型、文件大小,如果是视频的话,就获取视频播放的时长。

Item布局

图片墙是直接用一个 RecyclerView 通过 GridLayoutManager 进行展示的,Item 是一个继承了 SquareFrameLayout(正方形的 FrameLayout) 的自定义控件,主要包含三个部分

  • 右上角的 CheckView
  • 显示图片的 ImageView
  • 显示视频时长的 TextView

img

CheckView

CheckView是一个自定义的 CheckBox 。它重写了 onMeasure() 方法,将宽和高都定为 48,而且为了屏幕适配性,将 48dp 乘以 density,将 dp 单位转换为像素单位。

然后我们看到onDraw方法:

onDraw() 方法主要分为三个部分

  • 画出空心圆内外的阴影
    Matisse 为了图片选择库看起来更加美观,在空心圆的内外增加了一层辐射渐变的阴影

  • 画出白色的空心圆

  • 描绘出里面的内容
    通过我们外部配置的 mCountable 参数,来决定 CheckView 的显示方式,如果 mCountable 的值为 true 的话,便在内部描绘一层主题颜色的背景,以及代表所选择图片数量的数字,如果 mCount 的值为 false 的话,那么便描绘背景以及填入一个白色的 ✓

    img

MediaGrid

我们接着来看看图片墙的 Item 布局「MediaGrid」的实现逻辑 。MediaGrid 是一个继承了 SquareFrameLayout(正方形的 FrameLayout)的自定义View。是一个拓展了复选功能(CheckView)和显示视频时长(TextView)功能的 ImageView.

我们从 MediaGrid 在 Adapter 中的使用入手,进一步看看 MediaGrid 的代码实现

MediaGrid 的使用主要分两步

  • 初始化图片的公有属性(MediaGrid.preBindMedia(new MediaGrid.PreBindInfo()))
  • 将图片对应的信息进行绑定(MediaGrid.bindMedia(Item) )

PreBindInfo 是 MediaGrid 的一个静态内部类,封装了一些图片的一些公用的属性。

第二步便是将一个包含图片信息的 Item 传给 MediaGrid,然后进行相应信息的设置。

MediaGrid 中自定义了回调的接口

点击图片的时候,将点击事件回调到 Adapter,再回调到 MediaSelectionFragment,再回调到 MatisseActivity。

当点击右上角的 CheckView 的时候,便将点击事件回调到 Adapter 中,然后根据 countable 的值,来进行相应的设置(显示数字或者显示 √),然后再将对应的 Item 信息保存在 SelectedItemCollection(Item 的容器) 中。

预览界面的实现

打开预览界面有两种方法

  • 点击首页的某个图片
  • 选择图片之后,点击首页左下角的预览(Preview)按钮

这两种方法打开的界面看起来似乎是一样的,但实际上他们两个的实现逻辑很不一样,因此用了两个不同的 Activity。

点击首页的某张图片之后,会跳转到一个包含 ViewPager 的界面,因为对应资源文件夹中可能会有很多的图片,这时候如果将包含该文件夹中所有的图片直接传给预览界面的 Activity,是非常不实际的。

比较好的实现方式便是将「包含对应文件夹的信息的 Album」传给界面,然后再用 Loader 机制进行加载。

而选择首页图片后,点击左下角的预览按钮,实现就不是很一样了。跳转到预览界面,因为我们选择的图片一般都比较少,所以这时候直接将「包含所有选择图片信息的 List」传给预览界面就行了。

虽然两个 Activity 的实现逻辑不太一样,但由于都是预览界面,所以有很多相同的地方。因此Matisse实现了一个 BasePreviewActivity。

img

BasePreviewActivity 的布局主要由三部分组成

  • 右上角的 CheckView
  • 自定义的 ViewPager
  • 底部栏(包括预览(Preview)和使用按钮(Apply))

点击 CheckView 的时候,根据该图片是否已经被选择以及图片的类型,对 CheckView 进行相应的设置以及更新底部栏。

当用户对 ViewPager 进行左右滑动的时候,根据当前的 position 拿到对应的 Item 信息,然后对 CheckView 进行相应的设置以及切换图片。

评 论 区

    1. Antibiotic十

      (2018-07-27 16:41)


      [code]long long ago;[/code]
      测试

发表评论

%d 博主赞过: