在这第四个部分,我们将介绍的 Kotlin 主题:
全部章节:
Kotlin — Part 0:关于这个系列
Kotlin — Part 1:配置 Android Studio
Kotlin — Part 2:语法,空安全,静态类型
Kotlin — Part 3:扩展函数、Android 扩展、委托属性
Kotlin — Part 4:RecyclerView— Kotlin 适配器委托&数据类
Kotlin — Part 5:Kotlin,RxJava&RxAndroid
Kotlin — Part 6:API-Retrofit&Kotlin)
Kotlin — Part 7:无限滑动:高阶函数& Lambdas
Kotlin — Part 8:方向改变(序列化&数据类)
Kotlin — Part 9:单元测试与 Kotlin(Mockito,RxJava)
Github 仓库:github.com/imuhao/Kedd…
我们将为 RecyclerView创建一个新的适配器,在这种情况下我们将使用一个模式叫做” Delegate Adapter”,它是我看的这篇文章受的启发.
我们的 Adapter 将是一个委托列表适配器,它负责知道 RecyclerView 如何加载和返回一个指定的 View.一般的做法是接受一个 ViewType 列表,然后委托适配器根据 ViewType 加载和填充视图到 item 中.我们将通过参数将 item 传递给委托适配器,所以 item 可以根据指定的是数据到这个 View.
背后的思想是匹配委托适配器可以匹配指定的 item, 像下面的图片:
感谢 ViewType ,我们的委托适配器知道需要为这个
item 创建那个 View.在这种情况下我们将会有一个列表的 items ,在列表的底部将添加一个加载的 item 来显示正在加载更多的状态.所以我们需要两个委托适配器,一个是新闻,另一个是加载状态.
这个方法给予你许多的灵活性来添加新类型的 View 到你的 RecyclerView. 只需要添加一个新的委托适配器来对应一个新类型的 ViewType.例如, 我们可以为促销活动添加一个新的 ViewType,对应的委托适配器加载一个促销活动视图.
是一个用来显示 RecyclerView 上 item 的接口.每一个 item 必须实现这个接口,所以我们可以得到每一个 item 的 ViewType.然后根据这个类型搜索对性的委托适配器.
- interface ViewType {
- fun getViewType() : Int
- }
在我们的 Adapter 将有一个集合存储 ViewType 集合.
- private var items: ArrayList<ViewType>
在这里我们来存储新闻和加载的 item. 我们新模型将继承 ViewType. 我们将为加载 item 创建一个新的本地 item.
我们需要做3件事:
- interface ViewTypeDelegateAdapter {
- fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder
- fun onBindViewHolder(holder: RecyclerView.ViewHolder, item: ViewType)
- }
- class LoadingDelegateAdapter : ViewTypeDelegateAdapter {
- override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder = TurnsViewHolder(parent)
- override fun onBindViewHolder(holder: RecyclerView.ViewHolder, item: ViewType) {
- }
- class TurnsViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder(parent.inflate(R.layout.news_item_loading)) {
- }
- }
你可以看到我们通过实现” ViewTypeDelegateAdapter”接口,创建了我们自己的委托适配器.这个接口允许我们有一个通用的列表委托适配器,调用这个方法将不需要 NewsAdapter 知道委托适配器具体的实现.一个方法来创建 ViewHolder 另一个绑定数据.
我们将为 ViewType 与委托适配器绑定一个映射.
- private
- var delegateAdapters = SparseArrayCompat < ViewTypeDelegateAdapter > () init {
- delegateAdapters.put(AdapterConstants.LOADING, LoadingDelegateAdapter())
- }
init 是Kotlin 中的一个保留字,它代表一个类的构造函数.在这里我们初始化 map ,添加每一个 ViewType 类型和相应的委托适配器.在这种情况下:
AdapterConstants.LOADING > LoadingDelegateAdapter()
在 Kotlin 中创建一个对象不需要使用 new 关键字
让我们来创建Loading Item, 我们将插入到 items 集合中,这个 item 将根据位置加载视图.
- private val loadingItem = object: ViewType {
- override fun getViewType() : Int = AdapterConstants.LOADING
- }
将这个 item 添加到items 集合中的第一个位置来渲染它.
- init {
- delegateAdapters.put(...)
- items = ArrayList()
- items.add(loadingItem)
- }
在 Kotlin 中有一个叫”Object expressions”的东西,它类似 Java 中的匿名内部类,允许你显示地创建一个新的子类,在这种情况下我们没有创建一个新类文件来创建了一个加载 item.这个语法是非常直观的,你可以看到我们从 ViewType接口派生了一个类并且实现了需要的方法.
这个 getViewType() 函数在内部只有一个表达式.在 Kotlin 中我们可以利用 Kotlin 的优势,来转换这个方法:
- override fun getViewType() : Int {
- return AdapterConstants.LOADING
- }
转变为:
- override fun getViewType() = AdapterConstants.LOADING
它像我们将 AdapterConstants.LOADING 分配给一个函数.这是一个短的方法做相同实现,更加简洁.你也不需要指定返回类型,它可以从上下文中推断出来.所以它现在看起来这样:
- private val loadingItem = object: ViewType {
- override fun getViewType() = AdapterConstants.LOADING
- }
所有的代码可以在这里查看:
github.com/imuhao/Kedd…
在创建我们的新闻委托适配器之前,配置 NewsAdapter 来接受新闻集合,我们需要 UI 对象代表每一个新闻.在 Java 中正常情况下需要这样一个类:
- public class RedditNewsItem {
- private String author;
- private String title;
- public MyNews(String author, String title) {
- this.author = author;
- this.title = title;
- }
- public String getAuthor() {
- return author;
- }
- public void setAuthor(String author) {
- this.author = author;
- }
- public String getTitle() {
- return title;
- }
- public void setTitle(String title) {
- this.title = title;
- }
- }
Kotlin 再一次为我们带来帮助的是一个叫做” data class”的数据类型,它带来的许多的方便,下面是这个相同的实现:
- data class RedditNewsItem(var author: String, var title: String) {}
使用者简单的一行就完成了之前 Java 代码相同的功能,它意味着 author 和 title 有自己的 getter 和 setter 方法,它有一个构造器需要两个参数,这是非常惊人的!但是等等!使用这个数据类你会有更多的好处:
github.com/imuhao/Kedd…
另外我们需要我们的类继承 ViewType,这样它就可以包含在 NewsAdapter 的 items 中作为一个 item.在这种情况下作为一个新的 Item:
github.com/imuhao/Kedd…
现在我们已经创建了新闻 Bean 对象,我们需要委托适配器负责创建 View.这有一个我们需要做的预览图:
所以我们需要:
这里有所有我们创建 NewsDelegateAdapter 的方法.我们将不再说明每一个细节:
让我们重新查看一些 Kotlin 的东西
运行我们使用 ImageView 加载一个网络图片
- fun ImageView.loadImg(imageUrl: String) {
- if (TextUtils.isEmpty(imageUrl)) {
- Picasso.with(context).load(R.mipmap.ic_launcher).into(this)
- } else {
- Picasso.with(context).load(imageUrl).into(this)
- }
- }
我们将从 Reddit 接受一个时间,这个时间是一个 long 格式,我们将转换 Long 类型到一个字符串类型,像”3 day and 1 minute age”
- fun Long.getFriendlyTime() : String {
- // logic here...
- }
打开文件” TimeExt.kt”来查看代码,
打开文件” NewsDelelgateAdapter.kt”,你将查看我们也使用了 Android Extension, 但是在这种情况下我们添加了 synthetic 包,在结尾添加了一个”view”额外值.
- import kotlinx.android.synthetic.main.news_item.view. *
这是一个在没有 Activity 或Fragment 上下文时绑定 View 的一个办法.
让我们修改 NewsAdapter 接受一个新闻集合,来显示我们的新闻列表和正在加载状态.
Kotlin 允许你通过”1..10”这种表达式简单的创建一个范围的数字(Int,Long 和Char),IntRange 继承 IntProgression实现了 Iterable 接口.感谢这个特征我们可以迭代一个范围的数字,下面是从1到10的代码
- for(i in 1..10){
- ...
- }
你可以控制它间隔的步数,或使他递减像从10到1.更多关于这个你可以在这里找到
这是一个 Kotlin 函数,返回一个 MutableList, 一个可以被修改的集合,在这种情况下我们用来存储适配器中的 news 集合.
- val news = mutableListOf < RedditNewsItem > ()
我们将创建一个方法,在晚些时候使用,我们使用 filter 和 map 事件将集合中的 item 转换到另一种类型.
在我们的代码中有一个” getNews”方法,返回一个 RedditNewsItems 集合.为了 过滤和转换我们的列表,我们需要这样做
- fun getNews() : List < RedditNewsItem > {
- return items.filter {
- it.getViewType() == AdapterConstants.NEWS
- }.map {
- it as RedditNewsItem
- }
- }
每一个 list 都有一些有用的函数 像” filter”,允许我们使用一些条件过滤集合中的数据.在我们的 items 集合中保存的 ViewType 类型,里面的类型有 News item 或Loading item, 使用 filter 函数,我们确保只返回 news item .
另一个伟大的函数式” map”,他对集合中的每一项进行转换,在这种情况下我们将 ViewType 类型转换成一个 RedditnNewsItem. 另外,我们也返回一个新创建的对象.
- .map{it as RedditNewsItem}
Map 不是一个新的事物,但是 Kotlin 使它更伟大,因为它允许你在函数名后定义代码块,省略了括号.这个代码块就是 Lambda 表达式,一个不需要定义的函数.
在这里我们没有花更多的时间讲解关于 List 和 Lambdas,但是我想这是一个好的起点来了解如何使用这个伟大的特性,在之后的章节我们将花费更多的时间来谈论关于这个.
我知道我花费了更多的时间来解释委托适配器模式,我考虑到这个模式是一个很优秀的模式,我希望通过这个代码你学习到了 Kotlin 新的特征.
GitHub 仓库地址:github.com/imuhao/Kedd…
来源: https://juejin.im/entry/5a04fde8518825585132273d