概述
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): 绑定指定索引的元素,如果索引超出范围则绑定nullObjectBinding<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)