概述
Observable Collections
(可观察集合)是JavaFX
对Java集合的扩展JavaFX
提供了三种类型的可观察集合,(可以观察到集合变化),主要通过以下三个接口进行支持ObservableList
: 继承List
和Observable
接口ObservableSet
: 继承Set
和Observable
接口ObservableMap
: 继承Map
和Observable
接口
Observable Collections
有两个额外特性- 支持失效通知,继承
Observable
接口的特性 - 支持属性变化通知,可以注册监听器,当内容发生改变时会通知
- 支持失效通知,继承
- 工具类:
javafx.collections.FXCollections
- 创建: 没有公开的构造函数,只能通过工具类
FXCollections
创建
ObservableList
ObservableList
是一个支持属性变化通知的List
集合,其实就是对List
集合进行封装,底层还是基于List
集合,可以通过以下四个方法注册/移除监听器
addListener(ListChangeListener<? super E>)
: 注册属性变化监听器removeListener(ListChangeListener<? super E>)
: 移除属性变化监听器addListener(InvalidationListener listener)
: 注册属性失效监听器removeListener(InvalidationListener listener)
: 移除属性失效监听器
创建
必须通过
FXCollections
类的工厂方法进行创建
ObservableList emptyObservableList()
: 创建空的不可修改的ObservableList
,常作为空参数传递ObservableList observableArrayList()
: 创建底层由ArrayList
构成的集合ObservableList observableArrayList(Collection col)
: 创建并初始化集合内容ObservableList observableArrayList(E... items)
: 创建并初始化集合内容ObservableList observableList(List list)
: 创建底层基于传递的List构成的集合ObservableList observableArrayList(Callback extractor)
: 传入抽取器,用于实现监听集合内元素属性的变化,如不通过集合方法add
,remove
等直接修改集合内元素属性的更新变化,详见下文ObservableList observableList(List list, Callback extractor)
: 底层基于传递的List集合,其他同上
监听ObservableList
失效
Invalidation
: 失效监听@Test public void invalidationTest(){ ObservableList<Integer> observableList = FXCollections.observableArrayList(); observableList.addListener((Observable obs) -> { System.out.println("invalid"); }); observableList.add(10); //集合改变触发失效事件 }
监听ObservableList
变化
Change
: 变化监听@Test public void changeTest(){ ObservableList<Integer> observableList = FXCollections.observableArrayList(); observableList.addListener((ListChangeListener.Change<? extends Integer> c) -> { System.out.println("change"); while (c.next()){ if(c.wasAdded()) System.out.println(" -add"); } }); observableList.add(10);//集合改变触发变化事件 }
ListChangeListener.Change
类: 在变化监听器中通过此对象获取变化详细信息方法 描述 ObservableList getList() 获取源可观察集合 boolean next() 是否有下一个变化,通过此遍历所有变化(初始 change
实例是无效的,在调用其他方法之前需要通过此方法使change
指向下次变化)void reset() 重置到初始阶段,可以重新通过next方法遍历所有变化 boolean wasAdded() 变化是否为添加元素 boolean wasRemoved() 变化是否为移除元素 boolean wasReplaced() 变化是否为替换元素 boolean wasPermutated() 变化是否为重排序 boolean wasUpdated() 是否除了 getFrom
到getTo
之间元素以外的元素发生改变,可选事件,可能不会被所有ObservableList
触发int getFrom() 集合中变化的开始位置 int getTo() 集合中变化的结束位置, getFrom
~getTo
表示变化的区间,如果为添加则为所有添加元素的区间,如果为重排序则返回变动元素的区间,如果为删除,则开始位置和结束位置同为删除元素的起始索引int getAddedSize() 返回添加元素的数量 List getAddedSubList() 返回添加元素的集合 List getRemoved() 获取删除元素的集合 int getRemovedSize() 获取删除元素的数量 int getPermutation(int oldIndex) 获取指定索引位置元素重排序后的新索引 监听
ObservableList
更新(内部元素属性更新)- 如果还想监听集合内元素属性的更新变化(Observable对象才行),则需要通过一下两个方法传入
extractor
提取器,当集合发生变化时,会将变化的元素作为参数传入到extractor
中,同时返回一个Observable[]
即可观察数组,这样数组中的Observable
对象发生变化时也会触发集合注册的监听器,触发后change.wasUpdated() = true
<E> ObservableList<E> observableArrayList(Callback<E,Observable[]> extractor)
<E> ObservableList<E> observableList(List<E> list,Callback<E,Observable[]> extractor)
@Test public void extractorTest(){ /** * extractor提取器: 集合中元素发生变化时执行 * 参数: 变化的元素会作为参数传入 * 返回值: 返回Observable[]数组对象, * 这些返回的对象(Observable对象)也会被集合注册的监听器监听, * 当这些Observable对象发生变化,change.wasUpdated() = true */ Callback<IntegerProperty,Observable[]> extractor = p -> { System.out.println("p: " + p.get()); return new Observable[]{p}; }; //监听器: 注册到集合 ListChangeListener<IntegerProperty> listener = change -> { while(change.next()){ if(change.wasUpdated()) { System.out.println("update"); int begin = change.getFrom(); int end = change.getTo(); System.out.printf("begin~end: [%d,%d]%n",begin,end); List<? extends IntegerProperty> subList = change.getList().subList(begin,end); subList.stream() .mapToInt(IntegerExpression::intValue) .forEach(System.out::println); } } }; //不触发change.wasUpdated(): 不传入extractor提取器 ObservableList<IntegerProperty> listA = FXCollections.observableArrayList(); listA.addListener(listener); IntegerProperty a = new SimpleIntegerProperty(1); listA.add(a); a.set(10);//listener监听器不触发 //触发change.wasUpdated(): 传入extractor提取器 ObservableList<IntegerProperty> listB = FXCollections.observableArrayList(extractor); listB.addListener(listener); IntegerProperty b = new SimpleIntegerProperty(2); listB.add(b); b.set(20);//listener监听器触发,change.wasUpdated() = true /** 执行结果 * p: 2 * update * begin~end: [0,1] * 20 */ }
- 如果还想监听集合内元素属性的更新变化(Observable对象才行),则需要通过一下两个方法传入
ObservableSet
ObservableSet
是一个支持属性变化通知的Set
集合,其实就是对Set
集合进行封装,底层还是基于Set
集合,可以通过以下四个方法注册/移除监听器
addListener(SetChangeListener<? super E>)
: 注册属性变化监听器removeListener(SetChangeListener<? super E>)
: 移除属性变化监听器addListener(InvalidationListener listener)
: 注册属性失效监听器removeListener(InvalidationListener listener)
: 移除属性失效监听器
创建
必须通过
FXCollections
类的工厂方法进行创建
<E> ObservableSet<E> observableSet(E... elements)
<E> ObservableSet<E> observableSet(Set<E> set)
<E> ObservableSet<E> emptyObservableSet()
ObservableMap
是底层基于
Map
的可观察集合,支持属性变化通知,可以通过一下四个方法注册/移除监听器
void addListener(MapChangeListener<? super K, ? super V> listener)
: 注册属性变化监听器void removeListener(MapChangeListener<? super K, ? super V> listener)
: 移除属性变化监听器addListener(InvalidationListener listener)
: 注册属性失效监听器removeListener(InvalidationListener listener)
: 移除属性失效监听器
创建
必须通过
FXCollections
类的工厂方法进行创建
<K,V> ObservableMap<K, V> observableHashMap()
: 底层基于HashMap
创建可观察集合<K,V> ObservableMap<K, V> observableMap(Map<K, V> map)
: 基于传入的Map
创建<K,V> ObservableMap<K,V> emptyObservableMap()
: 创建空的不可更改的可观察集合
JavaFX集合的属性和绑定
ObservableList
丶ObservableSet
丶ObservableMap
可以作为对象的公开属性,因此也支持使用high-level
丶low-level
绑定API
ObservableList的属性和绑定
- 通过
ListProperty
(对Observable
进行封装)实现了属性绑定特性ListProperty
实现了ObservableValue
和ObservableList
接口,实际上就是对ObservableList
的封装,通过ListProperty
可以使用ObservableList
的所有方法,同时也支持属性和绑定
创建ListProperty
*创建: * 通过
SimpleListProperty
类的构造函数创建ListProperty
对象SimpleListProperty()
SimpleListProperty(ObservableList initialValue)
SimpleListProperty(Object bean, String name)
SimpleListProperty(Object bean, String name, ObservableList initialValue)
注意: 必须传入
ObservableList
对象,不通过构造函数传入,则通过set
方法设置@Test public void listPropertyTest(){ //错误: 不传入ObservableList则使用时报错 SimpleListProperty<Integer> listPropertyA = new SimpleListProperty<>(); //listPropertyA.add(1); //java.lang.UnsupportedOperationException //正确: 1.通过构造函数传入ObservableList SimpleListProperty<Integer> listPropertyB = new SimpleListProperty<>(FXCollections.observableArrayList()); listPropertyB.add(2); //正确: 2.通过set方法设置ObservableList SimpleListProperty<Integer> listPropertyC = new SimpleListProperty<>(); listPropertyC.set(FXCollections.observableArrayList()); listPropertyC.add(3); }
监听ListProperty
属性变化
- 可以向
ListProperty
添加如下监听器,当ListProperty
内部引用的ObservableList
发生变化或者其内容发生变化是,将会被监听器监听到InvalidationListener
: 失效监听器ChangeListener
: 属性变化监听器ListChangeListener
: 集合属性变化监听器
ListProperty
绑定
ListProperty
对外暴露两个属性:size
和empty
- size属性: 通过
sizeProperty()
获取,返回ReadOnlyIntegerProperty
,表示集合大小属性 - empty属性: 通过
emptyProperty()
获取,返回ReadOnlyBooleanProperty
,表示集合是否为空属性
@Test public void sizeAndEmptyTest(){ ListProperty<Integer> listProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); ReadOnlyIntegerProperty sizeProperty = listProperty.sizeProperty(); sizeProperty.addListener((observable, oldValue, newValue) -> { System.out.println(oldValue + " > " + newValue); }); ReadOnlyBooleanProperty emptyProperty = listProperty.emptyProperty(); emptyProperty.addListener((observable, oldValue, newValue) -> { System.out.println(oldValue + " >> " + newValue); }); listProperty.add(1);//0 > 1 //true >> false listProperty.addAll(2,3);//1 > 3 listProperty.addAll(4);//3 > 4 }
- size属性: 通过
绑定列表属性和内容
Low-level
: 自定义绑定,可以通过自定义类继承ListBinding
类实现high-level
: 系统提供的绑定由ListExpression
和Bindings
类提供绑定其内部
ObservableList
的引用: 通过bind()
或bindBidirectional()
方法即可注意: 不论使用单向还是双向绑定,最终两个
ListProperty
内部使用同一个ObservableList
的引用@Test public void bindListReferenceTest(){ ListProperty<String> lp1 = new SimpleListProperty<>(FXCollections.observableArrayList()); ListProperty<String> lp2 = new SimpleListProperty<>(FXCollections.observableArrayList()); /** 单向绑定: * 绑定之后lp1和lp2内部引用的ObservableList为同一个 */ lp1.bind(lp2); lp1.addAll("a","b"); //lp1.get()==lp2.get(): true , lp1: [a, b] ,lp2: [a, b] System.out.printf("lp1.get()==lp2.get(): %s , lp1: %s ,lp2: %s%n",lp1.get()==lp2.get(),lp1.get(),lp2.get()); lp2.set(FXCollections.observableArrayList("1","2")); //lp1.get()==lp2.get(): true , lp1: [1, 2] ,lp2: [1, 2] System.out.printf("lp1.get()==lp2.get(): %s , lp1: %s ,lp2: %s%n",lp1.get()==lp2.get(),lp1.get(),lp2.get()); //解绑 lp1.unbind(); lp2.set(FXCollections.observableArrayList("c")); //lp1.get()==lp2.get(): false , lp1: [1, 2] ,lp2: [c] System.out.printf("lp1.get()==lp2.get(): %s , lp1: %s ,lp2: %s%n",lp1.get()==lp2.get(),lp1.get(),lp2.get()); /** 双向绑定: 和单向绑定一样 * 绑定之后lp1和lp2内部引用的ObservableList为同一个 */ lp1.bindBidirectional(lp2); //lp1.get()==lp2.get(): true , lp1: [c] ,lp2: [c] System.out.printf("lp1.get()==lp2.get(): %s , lp1: %s ,lp2: %s%n",lp1.get()==lp2.get(),lp1.get(),lp2.get()); lp1.set(FXCollections.observableArrayList("d","f")); //lp1.get()==lp2.get(): true , lp1: [d, f] ,lp2: [d, f] System.out.printf("lp1.get()==lp2.get(): %s , lp1: %s ,lp2: %s%n",lp1.get()==lp2.get(),lp1.get(),lp2.get()); }
绑定其内部
ObservableList
的内容: 通过bindContent()
或bindContentBidirectional()
方法即可@Test public void bindListContentTest(){ ListProperty<String> lp1 = new SimpleListProperty<>(FXCollections.observableArrayList()); ListProperty<String> lp2 = new SimpleListProperty<>(FXCollections.observableArrayList()); //内容单向绑定 lp1.bindContent(lp2); lp1.add("z");//不推荐这样操作: 由于单向绑定,依赖与lp2,不符合单向绑定逻辑 lp2.addAll("a","b"); //lp1也会增加此内容 //lp1.get()==lp2.get(): false , lp1: [a, b, z] ,lp2: [a, b] System.out.printf("lp1.get()==lp2.get(): %s , lp1: %s ,lp2: %s%n",lp1.get()==lp2.get(),lp1.get(),lp2.get()); //解绑 lp1.unbindContent(lp2); //lp1.get()==lp2.get(): false , lp1: [a, b, z] ,lp2: [a, b] System.out.printf("lp1.get()==lp2.get(): %s , lp1: %s ,lp2: %s%n",lp1.get()==lp2.get(),lp1.get(),lp2.get()); //内容双向绑定: 对任意一个集合内容做出修改,通用会作用到另一个集合上 lp1.bindContentBidirectional(lp2); //双向绑定后内容相同 //lp1.get()==lp2.get(): false , lp1: [a, b] ,lp2: [a, b] System.out.printf("lp1.get()==lp2.get(): %s , lp1: %s ,lp2: %s%n",lp1.get()==lp2.get(),lp1.get(),lp2.get()); lp1.add("1"); //lp1.get()==lp2.get(): false , lp1: [a, b, 1] ,lp2: [a, b, 1] System.out.printf("lp1.get()==lp2.get(): %s , lp1: %s ,lp2: %s%n",lp1.get()==lp2.get(),lp1.get(),lp2.get()); lp2.add("2"); //lp1.get()==lp2.get(): false , lp1: [a, b, 1, 2] ,lp2: [a, b, 1, 2] System.out.printf("lp1.get()==lp2.get(): %s , lp1: %s ,lp2: %s%n",lp1.get()==lp2.get(),lp1.get(),lp2.get()); }
对列表中的元素进行绑定: 可以绑定到
ListProperty
内部ObservableList
中的特定元素ObjectBinding<E> valueAt(int index)
: 绑定指定索引的元素,如果索引超出范围则绑定null
ObjectBinding<E> valueAt(ObservableIntegerValue index)
绑定指定索引(索引为可观察类型)的元素,超出范围则绑定null
@Test public void bindListElementsTest(){ ListProperty<Integer> lp = new SimpleListProperty<>(FXCollections.observableArrayList()); lp.addAll(100); ObjectBinding<Integer> zero = lp.valueAt(0); //lp[0]:100,zero:100 System.out.printf("lp[0]:%s,zero:%s%n",lp.get(0),zero.get()); lp.set(0,10); //lp[0]:10,zero:10 System.out.printf("lp[0]:%s,zero:%s%n",lp.get(0),zero.get()); //绑定末尾元素,size-1=末尾索引 ObjectBinding<Integer> last = lp.valueAt(lp.sizeProperty().subtract(1)); //lp[0]:10,last:10 System.out.printf("lp[0]:%s,last:%s%n",lp.get(0),last.get()); lp.add(120); //lp[0]:10,last:120 System.out.printf("lp[0]:%s,last:%s%n",lp.get(0),last.get()); }
ObservableSet
的属性和绑定
通过
SetProperty
(对ObservableSet
进行封装)实现了属性绑定特性SetProperty
使用上与ListProperty
相似,以下可以直接参考上面Listproperty
即可相同点:
- *绑定: *
high-level
: 系统提供的绑定可以直接使用SetExpression
和Bindings
类low-level
: 自定义绑定通过自定义类继承SetBinding
类实现
size
和empty
属性- 绑定到内部
ObservableSet
的引用,绑定到内部ObservableSet
的内容 - 支持
incalidation
丶change
丶setChange
事件
- *绑定: *
不同点:
SetProperty
内部的ObservableSet
的元素是无序的,因此不支持绑定到指定元素,即没有valueAt()
方法支持
创建SetProperty
通过
SimpleSetProperty
类的构造方法进行创建
SimpleSetProperty()
SimpleSetProperty(ObservableSet<E> initialValue)
SimpleSetProperty(Object bean, String name)
SimpleSetProperty(Object bean, String name, ObservableSet<E> initialValue)
- 注意: 必须传入
ObservableSet
对象,不通过构造函数传入,则通过set
方法设置
ObservableMap
的属性和绑定
- 通过
MapProperty
(对ObservableMap
进行封装)实现了属性绑定特性 MapProperty
使用上与ListProperty
相似,以下可以直接参考上面Listproperty
即可- 相同点:
- *绑定: *
high-level
: 系统提供的绑定可以直接使用MapExpression
和Bindings
类low-level
: 自定义绑定通过自定义类继承MapBinding
类实现
size
和empty
属性- 绑定到内部
ObservableMap
的引用,绑定到内部ObservableMap
的内容 - 支持
incalidation
丶change
丶mapChange
事件
- *绑定: *
- 不同点:
MapProperty
支持绑定到其内部ObservableMap
的指定key的元素,通过valueAt()
方法即可
创建MapProperty
通过
SimpleMapProperty
类构造方法创建
SimpleMapProperty()
SimpleMapProperty(Object bean, String name)
SimpleMapProperty(Object bean, String name, ObservableMap<K,V> initialValue)
SimpleMapProperty(ObservableMap<K,V> initialValue)