/
ConcatExpandableListAdapter.kt
363 lines (324 loc) · 14.5 KB
/
ConcatExpandableListAdapter.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
/*
* Copyright 2021 panpf <panpfpanpf@outlook.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.panpf.assemblyadapter.list.expandable
import android.view.View
import android.view.ViewGroup
import android.widget.BaseExpandableListAdapter
import com.github.panpf.assemblyadapter.list.expandable.ConcatExpandableListAdapter.Config
import com.github.panpf.assemblyadapter.list.expandable.ConcatExpandableListAdapter.Config.StableIdMode
import com.github.panpf.assemblyadapter.list.expandable.internal.ConcatExpandableListAdapterController
import java.util.Collections
/**
* An [BaseExpandableListAdapter] implementation that presents the contents of multiple adapters in sequence.
*
* @param config The configuration for this ConcatExpandableListAdapter
* @param adapters The list of adapters to add
* @see Config.Builder
*/
open class ConcatExpandableListAdapter(
config: Config, adapters: List<BaseExpandableListAdapter>
) : BaseExpandableListAdapter() {
companion object {
const val NO_ID: Long = -1
const val TAG = "ConcatExpandableListAdapter"
}
/**
* Bulk of the logic is in the controller to keep this class isolated to the public API.
*/
private val mController: ConcatExpandableListAdapterController =
ConcatExpandableListAdapterController(this, config, adapters)
/**
* Returns an unmodifiable copy of the list of adapters in this [ConcatExpandableListAdapter].
* Note that this is a copy hence future changes in the ConcatExpandableListAdapter are not reflected in
* this list.
*
* @return A copy of the list of adapters in this ConcatExpandableListAdapter.
*/
val adapters: List<BaseExpandableListAdapter>
get() = Collections.unmodifiableList(mController.copyOfAdapters)
/**
* Creates a ConcatExpandableListAdapter with [Config.DEFAULT] and the given adapters in the given
* order.
*
* @param adapters The list of adapters to add
*/
constructor(adapters: List<BaseExpandableListAdapter>) : this(Config.DEFAULT, adapters)
/**
* Creates a ConcatExpandableListAdapter with the given config and the given adapters in the given order.
*
* @param config The configuration for this ConcatExpandableListAdapter
* @param adapters The list of adapters to add
* @see Config.Builder
*/
constructor(config: Config, vararg adapters: BaseExpandableListAdapter) : this(
config,
adapters.toList()
)
/**
* Creates a ConcatExpandableListAdapter with [Config.DEFAULT] and the given adapters in the given
* order.
*
* @param adapters The list of adapters to add
*/
constructor(vararg adapters: BaseExpandableListAdapter) : this(
Config.DEFAULT,
adapters.toList()
)
/**
* Appends the given adapter to the existing list of adapters and notifies the observers of
* this [ConcatExpandableListAdapter].
*
* @param adapter The new adapter to add
* @return `true` if the adapter is successfully added because it did not already exist,
* `false` otherwise.
* @see .addAdapter
* @see .removeAdapter
*/
open fun addAdapter(adapter: BaseExpandableListAdapter): Boolean {
return mController.addAdapter(adapter)
}
/**
* Adds the given adapter to the given index among other adapters that are already added.
*
* @param index The index into which to insert the adapter. ConcatExpandableListAdapter will throw an
* [IndexOutOfBoundsException] if the index is not between 0 and current
* adapter count (inclusive).
* @param adapter The new adapter to add to the adapters list.
* @return `true` if the adapter is successfully added because it did not already exist,
* `false` otherwise.
* @see .addAdapter
* @see .removeAdapter
*/
open fun addAdapter(index: Int, adapter: BaseExpandableListAdapter): Boolean {
return mController.addAdapter(index, adapter)
}
/**
* Removes the given adapter from the adapters list if it exists
*
* @param adapter The adapter to remove
* @return `true` if the adapter was previously added to this `ConcatExpandableListAdapter` and
* now removed or `false` if it couldn't be found.
*/
open fun removeAdapter(adapter: BaseExpandableListAdapter): Boolean {
return mController.removeAdapter(adapter)
}
val itemCount: Int
get() = mController.groupCount
fun getItemData(position: Int): Any? {
return mController.getGroup(position)
}
override fun getGroupCount(): Int {
return mController.groupCount
}
override fun getGroup(groupPosition: Int): Any? {
return mController.getGroup(groupPosition)
}
override fun getGroupId(groupPosition: Int): Long {
return mController.getGroupId(groupPosition)
}
override fun getGroupTypeCount(): Int {
return mController.groupTypeCount
}
override fun getGroupType(groupPosition: Int): Int {
return mController.getGroupType(groupPosition)
}
override fun getGroupView(
groupPosition: Int, isExpanded: Boolean, convertView: View?, parent: ViewGroup
): View {
return mController.getGroupView(groupPosition, isExpanded, convertView, parent)
}
override fun getChildrenCount(groupPosition: Int): Int {
return mController.getChildrenCount(groupPosition)
}
override fun getChild(groupPosition: Int, childPosition: Int): Any? {
return mController.getChild(groupPosition, childPosition)
}
override fun getChildId(groupPosition: Int, childPosition: Int): Long {
return mController.getChildId(groupPosition, childPosition)
}
override fun getChildTypeCount(): Int {
return mController.childTypeCount
}
override fun getChildType(groupPosition: Int, childPosition: Int): Int {
return mController.getChildType(groupPosition, childPosition)
}
override fun getChildView(
groupPosition: Int, childPosition: Int, isLastChild: Boolean,
convertView: View?, parent: ViewGroup
): View {
return mController.getChildView(
groupPosition, childPosition, isLastChild, convertView, parent
)
}
override fun hasStableIds(): Boolean {
return mController.hasStableIds()
}
override fun isChildSelectable(groupPosition: Int, childPosition: Int): Boolean {
return mController.isChildSelectable(groupPosition, childPosition)
}
override fun onGroupCollapsed(groupPosition: Int) {
mController.onGroupCollapsed(groupPosition)
}
override fun onGroupExpanded(groupPosition: Int) {
mController.onGroupExpanded(groupPosition)
}
open fun findLocalAdapterAndPosition(position: Int): Pair<BaseExpandableListAdapter, Int> {
return mController.findLocalAdapterAndPosition(position)
}
/**
* The configuration object for a [ConcatExpandableListAdapter].
*/
class Config internal constructor(
/**
* If `false`, [ConcatExpandableListAdapter] assumes all assigned adapters share a global
* view type pool such that they use the same view types to refer to the same convertView.
*
*
* Setting this to `false` will allow nested adapters to share convertViews but
* it also means these adapters should not have conflicting view types
* ([BaseExpandableListAdapter.getGroupType] or [BaseExpandableListAdapter.getChildType]) such that two different adapters return the same
* view type for different convertViews.
*
*
* By default, it is set to `true` which means [ConcatExpandableListAdapter] will isolate
* view types across adapters, preventing them from using the same convertViews.
*/
val isolateViewTypes: Boolean,
/**
* Defines whether the [ConcatExpandableListAdapter] should support stable ids or not
* ([BaseExpandableListAdapter.hasStableIds].
*
*
* There are 3 possible options:
*
*
* [StableIdMode.NO_STABLE_IDS]: In this mode, [ConcatExpandableListAdapter] ignores the
* stable
* ids reported by sub adapters. This is the default mode.
*
*
* [StableIdMode.ISOLATED_STABLE_IDS]: In this mode, [ConcatExpandableListAdapter] will return
* `true` from [ConcatExpandableListAdapter.hasStableIds] and will **require** all added
* [BaseExpandableListAdapter]s to have stable ids. As two different adapters may return same stable ids
* because they are unaware of each-other, [ConcatExpandableListAdapter] will isolate each
* [BaseExpandableListAdapter]'s id pool from each other such that it will overwrite the reported stable
* id before reporting back to the [android.widget.ExpandableListView].
*
*
* [StableIdMode.SHARED_STABLE_IDS]: In this mode, [ConcatExpandableListAdapter] will return
* `true` from [ConcatExpandableListAdapter.hasStableIds] and will **require** all added
* [BaseExpandableListAdapter]s to have stable ids. Unlike [StableIdMode.ISOLATED_STABLE_IDS],
* [ConcatExpandableListAdapter] will not override the returned item ids. In this mode,
* child [BaseExpandableListAdapter]s must be aware of each-other and never return the same id unless
* an item is moved between [BaseExpandableListAdapter]s.
*
*
* Default value is [StableIdMode.NO_STABLE_IDS].
*/
val stableIdMode: StableIdMode
) {
companion object {
/**
* Default configuration for [ConcatExpandableListAdapter] where [isolateViewTypes]
* is set to `true` and [stableIdMode] is set to
* [StableIdMode.NO_STABLE_IDS].
*/
val DEFAULT = Config(true, StableIdMode.NO_STABLE_IDS)
}
/**
* Defines how [ConcatExpandableListAdapter] handle stable ids ([BaseExpandableListAdapter.hasStableIds]).
*/
enum class StableIdMode {
/**
* In this mode, [ConcatExpandableListAdapter] ignores the stable
* ids reported by sub adapters. This is the default mode.
* Adding an [BaseExpandableListAdapter] with stable ids will result in a warning as it will be
* ignored.
*/
NO_STABLE_IDS,
/**
* In this mode, [ConcatExpandableListAdapter] will return `true` from
* [ConcatExpandableListAdapter.hasStableIds] and will **require** all added
* [BaseExpandableListAdapter]s to have stable ids. As two different adapters may return
* same stable ids because they are unaware of each-other, [ConcatExpandableListAdapter] will
* isolate each [BaseExpandableListAdapter]'s id pool from each other such that it will overwrite
* the reported stable id before reporting back to the [android.widget.ExpandableListView]. In this
* mode, the value returned from ([BaseExpandableListAdapter.getGroupId] or [BaseExpandableListAdapter.getChildId]) might differ from the
* value returned from ([BaseExpandableListAdapter.getGroupId] or [BaseExpandableListAdapter.getChildId]).
*
*
* Adding an adapter without stable ids will result in an
* [IllegalArgumentException].
*/
ISOLATED_STABLE_IDS,
/**
* In this mode, [ConcatExpandableListAdapter] will return `true` from
* [ConcatExpandableListAdapter.hasStableIds] and will **require** all added
* [BaseExpandableListAdapter]s to have stable ids. Unlike [StableIdMode.ISOLATED_STABLE_IDS],
* [ConcatExpandableListAdapter] will not override the returned item ids. In this mode,
* child [BaseExpandableListAdapter]s must be aware of each-other and never return the same id
* unless and item is moved between [BaseExpandableListAdapter]s.
* Adding an adapter without stable ids will result in an
* [IllegalArgumentException].
*/
SHARED_STABLE_IDS
}
/**
* The builder for [Config] class.
*/
class Builder {
private var mIsolateViewTypes = DEFAULT.isolateViewTypes
private var mStableIdMode = DEFAULT.stableIdMode
/**
* Sets whether [ConcatExpandableListAdapter] should isolate view types of nested adapters from
* each other.
*
* @param isolateViewTypes `true` if [ConcatExpandableListAdapter] should override view
* types of nested adapters to avoid view type
* conflicts, `false` otherwise.
* Defaults to [Config.DEFAULT]'s
* [isolateViewTypes] value (`true`).
* @return this
* @see isolateViewTypes
*/
fun setIsolateViewTypes(isolateViewTypes: Boolean): Builder {
mIsolateViewTypes = isolateViewTypes
return this
}
/**
* Sets how the [ConcatExpandableListAdapter] should handle stable ids
* ([BaseExpandableListAdapter.hasStableIds]). See documentation in [stableIdMode]
* for details.
*
* @param stableIdMode The stable id mode for the [ConcatExpandableListAdapter]. Defaults to
* [Config.DEFAULT]'s [stableIdMode] value
* ([StableIdMode.NO_STABLE_IDS]).
* @return this
* @see stableIdMode
*/
fun setStableIdMode(stableIdMode: StableIdMode): Builder {
mStableIdMode = stableIdMode
return this
}
/**
* @return A new instance of [Config] with the given parameters.
*/
fun build(): Config {
return Config(mIsolateViewTypes, mStableIdMode)
}
}
}
}