mirror of
https://github.com/AdrianKuta/Expandable-RecyclerView.git
synced 2025-07-01 14:58:02 +02:00
Compare commits
10 Commits
v0.0.1-bet
...
v1.0.0
Author | SHA1 | Date | |
---|---|---|---|
210d96c616 | |||
d9b1bab168 | |||
d003b586dc | |||
60eb7f98e0 | |||
007f8f52a3 | |||
7494ec14da | |||
3f0e1afa54 | |||
12e70f0a6a | |||
b3850a542d | |||
e019238c2c |
101
README.md
101
README.md
@ -4,8 +4,103 @@
|
|||||||
[](https://github.com/AdrianKuta/Expandable-RecyclerView/blob/master/LICENSE)
|
[](https://github.com/AdrianKuta/Expandable-RecyclerView/blob/master/LICENSE)
|
||||||
[](https://circleci.com/gh/AdrianKuta/Expandable-RecyclerView)
|
[](https://circleci.com/gh/AdrianKuta/Expandable-RecyclerView)
|
||||||
|
|
||||||
Library is currently during implementation! It is **not** ready to use yet :/
|
With this adapter you can add expand feature to regular RecyclerView.
|
||||||
|
|
||||||
Final version will be released soon.
|
All objects are store in [Tree (Data structure)](https://github.com/AdrianKuta/Tree-Data-Structure), so adapter can create multilevel expandable groups.
|
||||||
|
|
||||||

|
Under the hood, the tree is flattened to simple list, so from RecyclerView's point of view it can operate as usual.
|
||||||
|
|
||||||
|
|
||||||
|
<img src="https://github.com/AdrianKuta/Expandable-RecyclerView/blob/master/Demo.gif" width="400" />
|
||||||
|
|
||||||
|
## Download
|
||||||
|
|
||||||
|
implementation "com.github.adriankuta:expandable-recyclerView:$latest_versions"
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
To use expandable adapter we just have to expand our Adapter class with `ExpandableRecyclerViewAdapter<T, VH>`,
|
||||||
|
where `T` is data type on which adapter will operate, `VH` is ViewHolder type.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class ExpandableAdapter: ExpandableRecyclerViewAdapter<String, RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
|
|
||||||
|
override fun getTreeNodes(): ExpandableTreeNode<String> {
|
||||||
|
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(
|
||||||
|
holder: RecyclerView.ViewHolder,
|
||||||
|
treeNode: ExpandableTreeNode<String>,
|
||||||
|
nestLevel: Int
|
||||||
|
) {
|
||||||
|
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, nestLevel: Int): RecyclerView.ViewHolder {
|
||||||
|
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`ExpandableRecyclerViewAdapter` has build-in methods to control expandable groups:
|
||||||
|
```kotlin
|
||||||
|
fun toggleGroup(expandableTreeNode: ExpandableTreeNode<T>)
|
||||||
|
|
||||||
|
fun expand(expandableTreeNode: ExpandableTreeNode<T>)
|
||||||
|
|
||||||
|
fun collapse(expandableTreeNode: ExpandableTreeNode<T>)
|
||||||
|
```
|
||||||
|
|
||||||
|
Information if group is expanded or not is stored inside `ExpandableTreeNode` class
|
||||||
|
|
||||||
|
|
||||||
|
### Creating tree
|
||||||
|
|
||||||
|
There are different ways to create tree. The easiest way is to use extensions methods prepared specially for kotlin:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val tree =
|
||||||
|
expandableTree("World") {
|
||||||
|
child("North America") {
|
||||||
|
child("USA")
|
||||||
|
}
|
||||||
|
child("Europe") {
|
||||||
|
child("Poland") {
|
||||||
|
child("Warsaw")
|
||||||
|
}
|
||||||
|
child("Germany")
|
||||||
|
}
|
||||||
|
child("Asia") {
|
||||||
|
child("China")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
But in case when you want create tree at runtime, you can use `ExpandableTreeNode<T>` class to build tree:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val root = ExpandableTreeNode("World")
|
||||||
|
val northA = ExpandableTreeNode("North America")
|
||||||
|
val europe = ExpandableTreeNode("Europe")
|
||||||
|
val asia = ExpandableTreeNode("Asia")
|
||||||
|
|
||||||
|
root.addChild(northA)
|
||||||
|
root.addChild(europe)
|
||||||
|
root.addChild(asia)
|
||||||
|
|
||||||
|
val poland = ExpandableTreeNode("Poland")
|
||||||
|
europe.addChild(poland)
|
||||||
|
// etc.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sample
|
||||||
|
|
||||||
|
Full example of this library is available in app module.
|
||||||
|
|
||||||
|
## Contribution
|
||||||
|
|
||||||
|
Project is still during development and improvements.
|
||||||
|
Issues with BUGs or suggestions are welcome.
|
||||||
|
|
||||||
|
Also please feel free to fork library and contribute to project! ;-)
|
||||||
|
@ -21,6 +21,9 @@ android {
|
|||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
dataBinding {
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -29,6 +32,7 @@ dependencies {
|
|||||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
implementation 'androidx.core:core-ktx:1.1.0'
|
implementation 'androidx.core:core-ktx:1.1.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
|
implementation 'com.github.adriankuta:expandable-recyclerView:0.0.1-beta02'
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
|
97
app/src/main/java/com/github/adriankuta/ExpandableAdapter.kt
Normal file
97
app/src/main/java/com/github/adriankuta/ExpandableAdapter.kt
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package com.github.adriankuta
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.github.adriankuta.databinding.ItemLevel1Binding
|
||||||
|
import com.github.adriankuta.databinding.ItemLevel2Binding
|
||||||
|
import com.github.adriankuta.databinding.ItemLevel3Binding
|
||||||
|
import com.github.adriankuta.expandable_recyclerview.ExpandableTreeNode
|
||||||
|
import com.github.adriankuta.expandable_recyclerview.MultilevelRecyclerViewAdapter
|
||||||
|
import com.github.adriankuta.expandable_recyclerview.expandableTree
|
||||||
|
|
||||||
|
class ExpandableAdapter :
|
||||||
|
MultilevelRecyclerViewAdapter<String, ExpandableAdapter.ExpandableViewHolder>() {
|
||||||
|
|
||||||
|
private var tree: ExpandableTreeNode<String>? = null
|
||||||
|
|
||||||
|
fun setTree(tree: ExpandableTreeNode<String>) {
|
||||||
|
this.tree = tree
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTreeNodes(): ExpandableTreeNode<String> = tree ?: expandableTree("") {}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, nestLevel: Int): ExpandableViewHolder {
|
||||||
|
return when (nestLevel) {
|
||||||
|
1 -> ExpandableViewHolder.Level1(parent.inflateLevel1())
|
||||||
|
2 -> ExpandableViewHolder.Level2(parent.inflateLevel2())
|
||||||
|
3 -> ExpandableViewHolder.Level3(parent.inflateLevel3())
|
||||||
|
else -> throw IllegalArgumentException("Not implemented ViewHolder for nest level: $nestLevel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(
|
||||||
|
holder: ExpandableViewHolder,
|
||||||
|
treeNode: ExpandableTreeNode<String>,
|
||||||
|
nestLevel: Int
|
||||||
|
) {
|
||||||
|
holder.bind(treeNode) {
|
||||||
|
toggleGroup(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class ExpandableViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
|
||||||
|
class Level1(private val binding: ItemLevel1Binding) : ExpandableViewHolder(binding.root) {
|
||||||
|
override fun bind(
|
||||||
|
node: ExpandableTreeNode<String>,
|
||||||
|
onClickListener: ((ExpandableTreeNode<String>) -> Unit)?
|
||||||
|
) {
|
||||||
|
binding.node = node
|
||||||
|
binding.root.setOnClickListener { onClickListener?.invoke(node) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Level2(private val binding: ItemLevel2Binding) : ExpandableViewHolder(binding.root) {
|
||||||
|
override fun bind(
|
||||||
|
node: ExpandableTreeNode<String>,
|
||||||
|
onClickListener: ((ExpandableTreeNode<String>) -> Unit)?
|
||||||
|
) {
|
||||||
|
binding.node = node
|
||||||
|
binding.root.setOnClickListener { onClickListener?.invoke(node) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Level3(private val binding: ItemLevel3Binding) : ExpandableViewHolder(binding.root) {
|
||||||
|
override fun bind(
|
||||||
|
node: ExpandableTreeNode<String>,
|
||||||
|
onClickListener: ((ExpandableTreeNode<String>) -> Unit)?
|
||||||
|
) {
|
||||||
|
binding.node = node
|
||||||
|
binding.root.setOnClickListener { onClickListener?.invoke(node) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
abstract fun bind(
|
||||||
|
node: ExpandableTreeNode<String>,
|
||||||
|
onClickListener: ((ExpandableTreeNode<String>) -> Unit)? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ViewGroup.inflateLevel1(): ItemLevel1Binding {
|
||||||
|
return ItemLevel1Binding.inflate(LayoutInflater.from(context), this, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ViewGroup.inflateLevel2(): ItemLevel2Binding {
|
||||||
|
return ItemLevel2Binding.inflate(LayoutInflater.from(context), this, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ViewGroup.inflateLevel3(): ItemLevel3Binding {
|
||||||
|
return ItemLevel3Binding.inflate(LayoutInflater.from(context), this, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,12 +1,38 @@
|
|||||||
package com.github.adriankuta
|
package com.github.adriankuta
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.github.adriankuta.expandable_recyclerview.expandableTree
|
||||||
|
import kotlinx.android.synthetic.main.activity_main.*
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
|
|
||||||
|
|
||||||
|
val tree = expandableTree("World") {
|
||||||
|
child("North America") {
|
||||||
|
child("USA")
|
||||||
|
}
|
||||||
|
child("Europe") {
|
||||||
|
child("Poland") {
|
||||||
|
child("Warsaw")
|
||||||
|
}
|
||||||
|
child("Germany")
|
||||||
|
}
|
||||||
|
child("Asia") {
|
||||||
|
child("China")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val adapter = ExpandableAdapter()
|
||||||
|
recyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
|
||||||
|
recyclerView.adapter = adapter
|
||||||
|
|
||||||
|
adapter.setTree(tree)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
9
app/src/main/res/drawable/ic_expand_less_black_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_expand_less_black_24dp.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M12,8l-6,6 1.41,1.41L12,10.83l4.59,4.58L18,14z"/>
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_expand_more_black_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_expand_more_black_24dp.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/>
|
||||||
|
</vector>
|
18
app/src/main/res/drawable/ic_last_element.xml
Normal file
18
app/src/main/res/drawable/ic_last_element.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:pathData="M12,0 l0,13z"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:strokeColor="@android:color/black" />
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:pathData="M12.5,12 l12,0"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:strokeColor="@android:color/black" />
|
||||||
|
|
||||||
|
</vector>
|
18
app/src/main/res/drawable/ic_middle_element.xml
Normal file
18
app/src/main/res/drawable/ic_middle_element.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:pathData="M12,0 l0,24z"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:strokeColor="@android:color/black" />
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:pathData="M13,12 l11,0"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:strokeColor="@android:color/black" />
|
||||||
|
|
||||||
|
</vector>
|
13
app/src/main/res/drawable/ic_vertical_line.xml
Normal file
13
app/src/main/res/drawable/ic_vertical_line.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:pathData="M12,0 l0,24"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:strokeColor="@android:color/black" />
|
||||||
|
|
||||||
|
</vector>
|
@ -6,13 +6,13 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".MainActivity">
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
<TextView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:text="Hello World!"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
43
app/src/main/res/layout/item_level_1.xml
Normal file
43
app/src/main/res/layout/item_level_1.xml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout>
|
||||||
|
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="node"
|
||||||
|
type="com.github.adriankuta.expandable_recyclerview.ExpandableTreeNode<String>" />
|
||||||
|
|
||||||
|
<import type="android.view.View" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout 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"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/expand_icon"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
android:src="@{node.expanded ? @drawable/ic_expand_more_black_24dp : @drawable/ic_expand_less_black_24dp}"
|
||||||
|
android:visibility="@{node.children.empty ? View.GONE : View.VISIBLE}"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/textView"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/textView"
|
||||||
|
tools:src="@drawable/ic_expand_less_black_24dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="@{node.value}"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/expand_icon"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="@tools:sample/full_names" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</layout>
|
44
app/src/main/res/layout/item_level_2.xml
Normal file
44
app/src/main/res/layout/item_level_2.xml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout>
|
||||||
|
|
||||||
|
<data>
|
||||||
|
<variable
|
||||||
|
name="node"
|
||||||
|
type="com.github.adriankuta.expandable_recyclerview.ExpandableTreeNode<String>" />
|
||||||
|
|
||||||
|
<import type="android.view.View" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout 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"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/expand_icon"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
android:src="@{node.expanded ? @drawable/ic_expand_more_black_24dp : @drawable/ic_expand_less_black_24dp}"
|
||||||
|
android:visibility="@{node.children.empty ? View.GONE : View.VISIBLE}"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/textView"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/textView"
|
||||||
|
tools:src="@drawable/ic_expand_less_black_24dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="@{node.value}"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/expand_icon"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_goneMarginStart="48dp"
|
||||||
|
tools:text="@tools:sample/full_names" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</layout>
|
44
app/src/main/res/layout/item_level_3.xml
Normal file
44
app/src/main/res/layout/item_level_3.xml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout>
|
||||||
|
|
||||||
|
<data>
|
||||||
|
<variable
|
||||||
|
name="node"
|
||||||
|
type="com.github.adriankuta.expandable_recyclerview.ExpandableTreeNode<String>" />
|
||||||
|
|
||||||
|
<import type="android.view.View" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout 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"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/expand_icon"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginStart="48dp"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
android:src="@{node.expanded ? @drawable/ic_expand_more_black_24dp : @drawable/ic_expand_less_black_24dp}"
|
||||||
|
android:visibility="@{node.children.empty ? View.GONE : View.VISIBLE}"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/textView"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/textView"
|
||||||
|
tools:src="@drawable/ic_expand_less_black_24dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp"
|
||||||
|
app:layout_goneMarginStart="96dp"
|
||||||
|
android:text="@{node.value}"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/expand_icon"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="@tools:sample/full_names" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</layout>
|
@ -15,7 +15,7 @@ android {
|
|||||||
minSdkVersion 23
|
minSdkVersion 23
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "0.0.1-beta01"
|
versionName "1.0.0"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
consumerProguardFiles 'consumer-rules.pro'
|
consumerProguardFiles 'consumer-rules.pro'
|
||||||
@ -35,7 +35,7 @@ dependencies {
|
|||||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
implementation 'androidx.core:core-ktx:1.1.0'
|
implementation 'androidx.core:core-ktx:1.1.0'
|
||||||
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
||||||
implementation "com.github.adriankuta:tree-structure:1.2.0"
|
implementation "com.github.adriankuta:tree-structure:1.2.3"
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||||
|
@ -3,7 +3,7 @@ package com.github.adriankuta.expandable_recyclerview
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
abstract class MultilevelRecyclerViewAdapter<T, VH : RecyclerView.ViewHolder> :
|
abstract class ExpandableRecyclerViewAdapter<T, VH : RecyclerView.ViewHolder> :
|
||||||
RecyclerView.Adapter<VH>() {
|
RecyclerView.Adapter<VH>() {
|
||||||
|
|
||||||
private lateinit var treeNodes: ExpandableTreeNode<T>
|
private lateinit var treeNodes: ExpandableTreeNode<T>
|
@ -7,11 +7,12 @@ class ExpandableTreeNode<T>(value: T) : TreeNode<T>(value) {
|
|||||||
|
|
||||||
var expanded: Boolean = true
|
var expanded: Boolean = true
|
||||||
|
|
||||||
override fun child(child: T, childDeclaration: ChildDeclaration<T>?) {
|
override fun child(value: T, childDeclaration: ChildDeclaration<T>?) : ExpandableTreeNode<T> {
|
||||||
val newChild = ExpandableTreeNode(child)
|
val newChild = ExpandableTreeNode(value)
|
||||||
if (childDeclaration != null)
|
if (childDeclaration != null)
|
||||||
newChild.childDeclaration()
|
newChild.childDeclaration()
|
||||||
addChild(newChild)
|
addChild(newChild)
|
||||||
|
return newChild
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,7 +54,7 @@ class ExpandableTreeNode<T>(value: T) : TreeNode<T>(value) {
|
|||||||
/**
|
/**
|
||||||
* @return List of nodes which parent or higher ancestor aren't collapsed.
|
* @return List of nodes which parent or higher ancestor aren't collapsed.
|
||||||
*/
|
*/
|
||||||
private fun onlyVisibleItems(): List<ExpandableTreeNode<T>> {
|
fun onlyVisibleItems(): List<ExpandableTreeNode<T>> {
|
||||||
//Visible if parent of node is expanded.
|
//Visible if parent of node is expanded.
|
||||||
return map { it as ExpandableTreeNode }
|
return map { it as ExpandableTreeNode }
|
||||||
.filter { allAncestorsAreExpanded(it) }
|
.filter { allAncestorsAreExpanded(it) }
|
||||||
@ -64,7 +65,7 @@ class ExpandableTreeNode<T>(value: T) : TreeNode<T>(value) {
|
|||||||
* @return `True` if parent, and all parent's ancestors are expanded.
|
* @return `True` if parent, and all parent's ancestors are expanded.
|
||||||
*/
|
*/
|
||||||
private fun allAncestorsAreExpanded(node: ExpandableTreeNode<T>): Boolean {
|
private fun allAncestorsAreExpanded(node: ExpandableTreeNode<T>): Boolean {
|
||||||
var ancestor = parent as? ExpandableTreeNode
|
var ancestor = node.parent as? ExpandableTreeNode
|
||||||
var ancestorsAreExpanded = isAncestorExpanded(ancestor)
|
var ancestorsAreExpanded = isAncestorExpanded(ancestor)
|
||||||
|
|
||||||
while (ancestorsAreExpanded && ancestor != null) {
|
while (ancestorsAreExpanded && ancestor != null) {
|
||||||
|
@ -6,7 +6,7 @@ import com.github.adriankuta.datastructure.tree.ChildDeclaration
|
|||||||
@JvmSynthetic
|
@JvmSynthetic
|
||||||
inline fun <reified T> expandableTree(
|
inline fun <reified T> expandableTree(
|
||||||
value: T,
|
value: T,
|
||||||
childDeclaration: ChildDeclaration<T>
|
childDeclaration: ChildDeclaration<T> = {}
|
||||||
): ExpandableTreeNode<T> {
|
): ExpandableTreeNode<T> {
|
||||||
val treeNode = ExpandableTreeNode(value)
|
val treeNode = ExpandableTreeNode(value)
|
||||||
treeNode.childDeclaration()
|
treeNode.childDeclaration()
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
package com.github.adriankuta.expandable_recyclerview
|
||||||
|
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class ExpandableTreeNodeTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getVisibleNode() {
|
||||||
|
//given
|
||||||
|
val root = expandableTree("Root") {
|
||||||
|
child("Level 1") {
|
||||||
|
child("Level 2") {
|
||||||
|
child("Level 3") {
|
||||||
|
child("Level 4")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root.expanded = false
|
||||||
|
|
||||||
|
Assert.assertEquals("Root", root.onlyVisibleItems().first().value)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user