JavaFx ObservableList的使用詳解

one發表於2021-01-29

原文地址:JavaFx ObservableList的使用詳解 | Stars-One的雜貨小窩

最近在研究MVVM模式,發現可以將之前寫的FxRecyclerView控制元件改造一下,便是開始嘗試,嘗試過程中發現了不少坑,但是網上資料不是太全面,這裡便寫一篇筆記記錄一下,以供後來者的學習

注:由於本人使用的是TornadoFx來編寫JavaFx專案,所以本文程式碼是使用Kotlin進行編寫,需要有Kotlin基礎,閱讀前請須知,以免浪費你寶貴的時間

介紹

在MVVM模式火的今天,沒想到之前JavaFx已經早有實現,估計現在市面這些都是玩別人剩下的了

這次也是想到了之前的FxRecyclerView控制元件可以拿MVVM改造一番

網上的資料並不多,僅有少有的一兩篇,無奈之下,只得自己啃著官方文件,自己摸索,終於是把基本的使用摸清了

ObservableList,正如其名,可觀察的List,它與List一樣,也是個介面(Java的基礎知識了..)

官方簡短介紹:
A list that allows listeners to track changes when they occur.

大意為當資料改變時,ObservableList可以監聽到這些改變,本質上是提供了一個監聽器介面ListChangeListener.Change來進行相關的監聽,

怎麼監聽呢?只需要設定監聽器即可

val observableList = observableListOf(0,1,2,3)
observableList.onChange { change -> 
     while (change.next()) {
         when {
            change.wasPermutated() -> println("permutated (${change.from} ,${change.to})")
            change.wasReplaced() -> println("replace (${change.from} ,${change.to})")
            else -> {
                when {
                    change.wasAdded() -> println("add (${change.from} ,${change.to})")
                    change.wasRemoved() -> println("remove (${change.from} ,${change.to})")
                    change.wasUpdated() -> println("update (${change.from} ,${change.to})")
                }
            }
        }
     }
}

官方的使用中,必須要求我們在監聽之前呼叫next方法,其返回false表示當前已經是最後一次改變

wasPermutated()這種方法都是返回的boolean值,當資料發生對應符合的變化,各自對應的方法會返回true

change.fromchange.to兩個屬性在後面提及,這裡稍微留意一下

三種監聽型別

從上面的程式碼中,我們可以看到有幾個分支條件,每個分支就是資料發生了某種改變,基本的有三種情況:

  1. List排列順序改變
  2. List中的資料發生改變(資料更新)
  3. List的資料新增及刪除(資料新增或刪除)

PS:官方文件說明中,監聽的順序依次為wasPermutated、add/remove、update

下面對這幾種情況進行說明

1.排列更新監聽

順序排列更新,對應的change.wasPermutated(),其返回值為boolean值,當ObservableList的順序發生變化(即進行了排序操作),此方法就會返回為true

fun main() {
    val observableList = observableListOf(0,1,2,3)
    println(observableList)
    observableList.onChange { change ->
        while (change.next()) {
            when {
                change.wasPermutated() -> println("permutated (${change.from} ,${change.to})")
                
                change.wasReplaced() -> println("replace (${change.from} ,${change.to})")
                else -> {
                    when {
                        change.wasAdded() -> println("add (${change.from} ,${change.to})")
                        change.wasRemoved() -> println("remove (${change.from} ,${change.to})")
                        change.wasUpdated() -> println("update (${change.from} ,${change.to})")
                    }
                }
            }
        }
    }
    observableList.sortBy { it }
    println(observableList)
    observableList.sortByDescending { it }
    println(observableList)
}

對應的輸出結果為:

[0, 1, 2, 3]  //源陣列
permutated (0 ,4) //回撥監聽器中的方法
[0, 1, 2, 3] //升序排列
permutated (0 ,4) 
[3, 2, 1, 0] //降序排列

2.資料更新監聽(replace)

這裡大家可能會有點疑惑,最上面的程式碼不是有個wasUpdated()方法嗎,這裡怎麼標的是replace?

我自己研究的時候,也是很奇怪...官方的那個wasUpdated()方法沒有找到對應的回撥方式,照理說我更新了陣列中的一個資料,這個wasUpdated()應該返回的是true,但是實際測試的時候根本沒有,反而是wasReplaced()返回了true

//上面省略相關程式碼...
//JavaFx對應是set(index,element)方法
observableList[1] = 12 //下標為1的物件更新為12
println(observableList)

輸出結果:

[0, 1, 2, 3]
replace (1 ,2)
[0, 12, 2, 3]

3.資料新增與刪除監聽(add remove)

資料新增和刪除就和上面同理了,當我們呼叫add()(包括addAll())和remove()方法,對應的wasAdd()wasRemove()方法就會返回true

另外,ObservableList提供了兩個List供我們拿到新增的資料和移除的資料

  • change.addedSubList 新增的資料列表
  • change.removed 被移除的資料列表

補充:from與to

這裡大家可能就注意到了change物件的fromto這兩個屬性的區別了

本質上,ObservableList裡面定義的fromto這兩個代表開始下標和結束下標,在資料發生改變的時候,記錄了是哪幾條資料發生了改變,之後提供給我們,我們在ObservableList.change監聽器方法中就可以使用這兩個變數來進行相關的邏輯操作

以下是官方對from的說明:

If wasAdded is true, the interval contains all the values that were added. If wasPermutated is true, the interval marks the values that were permutated. If wasRemoved is true and wasAdded is false, getFrom() and getTo() should return the same number - the place where the removed elements were positioned in the list.

注意重點:如果是資料移除操作,返回的from和to數值是相同的

ObservableList方法說明

  • addAll()
  • remove(from,to) 移除[from,to)之間的元素,from是包含,to是不包含的,注意與remove監聽事件返回的from和to都是相同的
  • move(oldIndex,newIndex) 將下標為oldIndex移動至newIndex下標處

補充:移動監聽(move)

之前沒有找到更新的方法,發現了ObservableList有move的方法,沒想到這個在ObservableList中其實是先做remove操作,之後再做add操作

//程式碼與上面的一致,已省略
//將下標1的資料移動到下標3的位置
observableList.moveAt(1,3)
println(observableList)

輸出結果:

[0, 1, 2, 3]
remove (1 ,1)
add (3 ,4)
[0, 2, 3, 1]

參考