使用 kotlin 和 tornadofx 在列表视图上无限滚动
endless scroll on listview with kotlin and tornadofx
我想为列表视图实现无限滚动。
该应用从服务器部分接收数据,目前它只显示最后 50 行。
我正在使用 tornadofx
,代码如下:
MainController.kt
:
private fun setAllServerHistoryList() {
if (curSelectedLogId != -1) {
serverHistoryList.clear()
runAsync {
serverAPI.getServerHistory(curSelectedLogId)
} ui {
serverHistoryList.setAll(it)
}
}
}
MainView.kt
:
private val historyListView = listview(controller.serverHistoryList) {
selectionModel = NoSelectionModel()
cellFormat{...}
}
getServerHistory
向服务器发出 GET
请求并 returns 解析数据,我可以使用 offset
和 count
参数轻松询问其他行。
但我还没有意识到如何在列表的两侧添加侦听器以检测 amount of rows left
(如果我不是从底部而是在 [=40= 的范围内打开视图) ]),以及如何在避免内存错误的情况下将数据添加到现有列表。
我已经阅读了很多关于同一问题的答案,但其中大部分(如果不是全部)都是关于 android 和 java 应用程序的。
提前感谢所有回复。
UPD 重写了答案的解决方案(为 @TornadoFX 干杯):
import javafx.collections.FXCollections
import javafx.scene.control.ListCell
import javafx.util.Callback
import tornadofx.*
data class ScrollableItemWrapper(val data : String, val lastItem : Boolean = false) {
val id = nextId() // always increasing
companion object {
private var idgen = 1 // faux static class member
fun nextId() = idgen++
}
}
class EndlessScrollView : View("Endless Scroll") {
var currentBatch = 1
var last_batch = 0
val TOTAL_NUM_BATCHES = 10
private val records = FXCollections.observableArrayList<ScrollableItemWrapper>()
override val root = listview(records) {
selectionModel = NoSelectionModel()
cellFactory = Callback { listView ->
object : ListCell<ScrollableItemWrapper>() {
private val listener = ChangeListener<Number> { obs, ov, nv ->
if ( userData != null ) {
val scrollable = userData as ScrollableItemWrapper
val pos = listView.height - this.height - nv.toDouble()
if ( scrollable.lastItem && pos > 0 && (pos < this.height) && index < last_batch*50) {
val currentPos = index
runAsync {
getNextBatch()
} ui {
listView.scrollTo(currentPos)
records.addAll( it )
}
}
}
}
init {
layoutYProperty().addListener(listener)
}
override fun updateItem(item: ScrollableItemWrapper?, empty: Boolean) {
super.updateItem(item, empty)
if( item != null && !empty ) {
text = "[${item.id}]" + item.data
userData = item
} else {
text = null
userData = null
}
}
}
}
}
init {
records.addAll( getNextBatch() )
}
private fun getNextBatch() : List<ScrollableItemWrapper> {
if( currentBatch < TOTAL_NUM_BATCHES ) {
last_batch++
return 1.rangeTo(50).map {
ScrollableItemWrapper("Batch ${currentBatch} Record ${it}", it == 50)
}.apply {
currentBatch++
}.toList()
} else {
return emptyList()
}
}
}
试试我在普通 JavaFX 应用程序中使用的这段代码。它依赖于在多个版本的 JavaFX 中起作用的副作用。包装器 class 用于将数据与一个标志配对,该标志指示某个项目是否是批次中的最后一个。在cell factory中,当给定layoutY 属性 space 时,会注册并激活一个监听器。如果 layoutY 从 0 增加——暗示显示单元格——并且设置了最后一项标志,则获取更多数据。
该数据已添加但滚动位置已保存,因此 ListView 不会跳过整个获取的集合。
data class ScrollableItemWrapper(val data : String, val lastItem : Boolean = false) {
val id = nextId() // always increasing
companion object {
private var idgen = 1 // faux static class member
fun nextId() = idgen++
}
}
class EndlessScrollView : View("Endless Scroll") {
var currentBatch = 1
val TOTAL_NUM_BATCHES = 10
val records = mutableListOf<ScrollableItemWrapper>().observable()
override val root = listview(records) {
cellFactory = Callback {
object : ListCell<ScrollableItemWrapper>() {
private val listener = ChangeListener<Number> { obs, ov, nv ->
if ( userData != null ) {
val scrollable = userData as ScrollableItemWrapper
if( scrollable.lastItem &&
((listView.height - this.height - nv.toDouble()) > 0.0)) {
val currentPos = index
runAsync {
getNextBatch()
} ui {
listView.scrollTo(currentPos)
records.addAll( it )
}
}
}
}
init {
layoutYProperty().addListener(listener);
}
override fun updateItem(item: ScrollableItemWrapper?, empty: Boolean) {
super.updateItem(item, empty)
if( item != null && !empty ) {
text = "[${item.id}]" + item.data!!
userData = item
} else {
text = null
userData = null
}
}
protected fun finalize() {
layoutYProperty().removeListener(listener)
}
}
}
}
init {
records.addAll( getNextBatch() )
}
private fun getNextBatch() : List<ScrollableItemWrapper> {
if( currentBatch <= TOTAL_NUM_BATCHES ) {
return 'A'.rangeTo('Z').map {
ScrollableItemWrapper("Batch ${currentBatch} Record ${it}", it == 'Z')
}.apply {
currentBatch++
}.toList()
} else {
return emptyList()
}
}
}
我想为列表视图实现无限滚动。
该应用从服务器部分接收数据,目前它只显示最后 50 行。
我正在使用 tornadofx
,代码如下:
MainController.kt
:
private fun setAllServerHistoryList() {
if (curSelectedLogId != -1) {
serverHistoryList.clear()
runAsync {
serverAPI.getServerHistory(curSelectedLogId)
} ui {
serverHistoryList.setAll(it)
}
}
}
MainView.kt
:
private val historyListView = listview(controller.serverHistoryList) {
selectionModel = NoSelectionModel()
cellFormat{...}
}
getServerHistory
向服务器发出 GET
请求并 returns 解析数据,我可以使用 offset
和 count
参数轻松询问其他行。
但我还没有意识到如何在列表的两侧添加侦听器以检测 amount of rows left
(如果我不是从底部而是在 [=40= 的范围内打开视图) ]),以及如何在避免内存错误的情况下将数据添加到现有列表。
我已经阅读了很多关于同一问题的答案,但其中大部分(如果不是全部)都是关于 android 和 java 应用程序的。
提前感谢所有回复。
UPD 重写了答案的解决方案(为 @TornadoFX 干杯):
import javafx.collections.FXCollections
import javafx.scene.control.ListCell
import javafx.util.Callback
import tornadofx.*
data class ScrollableItemWrapper(val data : String, val lastItem : Boolean = false) {
val id = nextId() // always increasing
companion object {
private var idgen = 1 // faux static class member
fun nextId() = idgen++
}
}
class EndlessScrollView : View("Endless Scroll") {
var currentBatch = 1
var last_batch = 0
val TOTAL_NUM_BATCHES = 10
private val records = FXCollections.observableArrayList<ScrollableItemWrapper>()
override val root = listview(records) {
selectionModel = NoSelectionModel()
cellFactory = Callback { listView ->
object : ListCell<ScrollableItemWrapper>() {
private val listener = ChangeListener<Number> { obs, ov, nv ->
if ( userData != null ) {
val scrollable = userData as ScrollableItemWrapper
val pos = listView.height - this.height - nv.toDouble()
if ( scrollable.lastItem && pos > 0 && (pos < this.height) && index < last_batch*50) {
val currentPos = index
runAsync {
getNextBatch()
} ui {
listView.scrollTo(currentPos)
records.addAll( it )
}
}
}
}
init {
layoutYProperty().addListener(listener)
}
override fun updateItem(item: ScrollableItemWrapper?, empty: Boolean) {
super.updateItem(item, empty)
if( item != null && !empty ) {
text = "[${item.id}]" + item.data
userData = item
} else {
text = null
userData = null
}
}
}
}
}
init {
records.addAll( getNextBatch() )
}
private fun getNextBatch() : List<ScrollableItemWrapper> {
if( currentBatch < TOTAL_NUM_BATCHES ) {
last_batch++
return 1.rangeTo(50).map {
ScrollableItemWrapper("Batch ${currentBatch} Record ${it}", it == 50)
}.apply {
currentBatch++
}.toList()
} else {
return emptyList()
}
}
}
试试我在普通 JavaFX 应用程序中使用的这段代码。它依赖于在多个版本的 JavaFX 中起作用的副作用。包装器 class 用于将数据与一个标志配对,该标志指示某个项目是否是批次中的最后一个。在cell factory中,当给定layoutY 属性 space 时,会注册并激活一个监听器。如果 layoutY 从 0 增加——暗示显示单元格——并且设置了最后一项标志,则获取更多数据。
该数据已添加但滚动位置已保存,因此 ListView 不会跳过整个获取的集合。
data class ScrollableItemWrapper(val data : String, val lastItem : Boolean = false) {
val id = nextId() // always increasing
companion object {
private var idgen = 1 // faux static class member
fun nextId() = idgen++
}
}
class EndlessScrollView : View("Endless Scroll") {
var currentBatch = 1
val TOTAL_NUM_BATCHES = 10
val records = mutableListOf<ScrollableItemWrapper>().observable()
override val root = listview(records) {
cellFactory = Callback {
object : ListCell<ScrollableItemWrapper>() {
private val listener = ChangeListener<Number> { obs, ov, nv ->
if ( userData != null ) {
val scrollable = userData as ScrollableItemWrapper
if( scrollable.lastItem &&
((listView.height - this.height - nv.toDouble()) > 0.0)) {
val currentPos = index
runAsync {
getNextBatch()
} ui {
listView.scrollTo(currentPos)
records.addAll( it )
}
}
}
}
init {
layoutYProperty().addListener(listener);
}
override fun updateItem(item: ScrollableItemWrapper?, empty: Boolean) {
super.updateItem(item, empty)
if( item != null && !empty ) {
text = "[${item.id}]" + item.data!!
userData = item
} else {
text = null
userData = null
}
}
protected fun finalize() {
layoutYProperty().removeListener(listener)
}
}
}
}
init {
records.addAll( getNextBatch() )
}
private fun getNextBatch() : List<ScrollableItemWrapper> {
if( currentBatch <= TOTAL_NUM_BATCHES ) {
return 'A'.rangeTo('Z').map {
ScrollableItemWrapper("Batch ${currentBatch} Record ${it}", it == 'Z')
}.apply {
currentBatch++
}.toList()
} else {
return emptyList()
}
}
}