玩安卓从 0 到 1 之适配器思考

简介: 玩安卓从 0 到 1 之适配器思考

前言

这篇文章是这个系列的第五篇文章了,下面是前三篇文章:

1、玩安卓从 0 到 1 之总体概览

2、玩安卓从 0 到 1 之项目首页

3、玩安卓从 0 到 1 之首页框架搭建。

4、玩安卓从 0 到 1 之架构思考

按照惯例,放一下 Github 地址和 apk 下载地址吧!

apk 下载地址:www.pgyer.com/llj2

Github地址:github.com/zhujiang521…

前因后果

之前不管是 ListView 、GridView 还是 RecyclerView ,都使用的是泓洋大神的开源库 baseAdapter ,以前代码全都是 Java 编写的,觉得这个库很方便,省了很多事,所以之前公司的项目中也都使用的是这个库,一直没觉得有什么不对,但是之后都换成 Kotlin 编写项目之后就觉得有点不太对了,为什么这样说呢?

大家先来看下使用这个库的时候写的代码:

mRecyclerView.setAdapter(new CommonAdapter<String>(this, R.layout.item_list, mDatas)
{
    @Override
    public void convert(ViewHolder holder, String s)
    {
        holder.setText(R.id.id_item_list_title, s);
    }
});

看着很简单,也很方便,还有就是在 ViewHolder 中添加了许多常用的辅助方法,比如上面使用到的 setText 方法,只需要传入 TextView 的 id 和字符就可以完成设置,无需进行 findViewById ,如果你的控件是自定义的或者是辅助方法中没有你需要的方法,这个时候你还可以使用 getView 方法来获取到你所需要的控件来进行操作。

其实这个库是很好的,但 Kotlin 横空出世了,Kotlin 的 extensions 也是使用 Kotlin 的一大原因,为什么?因为不用写那些乱七八糟的 findViewById 了啊!之前用 Java 编写的时候没有办法才写的(这里不提黄油刀等工具,只是单纯地谈论 findViewById 。),现在能不写肯定不想写了,但是再看看我用 Kotlin 时使用这个库的时候写的代码:

class ProfileAdapter(context: Context,
    profileItemList: ArrayList<ProfileItem>,
    layoutId: Int = R.layout.adapter_profile):
    CommonAdapter<ProfileItem>(context, layoutId, profileItemList) {
    override fun convert(holder: ViewHolder, t: ProfileItem, position: Int) {
        val profileAdLlItem = holder.getView<LinearLayout>(R.id.profileAdLlItem)
        val profileAdIv = holder.getView<ImageView>(R.id.profileAdIv)
        val profileAdTvTitle = holder.getView<TextView>(R.id.profileAdTvTitle)
        profileAdTvTitle.text = t.title
        profileAdIv.setImageResource(t.imgId)
        profileAdLlItem.setOnClickListener {
        holder.itemView.profileAdTvTitle.text = t.title
        holder.itemView.profileAdIv.setImageResource(t.imgId)
    }
}

没有什么意义啊!我使用三方库的原因是什么?肯定是为了简化代码,为了省事,但这样显然没有省事。

再来看一下泓洋大神对这个库的描述:

只需要简单的将Adapter继承CommonAdapter,复写convert方法即可。省去了自己编写ViewHolder等大量的重复的代码。

“省去了自己编写ViewHolder等大量的重复的代码”,但是回过头来想一想,“ViewHolder等大量的重复的代码”又究竟是什么呢?随便举个例子吧:

public static class ViewHolder extends RecyclerView.ViewHolder {
        public View rootView;
        public TextView mTvTimeFrame;
        public TextView mTvTimeFrameDelete;
        public TextView mTvStartDate;
        public ViewHolder(View rootView) {
            super(rootView);
            this.rootView = rootView;
            this.mTvTimeFrame = (TextView) rootView.findViewById(R.id.tv_time_frame);
            this.mTvTimeFrameDelete = (TextView) rootView.findViewById(R.id.tv_time_frame_delete);
            this.mTvStartDate = (TextView) rootView.findViewById(R.id.tv_start_date);
        }
    }

平时写 ViewHolder 的作用也只是对控件进行初始化而已,现在有了 Kotlin 的 extensions ,其实并没有这个必要了,咱们完全可以自己来写个基类解决,不需要使用泓洋大神这个库了,况且这个库也停更了四年了。。。

开始解决

上面说的其实不完全对,其实这个库还有其他的作用,比如 Item 的 多 Type 等等,在这里由于玩安卓这个应用比较简单,没有用到多 Type 的地方,所以在这里先不考虑多 Item 的情况,只考虑单 Item 的情况。

接下来的任务就是自己写一个基类了,并把使用到这个库的 Adapter 给修改成继承咱们的基类。

其实这个基类很简单,平时咱们怎样写 RecyclerView 的 Adapter 现在就怎样写就行:


abstract class BaseListAdapter<T : Any>(
    protected val mContext: Context,
    private val layoutId: Int,
    private val dataList: List<T>
) : RecyclerView.Adapter<BaseListAdapter.ViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(layoutId, parent, false)
        return ViewHolder(view)
    }
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        convert(holder.itemView, dataList[position], position)
    }
    abstract fun convert(view: View, data: T, position: Int)
    override fun getItemId(position: Int): Long {
        return position.toLong()
    }
    override fun getItemCount() = dataList.size
    class ViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView),
        LayoutContainer
}

是不是很简单!如果其他地方需要使用的时候只需要继承此类并实现 convert 方法即可,为啥也叫 convert 呢?当然是因为懒了,这样就不需要改太多的代码啊!

还是再简单说下上面的基类吧,构造方法中的三个参数:Context 就不说了,Adapter 中很多地方也会用到,为什么叫 mContext ,也是因为泓洋大神的库中也是这个名称。。。。layoutId 则是布局,dataList 就是数据的集合了。这里有一点需要注意,我在 convert 方法中传入的并不是 ViewHolder ,而是 ViewHolder 中的 itemView,这是为了在使用的时候可以直接使用 Kotlin 的“魔法”!其他的代码就不多说了,因为平时大家写 Adapter 的时候都是这么干的。

来看下上面的例子改成继承咱们的 BaseListAdapter 该怎样写吧:

class ProfileAdapter(
    context: Context,
    profileItemList: ArrayList<ProfileItem>,
    layoutId: Int = R.layout.adapter_profile
) : BaseListAdapter<ProfileItem>(context, layoutId, profileItemList) {
    override fun convert(view: View, data: ProfileItem, position: Int) {
        view.profileAdTvTitle.text = data.title
        view.profileAdIv.setImageResource(data.imgId)
        view.profileAdLlItem.setOnClickListener {
            toJump(data.title)
        }
    }
}

看到刚才所说的魔法了吗?不需要进行 findViewById了,但是还需要通过 view 来获取一下,不过也比之前要好多了是不!

文末总结

这篇文章其实写的有点水,自己其实这块搞的并不是很清楚,看官方给出的实例完全可以直接获取到的,不需要再通过 view 来获取,但是我那样进行尝试的时候始终不行,来看下官方的代码:

class ForecastListAdapter(private val weekForecast: ForecastList,
        private val itemClick: (Forecast) -> Unit) :
        RecyclerView.Adapter<ForecastListAdapter.ViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.ctx).inflate(R.layout.item_forecast, parent, false)
        return ViewHolder(view, itemClick)
    }
    @SuppressLint("SetTextI18n")
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bindForecast(weekForecast[position])
    }
    override fun getItemCount() = weekForecast.size
    class ViewHolder(override val containerView: View, private val itemClick: (Forecast) -> Unit)
        : RecyclerView.ViewHolder(containerView), LayoutContainer {
        fun bindForecast(forecast: Forecast) {
            with(forecast) {
                Picasso.with(itemView.ctx).load(iconUrl).into(icon)
                dateText.text = date.toDateString()
                descriptionText.text = description
                maxTemperature.text = "${high}º"
                minTemperature.text = "${low}º"
                itemView.setOnClickListener { itemClick(this) }
            }
        }
    }
}

之前以为是在 ViewHolder 中的问题,但我试过将方法放到 ViewHolder 中,还是不行,我又以为是什么库没有引用,又把实例中的所有依赖引用了下,还是不可以,如果有知道的欢迎去我的 Github 上帮我提个 issues,或者直接在评论区告诉我,感激不尽。

接上文——解魔法

上面写了官方的就可以直接获取到控件,感觉像是用了魔法一样,但是开发没有魔法,肯定是有原因的。。。

于是乎我开始找不同,终于发现了罪魁祸首:

androidExtensions {
    experimental = true
}

刚开始的时候Extensions是不支持在ViewHolder中使用视图绑定的,因此还是需要些findViewById,但是从Kotlin 1.1.4起,Extensions加入了增强功能,由于这项功能还未正式发布,因此需要开启实验标志。

使用的方法上面其实写的也有,在Activity,Fragment,View中我们知道import对应的layout就可以了,但是 ViewHolder 需要实现 LayoutContainer ,接口返回一个containerView,按照字面意思理解就是内容视图,这个 containerView 就包含了 ViewHolder 里面的所有子View,因此就可以直接使用控件了。

修改代码

既然找到原因了,那就开始吧!

首先修改下 BaseListAdapter 的抽象方法中的函数,不直接通过 holder 的 itemView 来获取控件了,直接通过 ViewHolder 来获取就行了,

来看下修改后的 BaseListAdapter :

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    convert(holder, dataList[position], position)
}
abstract fun convert(holder: ViewHolder, data: T, position: Int)

基本没改,只是把参数又变回 ViewHolder 了,来看下使用吧:

override fun convert(holder: ViewHolder, data: ProfileItem, position: Int) {
    with(holder) {
        profileAdTvTitle.text = data.title
        profileAdIv.setImageResource(data.imgId)
        profileAdLlItem.setOnClickListener {
            toJump(data.title)
        }
    }
}

优雅了些许,而且也不担心 ViewHolder 不起作用。

OK 了,先到这里吧!



目录
相关文章
|
6月前
|
XML 前端开发 Java
24. 【Android教程】适配器 Adapter
24. 【Android教程】适配器 Adapter
126 3
|
设计模式 Java 数据库连接
Java设计模式-适配器模式(Adapter)
Java设计模式-适配器模式(Adapter)
|
XML 缓存 算法
重学RecyclerView Adapter封装的深度思考和实现
重学RecyclerView Adapter封装的深度思考和实现
269 0
重学RecyclerView Adapter封装的深度思考和实现
|
Android开发 Kotlin
玩安卓从 0 到 1 之架构思考
玩安卓从 0 到 1 之架构思考
138 0
玩安卓从 0 到 1 之架构思考
|
设计模式 Java
浅析Java设计模式【2.2】——适配器
Java常用设计模式,适配器模式
91 0
浅析Java设计模式【2.2】——适配器
|
Android开发
【Android 插件化】VirtualApp 源码分析 ( 添加应用源码分析 | LaunchpadAdapter 适配器 | 适配器添加元素 | PackageAppData 元素 )
【Android 插件化】VirtualApp 源码分析 ( 添加应用源码分析 | LaunchpadAdapter 适配器 | 适配器添加元素 | PackageAppData 元素 )
321 0
【Android 插件化】VirtualApp 源码分析 ( 添加应用源码分析 | LaunchpadAdapter 适配器 | 适配器添加元素 | PackageAppData 元素 )
|
设计模式
结构型-Adapter
适配器模式的原理与实现 适配器模式 的英文翻译是 Adapter Design Pattern。顾名思义,这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。对于这个模式,有一个经常被拿来解释它的例子,就是 USB 转接头充当适配器,把两种不兼容的接口,通过转接变得可以一起工作。 原理很简单,我们再来看下它的代码实现。适配器模式有两种实现方式:类适配器和对象适配器。其中,类适配器使用继承关系来实现,对象适配器使用组合关系来实现。具体的代码实现如下所示。其中,ITarget 表示要转化成的接口定义。Adaptee 是一组不兼容 ITa
119 0
结构型-Adapter
|
设计模式 Java
浅谈JAVA设计模式之——适配器模式(Adapter
将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
223 0
浅谈JAVA设计模式之——适配器模式(Adapter
适配器模式(Adapter)
一 概述 定义:适配器模式将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)。
922 0