大家好! 我的帖子是希望帮助一些Android元素。 如果您是尚未开发用于构建列表的算法的开发人员,则可能会发现阅读此材料很有用。 基本上,我想为开发提供现成的解决方案,在整个故事过程中,我会透露出一些有关我如何
进入他们的想法。
在本文中:- 我们形成了几个基本类和接口,用于使用RecyclerView和RecyclerView.Adapter
- 从Android Jetpack连接一个库(可选,首先没有它)
- 为了更快的开发-本文结尾处的template选项;)
参赛作品
好吧! 每个人都已经忘记了ListView,正在安全地在RecyclerView(
Rv )上书写。 那些时候我们
自己实现
ViewHolder模式的时代已经被遗忘了。
Rv为我们提供了一组用于实现列表的现成类,并提供了大量用于显示列表的
LayoutManager 。 实际上,查看许多屏幕,您可以将大多数屏幕想象成一个列表-正是由于每个元素都能够实现自己的
ViewHolder 。
我们已经在Google I / O上详细
了解了开发
故事。但是,总是有两个“ buts!” .. Stackoverflow的标准答案提出了导致复制粘贴的通用解决方案,尤其是在实现Adapter的地方。
目前,
Rv已经三岁了。 上面有很多信息,并且有很多库都有现成的解决方案,但是如果您不需要所有功能,或者如果您继续浏览别人的代码,该怎么办?您会看到那里的《
古代恐怖》不是您想要看到的,或者不是想像的? 在这三年中,Android终于正式接受Kotlin =代码可读性得到了改善,
Rv上发表了许多有趣的文章,充分揭示了其功能。
目的是从
自行车的最佳实践中收集基础,这是用于处理新应用程序列表的框架。 该框架可以通过应用程序之间的逻辑进行补充,使用您需要的内容并丢弃不必要的内容。 我认为这种方法比其他人的图书馆要好得多-在您的课堂上,您有机会了解一切工作原理并控制所需的案例,而不必与其他人的决定挂钩。
让我们从一开始就进行逻辑思考
组件应该做的是确定
接口,而不是类 ,但是最后,我们将关闭将要实现并实现此接口的类的具体实现逻辑。 但是,如果事实证明,通过接口的实现,就形成了复制粘贴-我们可以将其隐藏在抽象类的后面,然后将其隐藏-从抽象继承的类。 我将展示基本接口的实现,但是我的目标是使开发人员尝试朝同一方向思考。 再次-计划是这样的:
一组接口->需要复制粘贴(如果需要)的抽象类 ->并且已经是具有唯一代码的特定类 。 您可以以不同的方式实现接口。
适配器可以对列表做什么? 当您看一些示例时,最容易获得此问题的答案。 您可以查看RecyclerView.Adapter,您会发现一些提示。 如果您想一想,您可以想像以下方法:
IBaseListAdapterinterface IBaseListAdapter<T> { fun add(newItem: T) fun add(newItems: ArrayList<T>?) fun addAtPosition(pos : Int, newItem : T) fun remove(position: Int) fun clearAll() }
在项目中,我发现了一些其他方法,例如 getItemByPos(位置:Int), 甚至是 subList(startIndex:Int,endIndex:Int)。 我再说一遍:您自己必须注意项目中的需求,并在界面中包含功能。 当您知道所有事情都发生在一堂课中时,这并不困难。 禁欲主义将使您摆脱不必要的逻辑,这会降低代码的可读性,因为特定的实现需要更多行。 注意通用
T。 通常,适配器可与列表(项目)的任何对象一起使用,因此此处没有澄清-我们尚未选择方法。 在本文中,至少有两个,第一个界面如下所示:
interface IBaseListItem { fun getLayoutId(): Int }
好吧,是的,这似乎合乎逻辑-我们正在谈论一个列表项,因此每个项目都必须具有某种布局,您可以使用layoutId对其进行引用。 除非我们采用更
高级的方法 ,否则新手开发人员很可能将不需要任何东西。 如果您有足够的开发经验,您当然可以创建一个委托或包装程序,但是用一个较小的项目值得这样做-甚至更少的开发经验吗? 如果您现在没有时间,我在YouTube上的所有链接都非常有用-请记住并继续阅读,因为这里的方法比较简单-我相信通过
Rv的标准操作(
根据官方文档判断) ,上面提供的内容并非如此暗示。
现在是时候将
IBaseListAdapter与接口结合起来了,下面的类将是抽象的:
SimpleListAdapter abstract class SimpleListAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>(), IBaseListAdapter<IBaseListItem> { protected val items: ArrayList<IBaseListItem> = ArrayList() override fun getItemCount() = items.size override fun getItemViewType(position: Int) = items[position].layoutId protected fun inflateByViewType(context: Context?, viewType: Int, parent: ViewGroup) = LayoutInflater.from(context).inflate(viewType, parent, false) override fun add(newItem: IBaseListItem) { items.add(newItem) notifyDataSetChanged() } override fun add(newItems: ArrayList<IBaseListItem>?) { for (newItem in newItems ?: return) { items.add(newItem) notifyDataSetChanged() } } override fun addAtPosition(pos: Int, newItem: IBaseListItem) { items.add(pos, newItem) notifyDataSetChanged() } override fun clearAll() { items.clear() notifyDataSetChanged() } override fun remove(position: Int) { items.removeAt(position) notifyDataSetChanged() } }
*注意:请注意重写的
getItemViewType(位置:Int)函数。 我们需要某种int密钥,Rv将通过该密钥来了解哪种ViewHolder适合我们。 我们
项目的 Val layoutId 对此非常有用,因为 Android每次都有助于使布局的ID唯一,并且所有值均大于零-我们稍后将使用
它在
inflateByViewType()方法(下一行)中为我们的查看器“
inflate” itemView 。
建立清单
以设置屏幕为例。 Android为我们提供
了自己的版本,但是如果设计需要更复杂的功能怎么办? 我更喜欢将此屏幕作为列表填充。 这里将给出这种情况:

我们看到两个不同的列表项,因此
SimpleListAdapter和
Rv在这里
很完美!
让我们开始吧! 您可以从item'ov的布局布局开始:
item_info.xml; item_switch.xml <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="56dp"> <TextView android:id="@+id/tv_info_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="28dp" android:textColor="@color/black" android:textSize="20sp" tools:text="Balance" /> <TextView android:id="@+id/tv_info_value" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical|end" android:layout_marginEnd="48dp" tools:text="1000 $" /> </FrameLayout> <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="56dp"> <TextView android:id="@+id/tv_switch_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="28dp" android:textColor="@color/black" android:textSize="20sp" tools:text="Send notifications" /> <Switch android:id="@+id/tv_switch_value" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical|end" android:layout_marginEnd="48dp" tools:checked="true" /> </FrameLayout>
然后,我们定义类本身,在其中我们要传递与列表交互的值:第一个是标头和一些来自外部的值(我们将有一个存根(stub),关于另一个请求),第二个是标头和布尔变量我们必须采取行动。 为了区分Switch元素,服务器中实体的ID是合适的,如果它们不存在,我们可以在初始化期间自行创建它们。
InfoItem.kt,SwitchItem.kt class InfoItem(val title: String, val value: String): IBaseListItem { override val layoutId = R.layout.item_info } class SwitchItem( val id: Int, val title: String, val actionOnReceive: (itemId: Int, userChoice: Boolean) -> Unit ) : IBaseListItem { override val layoutId = R.layout.item_switch }
在一个简单的实现中,每个元素还需要一个ViewHolder:
InfoViewHolder.kt,SwitchViewHolder.kt class InfoViewHolder.kt(view: View) : RecyclerView.ViewHolder(view) { val tvTitle = view.tv_info_title val tvValue = view.tv_info_value } class SwitchViewHolder.kt(view: View) : RecyclerView.ViewHolder(view) { val tvTitle = view.tv_switch_title val tvValue = view.tv_switch_value }
好吧,最有趣的部分是SimpleListAdapter'a的具体实现:
SettingsListAdapter.kt class SettingsListAdapter : SimpleListAdapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val context = parent.context return when (viewType) { R.layout.item_info -> InfoHolder(inflateByViewType(context, viewType, parent)) R.layout.item_switch -> SwitchHolder(inflateByViewType(context, viewType, parent)) else -> throw IllegalStateException("There is no match with current layoutId") } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (holder) { is InfoHolder -> { val infoItem = items[position] as InfoItem holder.tvTitle.text = infoItem.title holder.tvValue.text = infoItem.value } is SwitchHolder -> { val switchItem = items[position] as SwitchItem holder.tvTitle.text = switchItem.title holder.tvValue.setOnCheckedChangeListener { _, isChecked -> switchItem.actionOnReceive.invoke(switchItem.id, isChecked) } } else -> throw IllegalStateException("There is no match with current holder instance") } } }
*注意:不要忘记在
inflateByViewType方法
(上下文,viewType,父级)的 幕后 : viewType = layoutId。所有组件都准备就绪! 现在,活动代码仍然存在,您可以运行该程序:
activity_settings.xml <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/rView" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout>
SettingsActivity.kt class SettingsActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_settings) val adapter = SettingsListAdapter() rView.layoutManager = LinearLayoutManager(this) rView.adapter = adapter adapter.add(InfoItem("User Name", "Leo Allford")) adapter.add(InfoItem("Balance", "350 $")) adapter.add(InfoItem("Tariff", "Business")) adapter.add(SwitchItem(1, "Send Notifications") { itemId, userChoice -> onCheck(itemId, userChoice) }) adapter.add(SwitchItem(2, "Send News on Email") { itemId, userChoice -> onCheck(itemId, userChoice) }) } private fun onCheck(itemId: Int, userChoice: Boolean) { when (itemId) { 1 -> Toast.makeText(this, "Notification now set as $userChoice", Toast.LENGTH_SHORT).show() 2 -> Toast.makeText(this, "Send news now set as $userChoice", Toast.LENGTH_SHORT).show() } } }
结果,在构建列表时,所有工作归结为以下内容:
1.计算项目
的不同布局数
2.选择他们的
名字 。 我使用的规则是:
Something Item.kt,item_
something .xml,
Something ViewHolder.kt
3.我们为这些类编写一个
适配器 。 原则上,如果您不假装优化,那么一个通用适配器就足够了。 但是在大型项目中,我仍然会在屏幕上做一些操作,因为在第一种情况下,
适配器中的onBindViewHolder()方法不可避免地会增长(代码可读性受到影响)(在我们的示例中是
SettingsListAdapter ),并且每次都要针对每个项目运行该程序,在此方法上运行+
onCreateViewHolder()方法
4.运行代码并享受!
喷气背包
在那一刻之前,我们使用了从
Item.kt到
item_layout.xml的标准数据绑定方法。 但是我们可以统一
onBindViewHolder()方法,将其保留为最小,然后将逻辑转移到Item和layout。
让我们转到官方的Android JetPack页面:

让我们注意“架构”部分中的第一个选项卡。
Android数据绑定是一个非常广泛的主题,我想在其他文章中更详细地讨论它,但是现在我们仅将其用作当前版本的一部分-我们将为
item.xml创建
Item.kt- 变量 (或者您可以将其称为布局的视图模型)。
在撰写本文时,
数据绑定可以这样连接:
android { compileSdkVersion 27 defaultConfig {...} buildTypes {...} dataBinding { enabled = true } dependencies { kapt "com.android.databinding:compiler:3.1.3"
让我们再次遍历基类。 该项目的界面是对前一个界面的补充:
interface IBaseItemVm: IBaseListItem { val brVariableId: Int }
另外,我们将扩展ViewHolder,因此,我们将与databyding联系。 我们将传递
ViewDataBinding到它,之后我们将安全地忘记创建布局和数据绑定
class VmViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root)
这里使用相同的方法,但是在Kotlin上看起来要短得多,不是吗? =)
VmListAdapter class VmListAdapter : RecyclerView.Adapter<VmViewHolder>(), IBaseListAdapter<IBaseItemVm> { private var mItems = ArrayList<IBaseItemVm>() override fun getItemCount() = mItems.size override fun getItemViewType(position: Int) = mItems[position].layoutId override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VmViewHolder { val inflater = LayoutInflater.from(parent.context) val viewDataBinding = DataBindingUtil.inflate<ViewDataBinding>(inflater!!, viewType, parent, false) return VmViewHolder(viewDataBinding) } override fun onBindViewHolder(holder: VmViewHolder, position: Int) { holder.binding.setVariable(mItems[position].brVariableId, mItems[position]) holder.binding.executePendingBindings() } override fun add(newItem: IBaseItemVm) { mItems.add(newItem) notifyItemInserted(mItems.lastIndex) } override fun add(newItems: ArrayList<IBaseItemVm>?) { val oldSize = mItems.size mItems.addAll(newItems!!) notifyItemRangeInserted(oldSize, newItems.size) } override fun clearAll() { mItems.clear() notifyDataSetChanged() } override fun getItemId(position: Int): Long { val pos = mItems.size - position return super.getItemId(pos) } override fun addAtPosition(pos: Int, newItem: IBaseItemVm) { mItems.add(pos, newItem) notifyItemInserted(pos) } override fun remove(position: Int) { mItems.removeAt(position) notifyItemRemoved(position) } }
通常要注意
onCreateViewHolder() ,
onBindViewHolder()方法 。 想法是它们不再增长。 总计,您可以为任何屏幕和任何列表项获得一个适配器。
我们的项目:
InfoItem.kt,SwitchItem.kt class InfoItem(val title: String, val value: String) : IBaseItemVm { override val brVariableId = BR.vmInfo override val layoutId = R.layout.item_info } // class SwitchItem( val id: Int, val title: String, private val actionOnReceive: (itemId: Int, userChoice: Boolean) -> Unit ) : IBaseItemVm { override val brVariableId = BR.vmSwitch override val layoutId = R.layout.item_switch val listener = CompoundButton.OnCheckedChangeListener { _, isChecked -> actionOnReceive.invoke(id, isChecked) } }
在这里,您可以看到
onBindViewHolder()方法的逻辑
去向 。 Android Databinding本身就发挥了作用-现在我们的任何布局都由其视图模型支持,它将平静地处理单击,动画,查询和其他事物的所有逻辑。 你想出什么。
绑定适配器将在此方面做得很好-让您将视图与任何类型的数据连接起来。 同样,借助
双向数据bybyding ,可以改善通信。 他可能会发表以下任何文章,在此示例中,所有内容都可以简化。 一个适配器足以满足我们的需求:
@BindingAdapter("switchListener") fun setSwitchListener(sw: Switch, listener: CompoundButton.OnCheckedChangeListener) { sw.setOnCheckedChangeListener(listener) }
之后,我们将变量值与xml中的
Item绑定在一起:
item_info.xml; item_switch.xml <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <data> <import type="com.lfkekpoint.adapters.adapters.presentation.modules.bindableItemsSettings.InfoItem" /> <variable name="vmInfo" type="InfoItem" /> </data> <FrameLayout android:layout_width="match_parent" android:layout_height="56dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="28dp" android:text="@{vmInfo.title}" android:textColor="@color/black" android:textSize="20sp" tools:text="Balance" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical|end" android:layout_marginEnd="48dp" android:text="@{vmInfo.value}" tools:text="1000 $" /> </FrameLayout> </layout> <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> <data> <import type="com.lfkekpoint.adapters.adapters.presentation.modules.bindableItemsSettings.SwitchItem" /> <variable name="vmSwitch" type="SwitchItem" /> </data> <FrameLayout android:layout_width="match_parent" android:layout_height="56dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginStart="28dp" android:text="@{vmSwitch.title}" android:textColor="@color/black" android:textSize="20sp" tools:text="Send notifications" /> <Switch android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical|end" android:layout_marginEnd="48dp" app:switchListener="@{vmSwitch.listener}" tools:checked="true" /> </FrameLayout> </layout>
应用程序:switchListener =“ @ {vmSwitch.listener}” -在这一行中,我们使用了
BindingAdapter *注意:出于公平的原因,在某些人看来,我们用xml编写了更多代码-但这是了解Android Databinding库的问题。 它可以对布局进行补充,可以快速读取,并且原则上在很大程度上可以精确地删除样板。 我认为Google将很好地开发此库,因为它是Android Jetpack的“架构”标签中的第一个。 尝试在几个项目中将MVP更改为MVVM-许多项目可能令人惊讶。
好吧!..啊,SettingsActivity中的代码:
SettingsActivity.kt...除非适配器已更改,否则未更改! =)但是为了不跳过文章:
class SettingsActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_settings) val adapter = BaseVmListAdapter() rView.layoutManager = LinearLayoutManager(this) rView.adapter = adapter adapter.add(InfoItem("User Name", "Leo Allford")) adapter.add(InfoItem("Balance", "350 $")) adapter.add(InfoItem("Tariff", "Business")) adapter.add(SwitchItem(1, "Send Notifications") { itemId, userChoice -> onCheck(itemId, userChoice) }) adapter.add(SwitchItem(2, "Send News on Email") { itemId, userChoice -> onCheck(itemId, userChoice) }) } private fun onCheck(itemId: Int, userChoice: Boolean) { when (itemId) { 1 -> Toast.makeText(this, "Notification now set as $userChoice", Toast.LENGTH_SHORT).show() 2 -> Toast.makeText(this, "Send news now set as $userChoice", Toast.LENGTH_SHORT).show() } } }
总结
我们得到了一种用于构建列表的算法以及用于处理它们的工具。 就我而言(我几乎总是使用
Databinding ),所有准备工作都归结为初始化文件夹中的基类,.xml中项目的布局,然后绑定至.kt中的变量。
加快发展为了更快地工作,我使用了Apache的Android Studio
模板 -并以小样的方式
演示了 模板的工作原理。 我真的希望有人会派上用场。 请注意,在工作时,您需要从项目的根文件夹中调用模板-之所以这样做,是因为如果您在Gradle中进行更改,则项目的
applicationId参数可能对您不利。 但是
packageName不能被我如此轻易地
欺骗 。 有关模板的可用语言可以在下面的链接中阅读
参考/媒体
1.
现代Android开发:Android Jetpack,Kotlin等(Google I / O 2018,40 m。) -当今时尚的简短指南,从这里也可以从总体上清楚地了解RecyclerView的开发方式;
2.
Droidcon NYC 2016-Radical RecyclerView,36 m。 -来自
Lisa Wray的有关RecyclerView的详细报告;
3.
使用RecyclerView创建列表 -官方文档
4.
接口与 类5.
Android IDE模板格式 ,
总模板 ,
FreeMarker手册 -一种便捷的方法,在本文的框架内将有助于快速创建用于处理列表的必要文件
6.
文章代码 (类名稍有不同,请小心),
用于工作和
视频的 模板 ,如何使用模板7.文章的
英文版