前言
这篇文章是这个系列的第五篇文章了,下面是前三篇文章:
按照惯例,放一下 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 了,先到这里吧!