一、说在前面的话
在做开发列表时,会经常遇到混排的情况。如下图:
不过在大部分情况下,做这种布局时都是在固定好的布局模式下做数据填充的,很少会涉及到动态根据不同的Data类型展示不同的布局。即使有,用的最多的也是新增Footer和Header类型。而今天我们要讲的事,是在已有的数据集合中,动态插入数据,并且是不同类型的数据,且保证原有的数据不能出现错乱的问题(PS:有人可能对错乱的问题,产生疑问,这点将会在后面作为重点来讲)。扔一个二向箔给你,自己去领悟
二、言归正传,回归正题
由于列的数量不同,会导致不同的算法,下面我们依次讲:单列、双列、三列,这三种模式
在使用RecyclerView时,设置layoutManager为GridLayoutManager,并结合SpanSizeLookup使用,即可达到跨行的效果。
代码如下:
layoutManager = GridLayoutManager(this, SPAN_COUNT)
rv_listView.layoutManager = layoutManager
rv_listView.adapter = myAdapter
layoutManager.spanSizeLookup = MySpanSizeLookup()
其中MySpanSizeLookup类的代码为:
inner class MySpanSizeLookup : SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return if (myAdapter.isFullRow(position)) {
SPAN_COUNT
} else {
1
}
}
}
或者使用StaggeredGridLayoutManager也可达到跨行的效果,
如果你们是在既定的数据集,根据不同的类型,展示不同的布局,那上面的两行代码基本上已经满足了,不需要在继续往下读了。下面主要讲的数据如何动态插入不同的数据类型,以展示不同的布局。
正文
PS:下文中描述的行,可能是单列、双列、三列
举个例子来说,如果我们在一个集合A展示的列表中,第3行插入一条跨行展示的数据类型;在3行后的基础上在加4行,也就是说第7行;在插入一条跨行展示的数据类型,然后在第7行的基础上在加4行,也就是说第11行插入一条跨行数据,以此类推。
如下图:
如果对于单列的,可能特别容易插入数据。在第1个基础上,每条跨行数据类型加上spanSize,即可达到上面的效果。
代码如下:
val listA = mutableListOf<String>()
for (item in 1..10) {
listA.add("gaozhongkui$item")
}
val spanSize = 4
val bTypeCount = 4
var preInsertBTypePosition = -1
for (item in 1..bTypeCount) {
//由于第一个位置和后续的位置的spanSize不一样,故需要特殊处理
if (preInsertBTypePosition == -1) {
preInsertBTypePosition = 2 //第3个位置,故需要写成2
} else {
preInsertBTypePosition += spanSize
}
if (preInsertBTypePosition < listA.size) {
listA.add(preInsertBTypePosition, "hhy$preInsertBTypePosition")
} else {
listA.add("hhy$preInsertBTypePosition")
}
}
如果对于双列的话,还是按照单行的代码模式写。
代码如下:
...和单行代码一致,故省略
val columnsSize = 2
for (item in 1..bTypeCount) {
//由于第一个位置和后续的位置的spanSize不一样,故需要特殊处理
if (preInsertBTypePosition == -1) {
preInsertBTypePosition = 2 //第3个位置,故需要写成2
} else {
//这段代码的含义是:上次插入的位置+间隔行数×单行的列数
preInsertBTypePosition += spanSize * columnsSize
}
...
}
展示的效果,如下图:
细心的同学,会发现展示的列表中出现空缺的问题。那为什么会出现这个问题那?
首先,集合A中按照上面的算法插入数据集合为:
第1条跨行数据类型位置有误,请大家忽略,但是后面的位置计算是对的
所以展示的数据效果为:
由此看出,我们这种位置计算方式不对。那如果从后面的非跨行的数据集合中,在拿一条放在前面不就可以填充满了吗?
所以改进了插入数据的算法。
代码如下:
...和单行代码一致,故省略
val columnsSize = 2
for (item in 1..bTypeCount) {
//由于第一个位置和后续的位置的spanSize不一样,故需要特殊处理
if (preInsertBTypePosition == -1) {
preInsertBTypePosition = 2 //第3个位置,故需要写成2
} else {
//这段代码的含义是:上次插入的位置+间隔行数×单行的列数
preInsertBTypePosition += spanSize * columnsSize
//这段代码的含义是:上次插入的位置+(上次插入位置+(已插入的广告数量)×(列数-1))%列数
preInsertBTypePosition += (preInsertBTypePosition + ((item - 1) * (columnsSize - 1))) % columnsSize
}
...
}
这样即可正常展示。 这时,有的同学对这段代码有疑问了
preInsertBTypePosition += (preInsertBTypePosition + ((item - 1) * (columnsSize - 1))) % columnsSize
它的含义是:通过spanSize * columnsSize计算所得值,赋值到变量preInsertBTypePosition,括号加上preInsertBTypePosition再加上item-1,其中item是已插入广告的数量,至于它为什么会减一,是因为item算上这次要插入的广告,故需要减一;然后在乘以columnsSize - 1,其中columnsSize是列数,至于为什么会减一,是因为广告本身也算上item,所以需要减一;然后在通过列数取余,获取需要往前提未跨行数据的数量。这块可能比较绕,扔一个二向箔给你,自己去领悟。
如果对于三列的话,还是按照双列的代码模式写。
恭喜你,答对了。
下面附上完整的代码:
class MainActivity2 : AppCompatActivity() {
private companion object {
private const val SPAN_COUNT = 3
private const val INTERVAL_NATIVE_AD = 5
private const val DEBUG: Boolean = true
private val TAG: String? = if (DEBUG) MainActivity2::class.java.name else null
private lateinit var layoutManager: GridLayoutManager
private val myAdapter = MyAdapter()
private var mInsertNativeAdCount = 0
private var mInsertNativeAdPosition = 0
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main1)
layoutManager = GridLayoutManager(this, SPAN_COUNT)
rv_listView.layoutManager = layoutManager
rv_listView.adapter = myAdapter
layoutManager.spanSizeLookup = MySpanSizeLookup()
button4.setOnClickListener {
mInsertNativeAdPosition = getNativeAdPositionAutoDown()
myAdapter.addNativeAdToListView(mInsertNativeAdPosition, NativeAdInfo("gaozhongkui"))
mInsertNativeAdCount++
}
}
private fun getNativeAdPositionDown(): Int {
var position =
mInsertNativeAdPosition + INTERVAL_NATIVE_AD * SPAN_COUNT// mInsertNativeAdCount * INTERVAL_NATIVE_AD * SPAN_COUNT
if (mInsertNativeAdCount >= 1) {
position += (position + ((mInsertNativeAdCount) * (SPAN_COUNT - 1))) % SPAN_COUNT
}
return position
}
private fun getNativeAdPositionAutoDown(): Int {
var position = getNativeAdPositionDown()
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
if (firstVisibleItemPosition > position) {
position =
firstVisibleItemPosition + (firstVisibleItemPosition + ((mInsertNativeAdCount) * (SPAN_COUNT - 1))) % SPAN_COUNT
}
return position
}
inner class MySpanSizeLookup : SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return if (myAdapter.isFullRow(position)) {
SPAN_COUNT
} else {
1
}
}
}
}
~~