I'm new to jetpack compose and trying to implement a scrollable data table using jetpack LazyColumn/LazyRow. The first row and first column of the entire table shall be sticky.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BaseTheme {
ExceptionsSpreadSheet(exceptions = generateData())
}
}
}
}
This is my activity, the BaseTheme
is just the default theme android studio generated for me.
@OptIn(ExperimentalFoundationApi::class)
@Preview(showBackground = true, widthDp = 960, heightDp = 540)
@Composable
fun ExceptionSpreadSheetHeader(
exception: DropBoxException = DropBoxException(),
state: LazyListState = rememberLazyListState()
) {
LazyRow(
modifier = Modifier.background(color = Color.DarkGray),
state = state
) {
stickyHeader {
ExceptionSpreadSheetCell(text = "id")
}
items(1) {
ExceptionSpreadSheetCell(text = "packageName")
ExceptionSpreadSheetCell(text = "processName")
ExceptionSpreadSheetCell(text = "packageVersionCode")
ExceptionSpreadSheetCell(text = "packageVersionName")
ExceptionSpreadSheetCell(text = "activity")
ExceptionSpreadSheetCell(text = "romVersion")
ExceptionSpreadSheetCell(text = "platformId")
ExceptionSpreadSheetCell(text = "productName")
ExceptionSpreadSheetCell(text = "modelName")
ExceptionSpreadSheetCell(text = "exceptionSummary")
ExceptionSpreadSheetCell(text = "exceptionType")
ExceptionSpreadSheetCell(text = "exceptionDigest")
ExceptionSpreadSheetCell(text = "androidId")
ExceptionSpreadSheetCell(text = "occurTime")
ExceptionSpreadSheetCell(text = "callStack")
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Preview(showBackground = true, widthDp = 960, heightDp = 540)
@Composable
fun ExceptionSpreadSheetRow(
exception: DropBoxException = DropBoxException(),
state: LazyListState = rememberLazyListState()
) {
LazyRow(state = state) {
stickyHeader {
ExceptionSpreadSheetCell(
text = exception.id.toString(),
modifier = Modifier.background(color = Color.DarkGray)
)
}
items(1) {
ExceptionSpreadSheetCell(text = exception.packageName)
ExceptionSpreadSheetCell(text = exception.processName)
ExceptionSpreadSheetCell(text = exception.packageVersionCode)
ExceptionSpreadSheetCell(text = exception.packageVersionName)
ExceptionSpreadSheetCell(text = exception.activity)
ExceptionSpreadSheetCell(text = exception.romVersion)
ExceptionSpreadSheetCell(text = exception.platformId.toString())
ExceptionSpreadSheetCell(text = exception.productName)
ExceptionSpreadSheetCell(text = exception.modelName)
ExceptionSpreadSheetCell(text = exception.exceptionSummary)
ExceptionSpreadSheetCell(text = exception.exceptionType)
ExceptionSpreadSheetCell(text = exception.exceptionDigest)
ExceptionSpreadSheetCell(text = exception.androidId)
ExceptionSpreadSheetCell(text = exception.occurTime.toString())
ExceptionSpreadSheetCell(text = exception.callStack)
}
}
}
I created a ExceptionSpreadSheetHeader
for the sticky title, which will display some static names for columns, and a ExceptionSpreadSheetRow
for rows below the header, each one has a sticky id
column.
Then I combined these two components together as ``
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ExceptionsSpreadSheet(exceptions: List<DropBoxException>) {
val sharedScrollState = rememberLazyListState()
if (sharedScrollState.isScrollInProgress) {
sharedScrollState.firstVisibleItemIndex
sharedScrollState.firstVisibleItemScrollOffset
}
LazyColumn {
stickyHeader {
ExceptionSpreadSheetHeader(exception = exceptions.first())
ExceptionSpreadSheetHeader(exception = exceptions.first(), state = sharedScrollState)
ExceptionSpreadSheetHeader(state = sharedScrollState)
ExceptionSpreadSheetRow(exception = exceptions.first(), state = sharedScrollState)
ExceptionSpreadSheetRow(state = sharedScrollState)
ExceptionSpreadSheetHeaderTest(state = sharedScrollState)
ExceptionSpreadSheetHeaderTest(exception = exceptions.first(), state = sharedScrollState)
}
items(exceptions) {
ExceptionSpreadSheetRow(exception = it, state = sharedScrollState)
}
}
}
I used sharedScrollState
to sync horizontal scroll state across columns, then I found 2 strange behavior, any help or explanation would be great for me, thanks in advance.
in the
items
section, the shared scroll state does not work at first, only when i added theif (sharedScrollState.isScrollInProgress)
part, the rows initems
would sync, otherwise it looks like items_not_sync. I would love to know why it works after I added theif
part, or why it does not work beforein the
stickyHeaders
section ofExceptionsSpreadSheet
, I added a lot of rows for testing, and the result looks like header_not_sync. The first 6 lines are headers I added, including the 2 red one asExceptionSpreadSheetHeaderTest
:
@OptIn(ExperimentalFoundationApi::class)
@Preview(showBackground = true, widthDp = 960, heightDp = 540)
@Composable
fun ExceptionSpreadSheetHeaderTest(
exception: DropBoxException = DropBoxException(),
state: LazyListState = rememberLazyListState()
) {
LazyRow(
modifier = Modifier.background(color = Color.Red),
state = state
) {
stickyHeader {
ExceptionSpreadSheetCell(text = "id")
}
items(1) {
ExceptionSpreadSheetCell(text = "packageName")
ExceptionSpreadSheetCell(text = "processName")
ExceptionSpreadSheetCell(text = "packageVersionCode")
ExceptionSpreadSheetCell(text = "packageVersionName")
ExceptionSpreadSheetCell(text = "activity")
ExceptionSpreadSheetCell(text = "romVersion")
ExceptionSpreadSheetCell(text = "platformId")
ExceptionSpreadSheetCell(text = "productName")
ExceptionSpreadSheetCell(text = "modelName")
ExceptionSpreadSheetCell(text = "exceptionSummary")
ExceptionSpreadSheetCell(text = "exceptionType")
ExceptionSpreadSheetCell(text = "exceptionDigest")
ExceptionSpreadSheetCell(text = "androidId")
ExceptionSpreadSheetCell(text = exception.occurTime.toString())
ExceptionSpreadSheetCell(text = "callStack")
}
}
}
In the gif, only the 4th and 7th header is synced with contents below, so here are the strange behavior(s):
- in the code, the 2nd header (
ExceptionSpreadSheetHeader(exception = exceptions.first(), state = sharedScrollState)
) and the 7th header (ExceptionSpreadSheetHeaderTest(exception = exceptions.first(), state = sharedScrollState)
) has nothing different but between the second lastExceptionSpreadSheetCell
in thereitems
section, it seems that I have to somehow use theexception
passed in then the sync would work, that looks really strange to me. - the 6th and 7th header differs in constructor parameters, 6th header uses default value, 7th header uses outer assigned value, but they result in different sync behavior.
The DropBoxException
class is a simple data class
data class DropBoxException(
var id: Int = 0,
var packageName: String = "",
var processName: String = "",
var packageVersionCode: String = "",
var packageVersionName: String = "",
var activity: String = "",
var romVersion: String = "",
var platformId: Int = -1,
var productName: String = "",
var modelName: String = "",
var exceptionSummary: String = "",
var exceptionType: String = "",
var exceptionDigest: String = "",
var androidId: String = "",
var occurTime: Int = 0,
var callStack: String = "",
)
and generateData()
create some dummy data for me
fun generateData(): List<DropBoxException> = (1..300).map {
DropBoxException(
id = it,
packageName = "$it packageName",
processName = "$it processName"
)
}
If you beleived I mis-used some of the interfaces or the question is asked somewhere else before, feel free to post links and I'll look into them, thanks again!