<commit_before>---
layout:     post
title:      统一富媒体动效控件
subtitle:   一种与RecycleView搭配实现高效复用的统一富媒体动效控件
date:       2021-08-09
author:     Tristan
header-img: img/post-bg-multimedia.jpeg
catalog: true
tags:
    - 富媒体
    - 动效

---

## 背景
富媒体当下各种样式的控件庞乱繁杂，接口方法不统一，开发者经常使用富媒体控件开发时需要注意的细节较多，比如要事先了解多种控件的api请求方法（工程中视频类控件有好几个）。另外，富媒体当下发展得很快，由于这种多而杂的控件存在，整个应用级别目前没有一个整体的缓存机制，也没有一个应用于列表的综合控件形成与列表类控件的高效复用。

## 目标
设计统一富媒体动效控件旨在满足两个场景：
1. 解决单个运营类场景的复杂媒体高效切换，一个控件承载多样化的动效
2. 解决类似feed流列表形式的模板统一并高效复用，减少模板量和视图数

## 收益
1. 单个控件职能丰富，将简化Feed流模板的设计和开发工作
2. 统一API调用后，会大幅降低对富媒体控件的开发学习成本

## 调研
#### 京东
1. 基本库调研
> 图片加载库：fresco<br/>
> 动效合成库：lottie、ValueAnimator<br/>
> 视频播放库：ijkplayer<br/>

2. feed流调研
> com.jingdong.app.mall.home.floor.view.linefloor.widget.VideoSkuView / IjkVideoView

3. 动效基本没有封装，属性动画使用较多
![image](https://user-images.githubusercontent.com/4709890/128839776-232c2e4d-dd72-4af0-82ea-a60bc1ef6f97.png)

#### 淘宝
1. 基本库调研
> 图片加载库：ImageLoadFeature<br/>
> 动效合成库：lottie、HGifView<br/>
> 视频播放库：ijkplayer<br/>

2. feed流调研
> com.taobao.homepage.view.widgets.MultiLayout / HGifView、HVideoView

3. 视频和GIF封装为一个富布局MultiLayout
HGifView和HImageView集成同一个父组件TUrlImageView使用同一个资源加载器ImageLoadFeature
![image](https://user-images.githubusercontent.com/4709890/128840462-792e7e32-35fe-4ec5-b4d2-c1c1c6b3694d.png)

#### 调研结果
两大电商在以下四方面的总结可以归纳为：
1. 静图上，淘宝对图片组件进行了封装，京东直接使用了fresco库提供的图片组件；<br/>
2. 动效上，淘宝基于图片组件做了封装，京东多使用属性动画的方式产生动画效果；<br/>
3. 两者对lottie的处理是直接使用了L库提供的LottieAnimationView，没有二次封装；<br/>
4. 视频上，两者统一使用了开源的ijkplayer库进行定制自己的视频加载和播放组件；<br/>

#### 初步产出
* 静图使用MultiImageView，三方库选用Fresco或Glide<br/>
* GIF使用MultiGifView，三方库选用Fresco<br/>
* 动效使用MultiLottieView，三方库选用Lottie<br/>
* Banner使用MultiPagerView，结合ViewPager产出banner效果<br/>
* 视频使用MultiVideoView，一方库选用LeadingVideoView支持加载本地资源<br/>
* ~~直播使用MultiLiveView，二方库选用WPlayer~~<br/>
* 整体统一组装为WubaMultiView，产出一个统一动效控件<br/>

## 设计
#### 需求明确后，设计上考虑要解决三个问题：
* 统一性：不同媒体类型调用的接口和模板统一化问题；
* 复用性：富媒体控件在整个APP级别上的缓存复用问题；
* 高效性：如何搭配RecycleView进一步深入到控件级别实现高效复用。

#### 1 统一性
首先，高度封装并统一了接口调用方法，使外部调用者对原本多样化动效的请求方式实现成统一的调用方法，控件自身能够按需加载且是高内聚的；<br/>
另外，支持扩展新的样式。<br/>
控件通过统一的接口封装和灵活切换媒体形式，实现资源请求、播放接口到列表模板的全链统一化。<br/>

#### 2 复用性
给予富媒体的多种item缓存和复用能力，通过一个全局静态媒体控件缓存池，自定义大小，实现了富媒体控件在APP级别上的复用。<br/>

#### 3 高效性
通过与RecycleView的缓存策略搭配使用，满足富媒体多种动效样式在列表视图展示中，完成了从一级模板布局到二级动效控件的高效双重复用；<br/>
既不需要创建和维护多余的富媒体动效模板，也不需要为不展示的动效控件开销额外的内存空间。<br/>

#### 整体控件设计流程图：
![multiview](https://user-images.githubusercontent.com/4709890/128875057-07d7fad2-166a-4847-bf82-e1ddd3a68df1.jpg)

## 实现
#### 1 统一性
1、该控件已枚举了单图（支持Webp格式）、GIF图、Lottie图、Pager定制动效和Video视频，支持扩展能力，开发者还可以继续接入自定义的其它媒体类动效。<br/>

```
public final enum class MediaType private constructor() : kotlin.Enum<MediaType> {
    IMAGE,

    GIF,

    LOTTIE,

    PAGER,

    VIDEO;

    public companion object {
        private final val CACHE_VIEWS: MutableMap<String, ArrayList<IView>
    }
}
```

2、该控件对这些不同类型的动效媒体进行了统一的接口封装，实现请求加载和播放控制等能力的高度统一，对外只提供了流程图中可见的四种基础方法调用：
> 1）设置当前媒体类型<br/>
> 2）设置URL请求<br/>
> 3）播放控制<br/>
> 4）设置缓存开关<br/>

&nbsp;&nbsp;&nbsp;&nbsp;**通过简单的方法封装和内部统一的接口实现完成了控件的高内聚。**

3、对外提供的API比起其它单媒体控件设计更简单：<br/>

```
// 设置媒体类型
fun setMediaType(mediaType: MediaType)

// 获取当前的媒体接口
fun getIView() : IView?


// 设置请求的地址url…
fun setUrl(vararg url: String)

// 设置裁剪类型
fun setActualImageScaleType(scaleType: ScalingUtils.ScaleType)

// 设置默认图及其裁剪类型
fun setPlaceholderImage(resourceId: Int, scaleType: ScalingUtils.ScaleType?)

// 设置请求结果回调
fun setOnRequestStatusListener(listener: WubaMultiView.OnRequestStatusListener)


// 动效播放
fun play()

// 动效停止
fun stop()

// 动效暂停
fun pause()

// 动效是否正在播放
fun isPlaying(): Boolean


// 设置缓存
fun setUseCache(useCache: Boolean)
```



## 总结<commit_msg>Update 2021-08-09-统一富媒体动效控件.md<commit_after>---
layout:     post
title:      统一富媒体动效控件
subtitle:   一种与RecycleView搭配实现高效复用的统一富媒体动效控件
date:       2021-08-09
author:     Tristan
header-img: img/post-bg-multimedia.jpeg
catalog: true
tags:
    - 富媒体
    - 动效

---

## 背景
富媒体当下各种样式的控件庞乱繁杂，接口方法不统一，开发者经常使用富媒体控件开发时需要注意的细节较多，比如要事先了解多种控件的api请求方法（工程中视频类控件有好几个）。另外，富媒体当下发展得很快，由于这种多而杂的控件存在，整个应用级别目前没有一个整体的缓存机制，也没有一个应用于列表的综合控件形成与列表类控件的高效复用。

## 目标
设计统一富媒体动效控件旨在满足两个场景：
1. 解决单个运营类场景的复杂媒体高效切换，一个控件承载多样化的动效
2. 解决类似feed流列表形式的模板统一并高效复用，减少模板量和视图数

## 收益
1. 单个控件职能丰富，将简化Feed流模板的设计和开发工作
2. 统一API调用后，会大幅降低对富媒体控件的开发学习成本

## 调研
#### 京东
1. 基本库调研
> 图片加载库：fresco<br/>
> 动效合成库：lottie、ValueAnimator<br/>
> 视频播放库：ijkplayer<br/>

2. feed流调研
> com.jingdong.app.mall.home.floor.view.linefloor.widget.VideoSkuView / IjkVideoView

3. 动效基本没有封装，属性动画使用较多
![image](https://user-images.githubusercontent.com/4709890/128839776-232c2e4d-dd72-4af0-82ea-a60bc1ef6f97.png)

#### 淘宝
1. 基本库调研
> 图片加载库：ImageLoadFeature<br/>
> 动效合成库：lottie、HGifView<br/>
> 视频播放库：ijkplayer<br/>

2. feed流调研
> com.taobao.homepage.view.widgets.MultiLayout / HGifView、HVideoView

3. 视频和GIF封装为一个富布局MultiLayout
HGifView和HImageView集成同一个父组件TUrlImageView使用同一个资源加载器ImageLoadFeature
![image](https://user-images.githubusercontent.com/4709890/128840462-792e7e32-35fe-4ec5-b4d2-c1c1c6b3694d.png)

#### 调研结果
两大电商在以下四方面的总结可以归纳为：
1. 静图上，淘宝对图片组件进行了封装，京东直接使用了fresco库提供的图片组件；<br/>
2. 动效上，淘宝基于图片组件做了封装，京东多使用属性动画的方式产生动画效果；<br/>
3. 两者对lottie的处理是直接使用了L库提供的LottieAnimationView，没有二次封装；<br/>
4. 视频上，两者统一使用了开源的ijkplayer库进行定制自己的视频加载和播放组件；<br/>

#### 初步产出
* 静图使用MultiImageView，三方库选用Fresco或Glide<br/>
* GIF使用MultiGifView，三方库选用Fresco<br/>
* 动效使用MultiLottieView，三方库选用Lottie<br/>
* Banner使用MultiPagerView，结合ViewPager产出banner效果<br/>
* 视频使用MultiVideoView，一方库选用LeadingVideoView支持加载本地资源<br/>
* ~~直播使用MultiLiveView，二方库选用WPlayer~~<br/>
* 整体统一组装为WubaMultiView，产出一个统一动效控件<br/>

## 设计
#### 需求明确后，设计上考虑要解决三个问题：
* 统一性：不同媒体类型调用的接口和模板统一化问题；
* 复用性：富媒体控件在整个APP级别上的缓存复用问题；
* 高效性：如何搭配RecycleView进一步深入到控件级别实现高效复用。

#### 1 统一性
首先，高度封装并统一了接口调用方法，使外部调用者对原本多样化动效的请求方式实现成统一的调用方法，控件自身能够按需加载且是高内聚的；<br/>
另外，支持扩展新的样式。<br/>
控件通过统一的接口封装和灵活切换媒体形式，实现资源请求、播放接口到列表模板的全链统一化。<br/>

#### 2 复用性
给予富媒体的多种item缓存和复用能力，通过一个全局静态媒体控件缓存池，自定义大小，实现了富媒体控件在APP级别上的复用。<br/>

#### 3 高效性
通过与RecycleView的缓存策略搭配使用，满足富媒体多种动效样式在列表视图展示中，完成了从一级模板布局到二级动效控件的高效双重复用；<br/>
既不需要创建和维护多余的富媒体动效模板，也不需要为不展示的动效控件开销额外的内存空间。<br/>

#### 整体控件设计流程图：
![multiview](https://user-images.githubusercontent.com/4709890/128875057-07d7fad2-166a-4847-bf82-e1ddd3a68df1.jpg)

## 实现
#### 1 统一性
1、该控件已枚举了单图（支持Webp格式）、GIF图、Lottie图、Pager定制动效和Video视频，支持扩展能力，开发者还可以继续接入自定义的其它媒体类动效。<br/>

```
public final enum class MediaType private constructor() : kotlin.Enum<MediaType> {
    IMAGE,

    GIF,

    LOTTIE,

    PAGER,

    VIDEO;

    public companion object {
        private final val CACHE_VIEWS: MutableMap<String, ArrayList<IView>
    }
}
```

2、该控件对这些不同类型的动效媒体进行了统一的接口封装，实现请求加载和播放控制等能力的高度统一，对外只提供了流程图中可见的四种基础方法调用：
> 1）设置当前媒体类型<br/>
> 2）设置URL请求<br/>
> 3）播放控制<br/>
> 4）设置缓存开关<br/>

&nbsp;&nbsp;&nbsp;&nbsp;**通过简单的方法封装和内部统一的接口实现完成了控件的高内聚。**

3、对外提供的API比起其它单媒体控件设计更简单：<br/>

```
// 设置媒体类型
fun setMediaType(mediaType: MediaType)

// 获取当前的媒体接口
fun getIView() : IView?


// 设置请求的地址url…
fun setUrl(vararg url: String)

// 设置裁剪类型
fun setActualImageScaleType(scaleType: ScalingUtils.ScaleType)

// 设置默认图及其裁剪类型
fun setPlaceholderImage(resourceId: Int, scaleType: ScalingUtils.ScaleType?)

// 设置请求结果回调
fun setOnRequestStatusListener(listener: WubaMultiView.OnRequestStatusListener)


// 动效播放
fun play()

// 动效停止
fun stop()

// 动效暂停
fun pause()

// 动效是否正在播放
fun isPlaying(): Boolean


// 设置缓存
fun setUseCache(useCache: Boolean)
```

同时，内部媒体类组件的接口实现方法既做到了统一又理解起来简单：<br/>

```
/**
 * 统一动效接口，抽象类
 */
interface IView {

    fun getMediaType() : MediaType

    fun getView(context: Context) : View

    fun setUrl(vararg url: String) {
        val list = ArrayList<String>()
        list.addAll(url)
        setUrl(list)
    }

    fun setUrl(urls: List<String>)

    fun setActualImageScaleType(scaleType: ScalingUtils.ScaleType)

    fun setPlaceholderImage(resourceId: Int, scaleType: ScalingUtils.ScaleType?)

    fun setFailureImage(resourceId: Int, scaleType: ScalingUtils.ScaleType?)

    fun play()

    fun pause()

    fun stop()

    fun isPlaying() : Boolean

    fun setOnRequestStatusListener(listener: WubaMultiView.OnRequestStatusListener)

}
```

&nbsp;&nbsp;&nbsp;&nbsp;**这种高内聚的接口设计和实现方式，对外部富媒体控件的使用者和内部富媒体样式的扩展者而言，都提供了极简的快速开发和定制入口。**


## 总结
{'fraction_non_alphanumeric': 0.12932312252964426, 'fraction_numerical': 0.034337944664031624, 'mean_word_length': 6.096406660823838, 'pattern_counts': {'":': 0, '<': 70, '<?xml version=': 0, '>': 92, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4868', 'n_tokens_mistral': 5209, 'n_tokens_neox': 5896, 'n_words': 397}