├── .gitignore ├── CONTRIBUTING.md ├── ExpandableListView.gif ├── ExpandableListView ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── simform │ │ └── expandablelistview │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── simform │ │ │ └── expandablelistview │ │ │ ├── ExpandableListData.kt │ │ │ ├── ExpandableListView.kt │ │ │ └── StylingAttributes.kt │ └── res │ │ ├── drawable │ │ ├── ic_arrow_drop_down.xml │ │ ├── ic_arrow_drop_up.xml │ │ ├── ic_arrow_right.xml │ │ ├── ic_audiotrack.xml │ │ └── ic_check.xml │ │ └── values │ │ ├── dimens.xml │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── simform │ └── expandablelistview │ └── ExampleUnitTest.kt ├── ExpandableListViewBanner.png ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── app │ │ └── sscomposeexpandablelistview │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── app │ │ │ └── sscomposeexpandablelistview │ │ │ └── ui │ │ │ ├── MainActivity.kt │ │ │ ├── MainUiState.kt │ │ │ ├── MainViewModel.kt │ │ │ └── theme │ │ │ ├── Color.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ └── res │ │ ├── drawable │ │ ├── ic_arrow_drop_down.xml │ │ ├── ic_arrow_drop_up.xml │ │ ├── ic_fruits.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_milk_products.xml │ │ └── ic_vegitable.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── com │ └── app │ └── sscomposeexpandablelistview │ └── ExampleUnitTest.kt ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | replay_pid* 25 | 26 | *.iml 27 | .gradle 28 | /local.properties 29 | /.idea/caches 30 | /.idea/libraries 31 | /.idea/modules.xml 32 | /.idea/workspace.xml 33 | /.idea/navEditor.xml 34 | /.idea/assetWizardSettings.xml 35 | .DS_Store 36 | /build 37 | /captures 38 | .externalNativeBuild 39 | .cxx 40 | local.properties -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Way to contribute 2 | 3 | 1. Fork the repo and create your branch from `main`. 4 | 2. Clone the project to your own machine. (Please have a look at [**Readme.md**](https://github.com/SimformSolutionsPvtLtd/SSComposeExpandableListView/blob/master/README.md) to understand how to run this project on your machine) 5 | 3. Commit changes to your own branch 6 | 4. Make sure your code lints. 7 | 5. Push your work back up to your fork. 8 | 6. Issue that pull request! -------------------------------------------------------------------------------- /ExpandableListView.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSComposeExpandableListView/be3275cf014e8866e88d35f71fb6f3381adde445/ExpandableListView.gif -------------------------------------------------------------------------------- /ExpandableListView/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /ExpandableListView/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.jetbrains.kotlin.android) 4 | alias(libs.plugins.compose.compiler) 5 | id(libs.versions.mavenpublish.get()) 6 | } 7 | 8 | afterEvaluate { 9 | publishing { 10 | publications { 11 | // Creates a Maven publication called "release". 12 | create(libs.versions.release.get()) { 13 | from(components[libs.versions.release.get()]) 14 | groupId = libs.versions.group.id.get() 15 | artifactId = libs.versions.artifact.id.get() 16 | version = libs.versions.version.get() 17 | } 18 | } 19 | } 20 | } 21 | 22 | android { 23 | namespace = "com.simform.expandablelistview" 24 | compileSdk = 35 25 | 26 | defaultConfig { 27 | minSdk = 24 28 | 29 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 30 | consumerProguardFiles("consumer-rules.pro") 31 | } 32 | 33 | buildTypes { 34 | release { 35 | isMinifyEnabled = false 36 | proguardFiles( 37 | getDefaultProguardFile("proguard-android-optimize.txt"), 38 | "proguard-rules.pro" 39 | ) 40 | } 41 | } 42 | compileOptions { 43 | sourceCompatibility = JavaVersion.VERSION_17 44 | targetCompatibility = JavaVersion.VERSION_17 45 | } 46 | kotlinOptions { 47 | jvmTarget = "17" 48 | } 49 | buildFeatures { 50 | compose = true 51 | } 52 | } 53 | 54 | dependencies { 55 | implementation(libs.androidx.core.ktx) 56 | implementation(libs.androidx.appcompat) 57 | implementation(libs.androidx.activity.compose) 58 | implementation(platform(libs.androidx.compose.bom)) 59 | implementation(libs.androidx.ui) 60 | implementation(libs.androidx.ui.graphics) 61 | implementation(libs.androidx.ui.tooling.preview) 62 | implementation(libs.androidx.material3) 63 | 64 | // Testing libraries 65 | androidTestImplementation(platform(libs.androidx.compose.bom)) 66 | androidTestImplementation(libs.androidx.ui.test.junit4) 67 | debugImplementation(libs.androidx.ui.tooling) 68 | debugImplementation(libs.androidx.ui.test.manifest) 69 | testImplementation(libs.junit) 70 | androidTestImplementation(libs.androidx.junit) 71 | androidTestImplementation(libs.androidx.espresso.core) 72 | } -------------------------------------------------------------------------------- /ExpandableListView/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSComposeExpandableListView/be3275cf014e8866e88d35f71fb6f3381adde445/ExpandableListView/consumer-rules.pro -------------------------------------------------------------------------------- /ExpandableListView/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /ExpandableListView/src/androidTest/java/com/simform/expandablelistview/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.simform.expandablelistview 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.simform.expandablelistview.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /ExpandableListView/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ExpandableListView/src/main/java/com/simform/expandablelistview/ExpandableListData.kt: -------------------------------------------------------------------------------- 1 | package com.simform.expandablelistview 2 | 3 | import androidx.annotation.DrawableRes 4 | import androidx.compose.runtime.Immutable 5 | import androidx.compose.runtime.Stable 6 | import androidx.compose.ui.text.AnnotatedString 7 | 8 | /** 9 | * Class for holding the input data for [ComposeExpandableListView] 10 | */ 11 | @Stable 12 | @Immutable 13 | data class ExpandableListData( 14 | val headerText: String, 15 | val listItems: List, 16 | val isExpanded: Boolean = false, 17 | @DrawableRes val headerCategoryIcon: Int? = null 18 | ) 19 | 20 | /** 21 | * Class for holding list item data [ListItemView] 22 | */ 23 | @Stable 24 | @Immutable 25 | data class ListItemData( 26 | val name: String? = null, 27 | val annotatedText: AnnotatedString? = null, 28 | val isSelected: Boolean = false 29 | ) -------------------------------------------------------------------------------- /ExpandableListView/src/main/java/com/simform/expandablelistview/ExpandableListView.kt: -------------------------------------------------------------------------------- 1 | package com.simform.expandablelistview 2 | 3 | import android.content.res.Configuration 4 | import androidx.annotation.DrawableRes 5 | import androidx.compose.animation.AnimatedVisibility 6 | import androidx.compose.animation.core.tween 7 | import androidx.compose.animation.expandVertically 8 | import androidx.compose.animation.shrinkVertically 9 | import androidx.compose.foundation.ExperimentalFoundationApi 10 | import androidx.compose.foundation.background 11 | import androidx.compose.foundation.clickable 12 | import androidx.compose.foundation.combinedClickable 13 | import androidx.compose.foundation.layout.Column 14 | import androidx.compose.foundation.layout.PaddingValues 15 | import androidx.compose.foundation.layout.Row 16 | import androidx.compose.foundation.layout.Spacer 17 | import androidx.compose.foundation.layout.defaultMinSize 18 | import androidx.compose.foundation.layout.fillMaxWidth 19 | import androidx.compose.foundation.layout.height 20 | import androidx.compose.foundation.layout.padding 21 | import androidx.compose.foundation.lazy.LazyColumn 22 | import androidx.compose.foundation.lazy.LazyListScope 23 | import androidx.compose.foundation.lazy.itemsIndexed 24 | import androidx.compose.foundation.shape.RoundedCornerShape 25 | import androidx.compose.material3.HorizontalDivider 26 | import androidx.compose.material3.Icon 27 | import androidx.compose.material3.MaterialTheme 28 | import androidx.compose.material3.Text 29 | import androidx.compose.runtime.Composable 30 | import androidx.compose.ui.Alignment 31 | import androidx.compose.ui.Modifier 32 | import androidx.compose.ui.draw.clip 33 | import androidx.compose.ui.graphics.Color 34 | import androidx.compose.ui.res.dimensionResource 35 | import androidx.compose.ui.res.painterResource 36 | import androidx.compose.ui.res.stringResource 37 | import androidx.compose.ui.text.AnnotatedString 38 | import androidx.compose.ui.text.TextStyle 39 | import androidx.compose.ui.text.font.FontWeight 40 | import androidx.compose.ui.tooling.preview.Preview 41 | import androidx.compose.ui.unit.dp 42 | import androidx.compose.ui.util.fastForEachIndexed 43 | import com.simform.expandablelistview.ExpandableListViewDefaults.defaultContentAnimation 44 | import com.simform.expandablelistview.ExpandableListViewDefaults.defaultHeaderPaddings 45 | import com.simform.expandablelistview.ExpandableListViewDefaults.defaultHeaderStylingAttributes 46 | import com.simform.expandablelistview.ExpandableListViewDefaults.defaultListContentPadding 47 | import com.simform.expandablelistview.ExpandableListViewDefaults.defaultListItemStylingAttributes 48 | 49 | /** 50 | * Displays an expandable list view with headers and child items. 51 | * Each header can be expanded to reveal items, and users can select them. 52 | * 53 | * @param modifier Modifier for the root container, allowing customization like padding or size. 54 | * @param data List of [ExpandableListData] representing the expandable list's headers and child items. 55 | * @param headerStylingAttributes Defines styling for headers, including appearance and layout. 56 | * @param listItemStylingAttributes Defines styling for list items, including appearance and layout. 57 | * @param contentAnimation Defines expand/collapse animation for expandable list content (default: [defaultContentAnimation]). 58 | * @param expandedIcon Drawable resource ID for the icon when a header is expanded (default: up arrow). 59 | * @param collapseIcon Drawable resource ID for the icon when a header is collapsed (default: down arrow). 60 | * @param itemSelectedIcon Drawable resource ID for the icon when a item is selected (default: check mark). 61 | * @param onStateChanged Callback for when a header's expand/collapse state changes, providing the header index and expanded state. 62 | * @param onListItemClicked Callback for when a list item is clicked, providing the header index, item index, and selection state. 63 | * @param onListItemLongClicked Callback for when a list item is long pressed, providing the header index, item index, and selection state. 64 | */ 65 | @Composable 66 | fun ComposeExpandableListView( 67 | modifier: Modifier = Modifier, 68 | expandableListData: List, 69 | headerStylingAttributes: HeaderStylingAttributes = defaultHeaderStylingAttributes, 70 | listItemStylingAttributes: ListItemStylingAttributes = defaultListItemStylingAttributes, 71 | contentAnimation: ContentAnimation = defaultContentAnimation, 72 | @DrawableRes expandedIcon: Int = R.drawable.ic_arrow_right, 73 | @DrawableRes collapseIcon: Int = R.drawable.ic_arrow_drop_down, 74 | @DrawableRes itemSelectedIcon: Int = R.drawable.ic_check, 75 | onStateChanged: (headerIndex: Int, isExpanded: Boolean) -> Unit = { _, _ -> }, 76 | onListItemClicked: (headerIndex: Int, itemIndex: Int, isSelected: Boolean) -> Unit = { _, _, _ -> }, 77 | onListItemLongClicked: (headerIndex: Int, itemIndex: Int, isSelected: Boolean) -> Unit = { _, _, _ -> }, 78 | ) { 79 | 80 | LazyColumn(modifier = modifier) { 81 | composeExpandableListView( 82 | expandableListData = expandableListData, 83 | headerStylingAttributes = headerStylingAttributes, 84 | listItemStylingAttributes = listItemStylingAttributes, 85 | contentAnimation = contentAnimation, 86 | expandedIcon = expandedIcon, 87 | collapseIcon = collapseIcon, 88 | itemSelectedIcon = itemSelectedIcon, 89 | onStateChanged = onStateChanged, 90 | onListItemClicked = onListItemClicked, 91 | onListItemLongClicked = onListItemLongClicked 92 | ) 93 | } 94 | } 95 | 96 | /** 97 | * Displays an expandable list view with headers and child items. 98 | * Each header can be expanded to reveal items, and users can select them. 99 | * 100 | * @param expandableListData List of [ExpandableListData] representing the expandable list's headers and child items. 101 | * @param headerStylingAttributes Defines styling for headers, including appearance and layout. 102 | * @param listItemStylingAttributes Defines styling for list items, including appearance and layout. 103 | * @param contentAnimation Defines expand/collapse animation for expandable list content (default: [defaultContentAnimation]). 104 | * @param expandedIcon Drawable resource ID for the icon when a header is expanded (default: up arrow). 105 | * @param collapseIcon Drawable resource ID for the icon when a header is collapsed (default: down arrow). 106 | * @param itemSelectedIcon Drawable resource ID for the icon when a item is selected (default: check mark). 107 | * @param onStateChanged Callback for when a header's expand/collapse state changes, providing the header index and expanded state. 108 | * @param onListItemClicked Callback for when a list item is clicked, providing the header index, item index, and selection state. 109 | * @param onListItemLongClicked Callback for when a list item is long pressed, providing the header index, item index, and selection state. 110 | */ 111 | @OptIn(ExperimentalFoundationApi::class) 112 | fun LazyListScope.composeExpandableListView( 113 | expandableListData: List, 114 | headerStylingAttributes: HeaderStylingAttributes? = null, 115 | listItemStylingAttributes: ListItemStylingAttributes? = null, 116 | contentAnimation: ContentAnimation = defaultContentAnimation, 117 | @DrawableRes expandedIcon: Int = R.drawable.ic_arrow_right, 118 | @DrawableRes collapseIcon: Int = R.drawable.ic_arrow_drop_down, 119 | @DrawableRes itemSelectedIcon: Int = R.drawable.ic_check, 120 | onStateChanged: (headerIndex: Int, isExpanded: Boolean) -> Unit = { _, _ -> }, 121 | onListItemClicked: (headerIndex: Int, itemIndex: Int, isSelected: Boolean) -> Unit = { _, _, _ -> }, 122 | onListItemLongClicked: (headerIndex: Int, itemIndex: Int, isSelected: Boolean) -> Unit = { _, _, _ -> }, 123 | ) { 124 | expandableListData.fastForEachIndexed { index, data -> 125 | item(key = data.headerText) { 126 | // If not provide the headerStylingAttributes, we use defaultHeaderStylingAttributes 127 | val headerStyling = headerStylingAttributes ?: defaultHeaderStylingAttributes 128 | 129 | HeaderView( 130 | modifier = Modifier 131 | .fillMaxWidth() 132 | .clip( 133 | RoundedCornerShape( 134 | topStart = headerStyling.cornerRadius, 135 | topEnd = headerStyling.cornerRadius, 136 | bottomStart = if (data.isExpanded) 0.dp else headerStyling.cornerRadius, 137 | bottomEnd = if (data.isExpanded) 0.dp else headerStyling.cornerRadius 138 | ) 139 | ) 140 | .background(color = headerStyling.backgroundColor) 141 | .clickable { onStateChanged(index, !data.isExpanded) }, 142 | text = data.headerText, 143 | textStyle = headerStyling.textStyle, 144 | headerPaddings = headerStyling.headerPaddings, 145 | isExpanded = data.isExpanded, 146 | expandedIcon = expandedIcon, 147 | collapseIcon = collapseIcon, 148 | categoryIcon = data.headerCategoryIcon 149 | ) 150 | } 151 | 152 | 153 | itemsIndexed( 154 | items = data.listItems, 155 | key = { _, item -> 156 | "${data.headerText} - ${item.annotatedText ?: item.name}" 157 | } 158 | ) { itemIndex, itemData -> 159 | // If not provide the listItemStylingAttributes, we use defaultListItemStylingAttributes 160 | val listItemStyling = listItemStylingAttributes ?: defaultListItemStylingAttributes 161 | 162 | AnimatedVisibility( 163 | visible = data.isExpanded, 164 | enter = contentAnimation.expandAnimation, 165 | exit = contentAnimation.collapseAnimation 166 | ) { 167 | Column { 168 | itemData.annotatedText?.let { 169 | ListItemView( 170 | modifier = Modifier 171 | .fillMaxWidth() 172 | .clip( 173 | RoundedCornerShape( 174 | bottomStart = if (itemData == data.listItems.last()) dimensionResource( 175 | id = R.dimen.header_corner_radius 176 | ) else 0.dp, 177 | bottomEnd = if (itemData == data.listItems.last()) dimensionResource( 178 | id = R.dimen.header_corner_radius 179 | ) else 0.dp 180 | ) 181 | ) 182 | .background( 183 | color = if (itemData.isSelected) { 184 | // Set color based on selection state. 185 | listItemStyling.selectedBackgroundColor 186 | } else { 187 | listItemStyling.backgroundColor 188 | } 189 | ) 190 | .combinedClickable( 191 | onClick = { 192 | onListItemClicked(index, itemIndex, !itemData.isSelected) 193 | }, 194 | onLongClick = { 195 | onListItemLongClicked( 196 | index, 197 | itemIndex, 198 | !itemData.isSelected 199 | ) 200 | } 201 | ), 202 | text = it, 203 | textStyle = if (itemData.isSelected) { 204 | // Set text style based on selection state. 205 | listItemStyling.selectedTextStyle 206 | } else { 207 | listItemStyling.textStyle 208 | }, 209 | contentPadding = listItemStyling.contentPadding, 210 | itemSelectedIcon = itemSelectedIcon, 211 | isSelected = itemData.isSelected 212 | ) 213 | } ?: run { 214 | itemData.name?.let { 215 | ListItemView( 216 | modifier = Modifier 217 | .fillMaxWidth() 218 | .clip( 219 | RoundedCornerShape( 220 | bottomStart = if (itemData == data.listItems.last()) dimensionResource( 221 | id = R.dimen.header_corner_radius 222 | ) else 0.dp, 223 | bottomEnd = if (itemData == data.listItems.last()) dimensionResource( 224 | id = R.dimen.header_corner_radius 225 | ) else 0.dp 226 | ) 227 | ) 228 | .background( 229 | color = if (itemData.isSelected) { 230 | // Set color based on selection state. 231 | listItemStyling.selectedBackgroundColor 232 | } else { 233 | listItemStyling.backgroundColor 234 | } 235 | ) 236 | .combinedClickable( 237 | onClick = { 238 | onListItemClicked( 239 | index, 240 | itemIndex, 241 | !itemData.isSelected 242 | ) 243 | }, 244 | onLongClick = { 245 | onListItemLongClicked( 246 | index, 247 | itemIndex, 248 | !itemData.isSelected 249 | ) 250 | } 251 | ), 252 | text = itemData.name, 253 | textStyle = if (itemData.isSelected) { 254 | // Set text style based on selection state. 255 | listItemStyling.selectedTextStyle 256 | } else { 257 | listItemStyling.textStyle 258 | }, 259 | contentPadding = listItemStyling.contentPadding, 260 | itemSelectedIcon = itemSelectedIcon, 261 | isSelected = itemData.isSelected 262 | ) 263 | } 264 | } 265 | if (itemData != data.listItems.last()) { 266 | HorizontalDivider( 267 | modifier = Modifier 268 | .padding(horizontal = dimensionResource(id = R.dimen.dimen_4dp)), 269 | thickness = dimensionResource(id = R.dimen.dimen_1dp), 270 | color = Color.DarkGray 271 | ) 272 | } 273 | } 274 | 275 | } 276 | } 277 | 278 | 279 | item { 280 | Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.dimen_4dp))) 281 | } 282 | } 283 | } 284 | 285 | /** 286 | * Displays a header view that can be expanded or collapsed, with a text label and an icon. 287 | * 288 | * @param modifier Modifier for the header container, allowing customization like padding or click events. 289 | * @param text The text to display in the header. 290 | * @param textStyle The style for the header text (e.g., font size, color). Default is [TextStyle.Default]. 291 | * @param headerPaddings Paddings for icon and text. (e.g., categoryIcon, text). Default is [defaultHeaderPaddings]. 292 | * @param isExpanded Whether the header is currently expanded or collapsed. 293 | * @param expandedIcon for the icon when the header is expanded (default: up arrow). 294 | * @param collapseIcon for the icon when the header is collapsed (default: down arrow). 295 | * @param categoryIcon for the category icon. 296 | */ 297 | @Composable 298 | private fun HeaderView( 299 | modifier: Modifier = Modifier, 300 | text: String, 301 | textStyle: TextStyle = defaultHeaderStylingAttributes.textStyle, 302 | headerPaddings: HeaderPaddings = defaultHeaderStylingAttributes.headerPaddings, 303 | isExpanded: Boolean, 304 | @DrawableRes expandedIcon: Int = R.drawable.ic_arrow_right, 305 | @DrawableRes collapseIcon: Int = R.drawable.ic_arrow_drop_down, 306 | @DrawableRes categoryIcon: Int? = null, 307 | ) { 308 | Row( 309 | modifier = modifier.defaultMinSize(minHeight = dimensionResource(id = R.dimen.min_height)), 310 | verticalAlignment = Alignment.CenterVertically, 311 | ) { 312 | categoryIcon?.let { 313 | Icon( 314 | modifier = Modifier 315 | .weight(0.15f) 316 | .padding(headerPaddings.categoryIconPadding), 317 | painter = painterResource(id = it), 318 | contentDescription = "Category Icon", 319 | tint = Color.Unspecified 320 | ) 321 | } 322 | Text( 323 | modifier = Modifier 324 | .weight(0.9f) 325 | .padding(headerPaddings.textPadding), 326 | text = text, 327 | style = textStyle 328 | ) 329 | Icon( 330 | modifier = Modifier 331 | .weight(0.15f) 332 | .padding(headerPaddings.actionIconPadding), 333 | painter = painterResource( 334 | id = if (isExpanded) collapseIcon else expandedIcon 335 | ), 336 | contentDescription = stringResource(id = R.string.txtListIndicatorDescription), 337 | tint = if (expandedIcon == R.drawable.ic_arrow_right && collapseIcon == R.drawable.ic_arrow_drop_down) MaterialTheme.colorScheme.onPrimaryContainer else Color.Unspecified 338 | ) 339 | } 340 | } 341 | 342 | /** 343 | * Displays an individual item in a list view with text styling and selection support. 344 | * 345 | * @param modifier Modifier for the list item container, allowing customization like padding or click events. 346 | * @param text The text to display in the list item. 347 | * @param textStyle The style for the text (e.g., font weight, color). Default is [TextStyle.Default]. 348 | * @param contentPadding Padding for text and icon (e.g., text, itemSelectedIcon). Default is [defaultListContentPadding]. 349 | * @param itemSelectedIcon Drawable resource ID for the icon when a item is selected (default: check mark). 350 | * @param isSelected Whether the list item is currently selected. Default is `false`. 351 | */ 352 | @Composable 353 | private fun ListItemView( 354 | modifier: Modifier = Modifier, 355 | text: String, 356 | textStyle: TextStyle = defaultListItemStylingAttributes.textStyle, 357 | contentPadding: PaddingValues = defaultListItemStylingAttributes.contentPadding, 358 | @DrawableRes itemSelectedIcon: Int = R.drawable.ic_check, 359 | isSelected: Boolean = false 360 | ) { 361 | Row( 362 | modifier = modifier.defaultMinSize(minHeight = dimensionResource(id = R.dimen.min_height)), 363 | verticalAlignment = Alignment.CenterVertically 364 | ) { 365 | Text( 366 | modifier = Modifier 367 | .weight(0.9f) 368 | .padding(contentPadding), 369 | text = text, 370 | style = textStyle, 371 | ) 372 | if (isSelected) { 373 | Icon( 374 | painter = painterResource(id = itemSelectedIcon), 375 | contentDescription = stringResource(R.string.txtSelectedListItemIcon), 376 | modifier = Modifier 377 | .weight(0.1f) 378 | .padding(contentPadding), 379 | tint = if (itemSelectedIcon == R.drawable.ic_check) MaterialTheme.colorScheme.onSurfaceVariant else Color.Unspecified 380 | ) 381 | } 382 | } 383 | } 384 | 385 | /** 386 | * Displays an individual item in a list view with text styling and selection support. 387 | * 388 | * @param modifier Modifier for the list item container, allowing customization like padding or click events. 389 | * @param text The annotated text to display in the list item. 390 | * @param textStyle The style for the text (e.g., font weight, color). Default is [TextStyle.Default]. 391 | * @param contentPadding Padding for text and icon (e.g., text, itemSelectedIcon). Default is [defaultListContentPadding]. 392 | * @param itemSelectedIcon Drawable resource ID for the icon when a item is selected (default: check mark). 393 | * @param isSelected Whether the list item is currently selected. Default is `false`. 394 | */ 395 | @Composable 396 | private fun ListItemView( 397 | modifier: Modifier = Modifier, 398 | text: AnnotatedString, 399 | textStyle: TextStyle = defaultListItemStylingAttributes.textStyle, 400 | contentPadding: PaddingValues = defaultListItemStylingAttributes.contentPadding, 401 | @DrawableRes itemSelectedIcon: Int = R.drawable.ic_check, 402 | isSelected: Boolean = false 403 | ) { 404 | Row( 405 | modifier = modifier.defaultMinSize(minHeight = dimensionResource(id = R.dimen.min_height)), 406 | verticalAlignment = Alignment.CenterVertically 407 | ) { 408 | Text( 409 | modifier = Modifier 410 | .weight(0.9f) 411 | .padding(contentPadding), 412 | text = text, 413 | style = textStyle, 414 | ) 415 | if (isSelected) { 416 | Icon( 417 | painter = painterResource(id = itemSelectedIcon), 418 | contentDescription = stringResource(R.string.txtSelectedListItemIcon), 419 | modifier = Modifier 420 | .weight(0.1f) 421 | .padding(contentPadding), 422 | tint = if (itemSelectedIcon == R.drawable.ic_check) MaterialTheme.colorScheme.onSurfaceVariant else Color.Unspecified 423 | ) 424 | } 425 | } 426 | } 427 | 428 | object ExpandableListViewDefaults { 429 | private val defaultHeaderTextPadding = PaddingValues(4.dp) 430 | private val defaultCategoryIconPadding = PaddingValues(4.dp) 431 | private val defaultActionIconPadding = PaddingValues(4.dp) 432 | val defaultListContentPadding = PaddingValues(8.dp) 433 | 434 | val defaultHeaderPaddings = HeaderPaddings( 435 | categoryIconPadding = defaultCategoryIconPadding, 436 | textPadding = defaultHeaderTextPadding, 437 | actionIconPadding = defaultActionIconPadding 438 | ) 439 | 440 | val defaultHeaderStylingAttributes: HeaderStylingAttributes 441 | @Composable get() = HeaderStylingAttributes( 442 | backgroundColor = MaterialTheme.colorScheme.primaryContainer, 443 | cornerRadius = dimensionResource(id = R.dimen.header_corner_radius), 444 | textStyle = MaterialTheme.typography.titleMedium, 445 | headerPaddings = defaultHeaderPaddings 446 | ) 447 | 448 | 449 | val defaultListItemStylingAttributes: ListItemStylingAttributes 450 | @Composable get() = ListItemStylingAttributes( 451 | backgroundColor = MaterialTheme.colorScheme.surfaceContainer, 452 | selectedBackgroundColor = MaterialTheme.colorScheme.surfaceVariant, 453 | textStyle = MaterialTheme.typography.bodyMedium, 454 | selectedTextStyle = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold), 455 | contentPadding = defaultListContentPadding 456 | ) 457 | 458 | val defaultContentAnimation 459 | get() = ContentAnimation( 460 | expandAnimation = expandVertically(animationSpec = tween()), 461 | collapseAnimation = shrinkVertically(animationSpec = tween()) 462 | ) 463 | } 464 | 465 | @Composable 466 | @Preview(showBackground = true) 467 | @Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) 468 | private fun ComposeExpandableListViewPreview() { 469 | ComposeExpandableListView( 470 | expandableListData = listOf( 471 | ExpandableListData( 472 | stringResource(id = R.string.txtHeader), 473 | listItems = listOf( 474 | ListItemData( 475 | name = stringResource(id = R.string.txtListItem), 476 | isSelected = false 477 | ) 478 | ), 479 | isExpanded = false 480 | ) 481 | ) 482 | ) 483 | } 484 | 485 | @Composable 486 | @Preview(showBackground = true) 487 | @Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) 488 | private fun HeaderViewPreview() { 489 | HeaderView( 490 | modifier = Modifier 491 | .fillMaxWidth() 492 | .clip(RoundedCornerShape(8.dp)) 493 | .background(color = MaterialTheme.colorScheme.primaryContainer), 494 | text = stringResource(id = R.string.txtHeader), 495 | categoryIcon = R.drawable.ic_audiotrack, 496 | isExpanded = false 497 | ) 498 | } 499 | 500 | @Composable 501 | @Preview(showBackground = true) 502 | @Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) 503 | private fun ListViewItemPreview() { 504 | ListItemView( 505 | modifier = Modifier 506 | .fillMaxWidth() 507 | .clickable {} 508 | .background(color = MaterialTheme.colorScheme.background), 509 | isSelected = true, 510 | text = stringResource(id = R.string.txtListItem) 511 | ) 512 | } 513 | -------------------------------------------------------------------------------- /ExpandableListView/src/main/java/com/simform/expandablelistview/StylingAttributes.kt: -------------------------------------------------------------------------------- 1 | package com.simform.expandablelistview 2 | 3 | import androidx.compose.animation.EnterTransition 4 | import androidx.compose.animation.ExitTransition 5 | import androidx.compose.foundation.layout.PaddingValues 6 | import androidx.compose.ui.graphics.Color 7 | import androidx.compose.ui.text.TextStyle 8 | import androidx.compose.ui.unit.Dp 9 | import androidx.compose.ui.unit.dp 10 | 11 | /** 12 | * Class for grouping styling parameters of [HeaderView] 13 | */ 14 | data class HeaderStylingAttributes( 15 | val backgroundColor: Color, 16 | val cornerRadius: Dp, 17 | val textStyle: TextStyle, 18 | val headerPaddings: HeaderPaddings 19 | ) 20 | 21 | /** 22 | * Class for grouping styling parameters of [ListItemView] 23 | */ 24 | data class ListItemStylingAttributes( 25 | val backgroundColor: Color, 26 | val selectedBackgroundColor: Color, 27 | val textStyle: TextStyle, 28 | val selectedTextStyle: TextStyle, 29 | val contentPadding: PaddingValues 30 | ) 31 | 32 | /** 33 | * Class for grouping [HeaderView] paddings. 34 | */ 35 | data class HeaderPaddings( 36 | val categoryIconPadding: PaddingValues, 37 | val textPadding: PaddingValues, 38 | val actionIconPadding: PaddingValues 39 | ) 40 | 41 | /** 42 | * Class for grouping Expand/Collapse animation. 43 | */ 44 | data class ContentAnimation( 45 | val expandAnimation: EnterTransition, 46 | val collapseAnimation: ExitTransition 47 | ) -------------------------------------------------------------------------------- /ExpandableListView/src/main/res/drawable/ic_arrow_drop_down.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ExpandableListView/src/main/res/drawable/ic_arrow_drop_up.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ExpandableListView/src/main/res/drawable/ic_arrow_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ExpandableListView/src/main/res/drawable/ic_audiotrack.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ExpandableListView/src/main/res/drawable/ic_check.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ExpandableListView/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12.dp 4 | 8.dp 5 | 12.dp 6 | 8.dp 7 | 48.dp 8 | 4.dp 9 | 1.dp 10 | -------------------------------------------------------------------------------- /ExpandableListView/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Header 4 | List Item 5 | List expand indicator 6 | Selected icon item 7 | -------------------------------------------------------------------------------- /ExpandableListView/src/test/java/com/simform/expandablelistview/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.simform.expandablelistview 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /ExpandableListViewBanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSComposeExpandableListView/be3275cf014e8866e88d35f71fb6f3381adde445/ExpandableListViewBanner.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Simform Solutions 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](ExpandableListViewBanner.png) 2 | 3 | # SSComposeExpandableListView 4 | 5 | [![Platform-badge]][Android] 6 | [![Jetpack Compose-badge]][Android] 7 | [![API-badge]][Android] 8 | [![kotlin]](https://kotlinlang.org) 9 | [![](https://jitpack.io/v/SimformSolutionsPvtLtd/SSComposeExpandableListView.svg)](https://jitpack.io/#SimformSolutionsPvtLtd/SSComposeExpandableListView) 10 | 11 | 12 | Welcome to our SSCompose-Expandable-ListView Library! :tada: 13 | Expandable ListView in Jetpack Compose, providing an easy and flexible way to display hierarchical data in an interactive, expandable list format. 14 | 15 | ## :zap: Features 16 | - :white_check_mark: **Multiple Item Selection**: Supports selecting multiple items at once, allowing users to interact with the list more efficiently. 17 | - :wrench: **Custom Icons**: Easily customizable icons for the header, expand/collapse actions, and selected items, providing a more personalized and visually engaging experience. 18 | - :art: **Customization Options**: Full flexibility to customize the header and list items, including options for text color, background color, and font style, allowing seamless integration with your app's design. 19 | - :page_facing_up: **HTML Text Support**: The list supports rendering HTML-formatted text, making it easier to display rich content like links, bold, italics, and more within list items. 20 | 21 | ## :framed_picture: Preview 22 | 23 | 24 | # :books: How it works: 25 | 1. Add the dependency in your app's build.gradle file 26 | ```kotlin 27 | dependencies { 28 | implementation("com.github.SimformSolutionsPvtLtd:SSComposeExpandableListView:") 29 | } 30 | ``` 31 | 2. Add the JitPack repository 32 | For latest Android Studio, in **settings.gradle** file 33 | inside **`dependencyResolutionManagement`** block 34 | 35 | ```kotlin 36 | dependencyResolutionManagement { 37 | repositories { 38 | ... 39 | maven { url = uri("https://jitpack.io") } 40 | } 41 | } 42 | ``` 43 | 44 | 3. Create ExpandableListView with default values or with custom values. 45 | ```kotlin 46 | ComposeExpandableListView( 47 | modifier = Modifier 48 | .fillMaxWidth(), 49 | expandableListData = uiState.simpleExpandableListData, 50 | onStateChanged = mainViewModel::updateExpandStatus, 51 | onListItemClicked = mainViewModel::listItemSelected, 52 | onListItemLongClick = this@MainActivity::listItemLongClicked 53 | ) 54 | ``` 55 | 4. If you want to use this component in LazyColumn then you can use a variant scoped to LazyListScope as below. 56 | ```kotlin 57 | LazyColumn { 58 | composeExpandableListView( 59 | expandableListData = uiState.simpleExpandableListData, 60 | onStateChanged = mainViewModel::updateExpandStatus, 61 | onListItemClicked = mainViewModel::listItemSelected, 62 | onListItemLongClicked = this@MainActivity::listItemLongClicked 63 | ) 64 | } 65 | ``` 66 | 67 | **Note:** If you have a scrollable Column as a parent then you need to convert your scrollable parent to LazyColumn and use `LazyListScope.composeExpandableListView()` variant which is scoped to LazyListScope. 68 | 69 | ## :art: Customization 70 | 71 | ### You can customize the Expandable listview by providing below parameters. 72 | 73 | | Parameter Name | Parameter Type | Description | Default Value | 74 | |---------------------------|-----------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------|----------------------------------| 75 | | expandableListData | ExpandableListData | List of [ExpandableListData] representing the expandable list's headers and child items. | N/A | 76 | | headerStylingAttributes | HeaderStylingAttributes | Defines styling for headers, including appearance and layout. | defaultHeaderStylingAttributes | 77 | | listItemStylingAttributes | ListItemStylingAttributes | Defines styling for list items, including appearance and layout. | defaultListItemStylingAttributes | 78 | | contentAnimation | ContentAnimation | Defines expand/collapse animation for expandable list content. | defaultContentAnimation | 79 | | expandedIcon | Int | Drawable resource ID for the icon when a header is expanded. | up arrow | 80 | | collapseIcon | Int | Drawable resource ID for the icon when a header is collapsed. | down arrow | 81 | | itemSelectedIcon | Int | Drawable resource ID for the icon when a item is selected. | check mark | 82 | | onStateChanged | (headerIndex: Int, isExpanded: Boolean) -> Unit | Callback for when a header's expand/collapse state changes, providing the header index and expanded state. | Empty Lambda | 83 | | onListItemClicked | (headerIndex: Int, itemIndex: Int, isSelected: Boolean) -> Unit | Callback for when a list item is clicked, providing the header index, item index, and selection state. | Empty Lambda | 84 | | onListItemLongClicked | (headerIndex: Int, itemIndex: Int, isSelected: Boolean) -> Unit | Callback for when a list item is long pressed, providing the header index, item index, and selection state. | Empty Lambda | 85 | 86 | 87 | ## :heart: Find the library useful? 88 | 89 | Support it by joining [stargazers] :star: for this repository. 90 | 91 | ## :handshake: How to Contribute? 92 | 93 | Whether you're helping us fix bugs, improve the docs, or a feature request, we'd love to have you! : 94 | muscle: \ 95 | Check out our __[Contributing Guide]__ for ideas on contributing. 96 | 97 | ## :lady_beetle: Bugs and Feedback 98 | 99 | For bugs, feature requests, and discussion use [GitHub Issues]. 100 | 101 | ## :rocket: Other Mobile Libraries 102 | 103 | Check out our other libraries [Awesome-Mobile-Libraries]. 104 | 105 | ## :balance_scale: License 106 | 107 | Distributed under the MIT license. See [LICENSE] for details. 108 | 109 | 110 | [Android]: https://www.android.com/ 111 | 112 | [Android App Architecture]: https://developer.android.com/topic/architecture 113 | 114 | [stargazers]: https://github.com/SimformSolutionsPvtLtd/SSComposeExpandableListView/stargazers 115 | 116 | [Contributing Guide]: CONTRIBUTING.md 117 | 118 | [Github Issues]: https://github.com/SimformSolutionsPvtLtd/SSComposeExpandableListView/issues 119 | 120 | [Awesome-Mobile-Libraries]: https://github.com/SimformSolutionsPvtLtd/Awesome-Mobile-Libraries 121 | 122 | [license]: LICENSE 123 | 124 | 125 | 126 | [Platform-badge]: https://img.shields.io/badge/Platform-Android-green.svg?logo=Android 127 | 128 | [Jetpack Compose-badge]: https://img.shields.io/badge/Jetpack_Compose-v1.9.2-1c274a.svg?logo=jetpackcompose&logoColor=3ddc84 129 | 130 | [API-badge]: https://img.shields.io/badge/API-24+-51b055 131 | 132 | [kotlin]: https://img.shields.io/badge/Kotlin-v1.9.0-blue.svg -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.application) 3 | alias(libs.plugins.jetbrains.kotlin.android) 4 | alias(libs.plugins.compose.compiler) 5 | } 6 | 7 | android { 8 | namespace = "com.app.sscomposeexpandablelistview" 9 | compileSdk = 35 10 | 11 | defaultConfig { 12 | applicationId = "com.app.sscomposeexpandablelistview" 13 | minSdk = 24 14 | targetSdk = 35 15 | versionCode = 1 16 | versionName = "1.0" 17 | 18 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 19 | vectorDrawables { 20 | useSupportLibrary = true 21 | } 22 | } 23 | 24 | buildTypes { 25 | release { 26 | isMinifyEnabled = false 27 | proguardFiles( 28 | getDefaultProguardFile("proguard-android-optimize.txt"), 29 | "proguard-rules.pro" 30 | ) 31 | } 32 | } 33 | compileOptions { 34 | sourceCompatibility = JavaVersion.VERSION_17 35 | targetCompatibility = JavaVersion.VERSION_17 36 | } 37 | kotlinOptions { 38 | jvmTarget = "17" 39 | } 40 | buildFeatures { 41 | compose = true 42 | } 43 | packaging { 44 | resources { 45 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 46 | } 47 | } 48 | } 49 | 50 | dependencies { 51 | implementation(libs.androidx.core.ktx) 52 | implementation(libs.androidx.lifecycle.runtime.ktx) 53 | implementation(libs.androidx.activity.compose) 54 | implementation(platform(libs.androidx.compose.bom)) 55 | implementation(libs.androidx.ui) 56 | implementation(libs.androidx.ui.graphics) 57 | implementation(libs.androidx.ui.tooling.preview) 58 | implementation(libs.androidx.material3) 59 | implementation(project(":ExpandableListView")) 60 | 61 | // Testing libraries 62 | testImplementation(libs.junit) 63 | androidTestImplementation(libs.androidx.junit) 64 | androidTestImplementation(libs.androidx.espresso.core) 65 | androidTestImplementation(platform(libs.androidx.compose.bom)) 66 | androidTestImplementation(libs.androidx.ui.test.junit4) 67 | debugImplementation(libs.androidx.ui.tooling) 68 | debugImplementation(libs.androidx.ui.test.manifest) 69 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/app/sscomposeexpandablelistview/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.app.sscomposeexpandablelistview 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.app.sscomposeexpandablelistview", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/app/sscomposeexpandablelistview/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.app.sscomposeexpandablelistview.ui 2 | 3 | import android.os.Bundle 4 | import android.widget.Toast 5 | import androidx.activity.ComponentActivity 6 | import androidx.activity.compose.setContent 7 | import androidx.activity.enableEdgeToEdge 8 | import androidx.annotation.ArrayRes 9 | import androidx.compose.foundation.layout.PaddingValues 10 | import androidx.compose.foundation.layout.Spacer 11 | import androidx.compose.foundation.layout.fillMaxSize 12 | import androidx.compose.foundation.layout.fillMaxWidth 13 | import androidx.compose.foundation.layout.height 14 | import androidx.compose.foundation.layout.padding 15 | import androidx.compose.foundation.lazy.LazyColumn 16 | import androidx.compose.material3.ExperimentalMaterial3Api 17 | import androidx.compose.material3.MaterialTheme 18 | import androidx.compose.material3.Scaffold 19 | import androidx.compose.material3.Text 20 | import androidx.compose.material3.TopAppBar 21 | import androidx.compose.material3.TopAppBarDefaults 22 | import androidx.compose.runtime.getValue 23 | import androidx.compose.ui.Modifier 24 | import androidx.compose.ui.graphics.Color 25 | import androidx.compose.ui.res.dimensionResource 26 | import androidx.compose.ui.res.stringResource 27 | import androidx.compose.ui.text.AnnotatedString 28 | import androidx.compose.ui.text.LinkAnnotation 29 | import androidx.compose.ui.text.LinkInteractionListener 30 | import androidx.compose.ui.text.SpanStyle 31 | import androidx.compose.ui.text.TextLinkStyles 32 | import androidx.compose.ui.text.buildAnnotatedString 33 | import androidx.compose.ui.text.fromHtml 34 | import androidx.compose.ui.text.withLink 35 | import androidx.lifecycle.compose.collectAsStateWithLifecycle 36 | import com.app.sscomposeexpandablelistview.R 37 | import com.app.sscomposeexpandablelistview.ui.theme.SSComposeExpandableListViewTheme 38 | import com.simform.expandablelistview.ExpandableListData 39 | import com.simform.expandablelistview.ExpandableListViewDefaults 40 | import com.simform.expandablelistview.ListItemData 41 | import com.simform.expandablelistview.composeExpandableListView 42 | 43 | class MainActivity : ComponentActivity() { 44 | 45 | private lateinit var mainViewModel: MainViewModel 46 | 47 | @OptIn(ExperimentalMaterial3Api::class) 48 | override fun onCreate(savedInstanceState: Bundle?) { 49 | super.onCreate(savedInstanceState) 50 | mainViewModel = MainViewModel(getExpandableListData(), getQnAListItem()) 51 | 52 | enableEdgeToEdge() 53 | setContent { 54 | SSComposeExpandableListViewTheme { 55 | Scaffold( 56 | topBar = { 57 | TopAppBar( 58 | colors = TopAppBarDefaults.topAppBarColors( 59 | containerColor = MaterialTheme.colorScheme.primaryContainer, 60 | titleContentColor = MaterialTheme.colorScheme.primary, 61 | ), 62 | title = { 63 | Text(stringResource(R.string.txtAppTitle)) 64 | } 65 | ) 66 | } 67 | ) { innerPadding -> 68 | 69 | val uiState by mainViewModel.uiState.collectAsStateWithLifecycle() 70 | val headerPaddings = ExpandableListViewDefaults.defaultHeaderPaddings.copy( 71 | textPadding = PaddingValues( 72 | horizontal = dimensionResource(id = R.dimen.dimen_12_dp), 73 | vertical = dimensionResource(id = R.dimen.dimen_8_dp) 74 | ) 75 | ) 76 | val headerStylingAttributes = 77 | ExpandableListViewDefaults.defaultHeaderStylingAttributes.copy( 78 | headerPaddings = headerPaddings 79 | ) 80 | 81 | LazyColumn( 82 | modifier = Modifier 83 | .fillMaxSize() 84 | .padding(horizontal = dimensionResource(id = R.dimen.dimen_12_dp)) 85 | .padding(innerPadding) 86 | ) { 87 | item { 88 | Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.dimen_24_dp))) 89 | Text( 90 | modifier = Modifier 91 | .fillMaxWidth() 92 | .padding(dimensionResource(id = R.dimen.dimen_8_dp)), 93 | text = "Simple", 94 | style = MaterialTheme.typography.headlineMedium 95 | ) 96 | } 97 | composeExpandableListView( 98 | expandableListData = uiState.simpleExpandableListData, 99 | onStateChanged = mainViewModel::updateExpandStatus, 100 | onListItemClicked = mainViewModel::listItemSelected, 101 | onListItemLongClicked = this@MainActivity::listItemLongClicked 102 | ) 103 | item { 104 | Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.dimen_12_dp))) 105 | Text( 106 | modifier = Modifier 107 | .fillMaxWidth() 108 | .padding(dimensionResource(id = R.dimen.dimen_8_dp)), 109 | text = "FAQ", 110 | style = MaterialTheme.typography.headlineMedium 111 | ) 112 | } 113 | composeExpandableListView( 114 | expandableListData = uiState.faqExpandableListData, 115 | headerStylingAttributes = headerStylingAttributes, 116 | onStateChanged = mainViewModel::updateFAQExpandedState, 117 | onListItemLongClicked = this@MainActivity::listItemFAQLongClicked 118 | ) 119 | } 120 | } 121 | } 122 | } 123 | } 124 | 125 | private fun getExpandableListData(): List { 126 | val dataList = ArrayList() 127 | dataList.add( 128 | ExpandableListData( 129 | headerText = "Vegetables", 130 | listItems = getListItemData(R.array.vegetables), 131 | headerCategoryIcon = R.drawable.ic_vegitable 132 | ) 133 | ) 134 | dataList.add( 135 | ExpandableListData( 136 | headerText = "Fruits", 137 | listItems = getListItemData(R.array.fruits), 138 | headerCategoryIcon = R.drawable.ic_fruits 139 | ) 140 | ) 141 | dataList.add( 142 | ExpandableListData( 143 | headerText = "Milk Products", 144 | listItems = getListItemData(R.array.milk_products), 145 | headerCategoryIcon = R.drawable.ic_milk_products 146 | ) 147 | ) 148 | return dataList 149 | } 150 | 151 | private fun getListItemData(@ArrayRes resId: Int): ArrayList { 152 | return ArrayList().apply { 153 | resources.getStringArray(resId).forEach { 154 | add(ListItemData(it, isSelected = false)) 155 | } 156 | } 157 | } 158 | 159 | private fun getQnAListItem(): ArrayList { 160 | val items = ArrayList() 161 | items.add( 162 | ExpandableListData( 163 | headerText = getString(R.string.question_first), 164 | listItems = listOf( 165 | ListItemData( 166 | name = getString(R.string.answer_first), 167 | ) 168 | ), 169 | ) 170 | ) 171 | items.add( 172 | ExpandableListData( 173 | headerText = getString(R.string.question_second), 174 | listItems = listOf( 175 | ListItemData( 176 | annotatedText = buildAnnotatedString { 177 | append(getString(R.string.answer_second)) 178 | withLink( 179 | LinkAnnotation.Clickable( 180 | tag = "Sign in", 181 | styles = TextLinkStyles( 182 | style = SpanStyle( 183 | color = Color.Blue 184 | ) 185 | ), 186 | linkInteractionListener = LinkInteractionListener { 187 | Toast.makeText( 188 | this@MainActivity, 189 | "Link clicked", 190 | Toast.LENGTH_SHORT 191 | ).show() 192 | } 193 | ) 194 | ) { 195 | append(" Click here") 196 | } 197 | }, 198 | ) 199 | ), 200 | ) 201 | ) 202 | items.add( 203 | ExpandableListData( 204 | headerText = getString(R.string.question_third), 205 | listItems = listOf( 206 | ListItemData( 207 | annotatedText = AnnotatedString.fromHtml(getString(R.string.answer_third)), 208 | ) 209 | ), 210 | ) 211 | ) 212 | return items 213 | } 214 | 215 | private fun listItemLongClicked(headerIndex: Int, listItemIndex: Int, isSelected: Boolean) { 216 | val listItem = 217 | mainViewModel.uiState.value.simpleExpandableListData.get(headerIndex).listItems.get( 218 | listItemIndex 219 | ) 220 | Toast.makeText(this, "${listItem.name} Long clicked", Toast.LENGTH_SHORT).show() 221 | } 222 | 223 | private fun listItemFAQLongClicked(headerIndex: Int, listItemIndex: Int, isSelected: Boolean) { 224 | val listItem = 225 | mainViewModel.uiState.value.faqExpandableListData.get(headerIndex).listItems.get( 226 | listItemIndex 227 | ) 228 | Toast.makeText( 229 | this, 230 | "${listItem.name ?: listItem.annotatedText} Long clicked", 231 | Toast.LENGTH_SHORT 232 | ).show() 233 | } 234 | } 235 | 236 | 237 | -------------------------------------------------------------------------------- /app/src/main/java/com/app/sscomposeexpandablelistview/ui/MainUiState.kt: -------------------------------------------------------------------------------- 1 | package com.app.sscomposeexpandablelistview.ui 2 | 3 | import androidx.compose.runtime.Immutable 4 | import androidx.compose.runtime.Stable 5 | import com.simform.expandablelistview.ExpandableListData 6 | 7 | @Immutable 8 | @Stable 9 | data class MainUiState( 10 | val simpleExpandableListData: List, 11 | val faqExpandableListData: List, 12 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/app/sscomposeexpandablelistview/ui/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.app.sscomposeexpandablelistview.ui 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.simform.expandablelistview.ExpandableListData 6 | import kotlinx.coroutines.flow.MutableStateFlow 7 | import kotlinx.coroutines.flow.SharingStarted 8 | import kotlinx.coroutines.flow.stateIn 9 | import kotlinx.coroutines.flow.update 10 | 11 | class MainViewModel( 12 | private val simpleFruitsListData: List, 13 | private val faqListData: List 14 | ) : ViewModel() { 15 | 16 | private val _uiState = MutableStateFlow( 17 | MainUiState( 18 | simpleExpandableListData = simpleFruitsListData, 19 | faqExpandableListData = faqListData 20 | ) 21 | ) 22 | val uiState = _uiState.stateIn( 23 | scope = viewModelScope, 24 | started = SharingStarted.Lazily, 25 | initialValue = MainUiState( 26 | simpleExpandableListData = simpleFruitsListData, 27 | faqExpandableListData = faqListData 28 | ) 29 | ) 30 | 31 | fun updateExpandStatus(index: Int, isExpanded: Boolean) { 32 | _uiState.update { state -> 33 | state.copy(simpleExpandableListData = state.simpleExpandableListData.mapIndexed { indexOfData, expandableListData -> 34 | if (indexOfData == index) expandableListData.copy(isExpanded = isExpanded) else expandableListData 35 | }) 36 | } 37 | } 38 | 39 | fun listItemSelected(headerIndex: Int, listItemIndex: Int, isSelected: Boolean) { 40 | val listItems = 41 | _uiState.value.simpleExpandableListData[headerIndex].listItems.mapIndexed { index, listItemData -> 42 | if (index == listItemIndex) listItemData.copy(isSelected = isSelected) else listItemData 43 | } 44 | val expandableListData = 45 | _uiState.value.simpleExpandableListData.mapIndexed { indexOfData: Int, expandableListData: ExpandableListData -> 46 | if (indexOfData == headerIndex) expandableListData.copy(listItems = listItems) else expandableListData 47 | } 48 | _uiState.update { state -> 49 | state.copy(simpleExpandableListData = expandableListData) 50 | } 51 | } 52 | 53 | fun updateFAQExpandedState(index: Int, isExpanded: Boolean) { 54 | _uiState.update { state -> 55 | state.copy(faqExpandableListData = state.faqExpandableListData.mapIndexed { indexOfData, expandableListData -> 56 | if (indexOfData == index) expandableListData.copy(isExpanded = isExpanded) else expandableListData 57 | }) 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/app/sscomposeexpandablelistview/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.app.sscomposeexpandablelistview.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Purple80 = Color(0xFFD0BCFF) 6 | val PurpleGrey80 = Color(0xFFCCC2DC) 7 | val Pink80 = Color(0xFFEFB8C8) 8 | 9 | val Purple40 = Color(0xFF6650a4) 10 | val PurpleGrey40 = Color(0xFF625b71) 11 | val Pink40 = Color(0xFF7D5260) -------------------------------------------------------------------------------- /app/src/main/java/com/app/sscomposeexpandablelistview/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.app.sscomposeexpandablelistview.ui.theme 2 | 3 | import android.app.Activity 4 | import android.os.Build 5 | import androidx.compose.foundation.isSystemInDarkTheme 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.darkColorScheme 8 | import androidx.compose.material3.dynamicDarkColorScheme 9 | import androidx.compose.material3.dynamicLightColorScheme 10 | import androidx.compose.material3.lightColorScheme 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.platform.LocalContext 13 | 14 | private val DarkColorScheme = darkColorScheme( 15 | primary = Purple80, 16 | secondary = PurpleGrey80, 17 | tertiary = Pink80 18 | ) 19 | 20 | private val LightColorScheme = lightColorScheme( 21 | primary = Purple40, 22 | secondary = PurpleGrey40, 23 | tertiary = Pink40 24 | 25 | /* Other default colors to override 26 | background = Color(0xFFFFFBFE), 27 | surface = Color(0xFFFFFBFE), 28 | onPrimary = Color.White, 29 | onSecondary = Color.White, 30 | onTertiary = Color.White, 31 | onBackground = Color(0xFF1C1B1F), 32 | onSurface = Color(0xFF1C1B1F), 33 | */ 34 | ) 35 | 36 | @Composable 37 | fun SSComposeExpandableListViewTheme( 38 | darkTheme: Boolean = isSystemInDarkTheme(), 39 | // Dynamic color is available on Android 12+ 40 | dynamicColor: Boolean = true, 41 | content: @Composable () -> Unit 42 | ) { 43 | val colorScheme = when { 44 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 45 | val context = LocalContext.current 46 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) 47 | } 48 | 49 | darkTheme -> DarkColorScheme 50 | else -> LightColorScheme 51 | } 52 | 53 | MaterialTheme( 54 | colorScheme = colorScheme, 55 | typography = Typography, 56 | content = content 57 | ) 58 | } -------------------------------------------------------------------------------- /app/src/main/java/com/app/sscomposeexpandablelistview/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.app.sscomposeexpandablelistview.ui.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | bodyLarge = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp, 15 | lineHeight = 24.sp, 16 | letterSpacing = 0.5.sp 17 | ) 18 | /* Other default text styles to override 19 | titleLarge = TextStyle( 20 | fontFamily = FontFamily.Default, 21 | fontWeight = FontWeight.Normal, 22 | fontSize = 22.sp, 23 | lineHeight = 28.sp, 24 | letterSpacing = 0.sp 25 | ), 26 | labelSmall = TextStyle( 27 | fontFamily = FontFamily.Default, 28 | fontWeight = FontWeight.Medium, 29 | fontSize = 11.sp, 30 | lineHeight = 16.sp, 31 | letterSpacing = 0.5.sp 32 | ) 33 | */ 34 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_drop_down.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_drop_up.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fruits.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_milk_products.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_vegitable.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSComposeExpandableListView/be3275cf014e8866e88d35f71fb6f3381adde445/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSComposeExpandableListView/be3275cf014e8866e88d35f71fb6f3381adde445/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSComposeExpandableListView/be3275cf014e8866e88d35f71fb6f3381adde445/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSComposeExpandableListView/be3275cf014e8866e88d35f71fb6f3381adde445/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSComposeExpandableListView/be3275cf014e8866e88d35f71fb6f3381adde445/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSComposeExpandableListView/be3275cf014e8866e88d35f71fb6f3381adde445/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSComposeExpandableListView/be3275cf014e8866e88d35f71fb6f3381adde445/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSComposeExpandableListView/be3275cf014e8866e88d35f71fb6f3381adde445/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSComposeExpandableListView/be3275cf014e8866e88d35f71fb6f3381adde445/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimformSolutionsPvtLtd/SSComposeExpandableListView/be3275cf014e8866e88d35f71fb6f3381adde445/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12.dp 4 | 8.dp 5 | 24.dp 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SSComposeExpandableListView 3 | SSComposeExpandableListView 4 | This is a HTML text Formatted with html tags.]]> 5 | 6 | 7 | Potato 8 | Tomato 9 | Ladies finger 10 | 11 | 12 | 13 | Buttermilk 14 | Curd 15 | Cheese 16 | 17 | 18 | 19 | Apple 20 | Banana 21 | Mango 22 | 23 | 24 | 25 | Vegetables 26 | Milk Products 27 | Fruits 28 | 29 | 30 | 31 | How do I get my apps ready for using Jetpack Compose? 32 | If you apps are using uni-directional data flow, they will stand to benefit as the migration to Compose would be much smoother. I had spoken about a very relevant topic at Droidcon SF 2019. Towards the second half of the talk, I replace my entire UI with Compose with very little effort. 33 | How would you describe a composable? 34 | \@Composable is the secret sauce for Jetpack Compose and it is the most fundamental building block. Annotating a function with @Composable allows that function to describe UI in Compose. This annotation is needed because Compose uses a custom kotlin compiler to function. This custom compiler does some post-processing to each @Composable function and changes its definition at compile time 35 | How can I started with Jetpack Compose? 36 | here to get your machine ready for doing Compose development.]]> 37 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |