Initial project
This commit is contained in:
337
src/main/java/com/imyeyu/fx/ui/components/XPagination.java
Normal file
337
src/main/java/com/imyeyu/fx/ui/components/XPagination.java
Normal file
@@ -0,0 +1,337 @@
|
||||
package com.imyeyu.fx.ui.components;
|
||||
|
||||
import com.imyeyu.fx.icon.TimiFXIcon;
|
||||
import com.imyeyu.fx.ui.TimiFXUI;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleLongProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页组件,支持省略页,滚动页,如 [<][1]..[5][6][7][8][9]..[20][>]
|
||||
* <pre>
|
||||
* XPagination pagination = new XPagination();
|
||||
* pagination.setSize(10); // 单页数据量
|
||||
* pagination.setLength(200); // 总数据量
|
||||
* pagination.indexProperty((obs, o, newIndex) -> {
|
||||
* // 监听激活下标
|
||||
* });
|
||||
* pagination.setIndex(6); // 激活页下标(0 开始,这是第 7 页)
|
||||
* </pre>
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2021-12-22 15:25
|
||||
*/
|
||||
public class XPagination extends HBox implements TimiFXUI {
|
||||
|
||||
private static final String STYLE_CLASS = "x-pagination";
|
||||
|
||||
/** 阻止取消选择 */
|
||||
public static final EventHandler<MouseEvent> EVENT_TOGGLE_BUTTON = e -> {
|
||||
if (e.getSource() instanceof ToggleButton btn && btn.isSelected()) {
|
||||
e.consume();
|
||||
}
|
||||
};
|
||||
|
||||
private final IconButton prev, next;
|
||||
|
||||
// 步进翻页动态下标
|
||||
private int prevI, nextI;
|
||||
|
||||
private final LongProperty lp; // lengthProperty 总数据量 [0, N]
|
||||
private final IntegerProperty ip; // indexProperty 激活下标 [0, chunkProperty.value]
|
||||
private final IntegerProperty sp; // sizeProperty 单页数量 [1, N]
|
||||
private final IntegerProperty cp; // chunkProperty 页面数量 [1, N]
|
||||
|
||||
/** 默认构造 */
|
||||
public XPagination() {
|
||||
// 基本参数
|
||||
lp = new SimpleLongProperty();
|
||||
ip = new SimpleIntegerProperty();
|
||||
sp = new SimpleIntegerProperty();
|
||||
cp = new SimpleIntegerProperty();
|
||||
|
||||
// 更新页面大小和总数据量时重新计算页面数量
|
||||
cp.bind(Bindings.createIntegerBinding(() -> {
|
||||
long total = lp.get();
|
||||
int page = sp.get();
|
||||
return (int) Math.ceil(1D * total / page);
|
||||
}, sp, lp));
|
||||
|
||||
// 页面组
|
||||
List<PageButton> pageButtons = new ArrayList<>();
|
||||
PageButton tb;
|
||||
|
||||
// 上一页
|
||||
prev = new IconButton(TimiFXIcon.fromName("ARROW_1_W")).withBackground();
|
||||
prev.getStyleClass().add(CSS.BORDER_N);
|
||||
prev.setMaxHeight(Double.MAX_VALUE);
|
||||
prev.disableProperty().bind(ip.isEqualTo(0));
|
||||
getChildren().add(prev);
|
||||
|
||||
// 前置页 [1, 6]
|
||||
for (int i = 0; i < 6; i++) {
|
||||
tb = new PageButton();
|
||||
tb.indexProperty.set(i);
|
||||
if (0 < i) {
|
||||
|
||||
// 第一页保持显示,非第一页显示条件:
|
||||
// 1. 总页数小于 8,页码大于 i
|
||||
// 2. 总页数大于等于 8,激活下标小于 4(1 - 4 页)
|
||||
tb.visibleProperty().bind(cp.greaterThan(i).and(cp.lessThan(8)).or(cp.greaterThan(7).and(ip.lessThan(4))));
|
||||
} else {
|
||||
tb.getStyleClass().add(CSS.BORDER_LR);
|
||||
}
|
||||
pageButtons.add(tb);
|
||||
getChildren().add(tb);
|
||||
}
|
||||
{
|
||||
// 左省略,总页数大于 7(有中间页),激活下标大于 3 时显示(第五页)
|
||||
Label leftEllipsis = new Label("..");
|
||||
leftEllipsis.setAlignment(Pos.CENTER);
|
||||
leftEllipsis.setPrefWidth(32);
|
||||
leftEllipsis.setBorder(Stroke.RIGHT);
|
||||
leftEllipsis.setMaxHeight(Double.MAX_VALUE);
|
||||
leftEllipsis.visibleProperty().bind(cp.greaterThan(7).and(ip.greaterThan(3)));
|
||||
leftEllipsis.managedProperty().bind(leftEllipsis.visibleProperty());
|
||||
getChildren().add(leftEllipsis);
|
||||
|
||||
// 中间页,显示条件:总页数大于 8 ,激活下标大于 3 且小于总页数 - 4(小于 3 时前置页处理,大于总页数 - 4 时后置页处理)
|
||||
for (int i = 0; i < 5; i++) {
|
||||
tb = new PageButton();
|
||||
tb.indexProperty.bind(ip.add(i - 2)); // 动态数值
|
||||
tb.visibleProperty().bind(cp.greaterThan(8).and(ip.greaterThan(3).and(ip.lessThan(cp.subtract(4)))));
|
||||
|
||||
pageButtons.add(tb);
|
||||
getChildren().add(tb);
|
||||
}
|
||||
|
||||
// 右省略,显示条件:总页数大于 7,激活下标小于总页数 - 4(大于总页数 - 4 时后置页处理,不需要省略)
|
||||
Label rightEllipsis = new Label("..");
|
||||
rightEllipsis.setAlignment(Pos.CENTER);
|
||||
rightEllipsis.setPrefWidth(32);
|
||||
rightEllipsis.setMaxHeight(Double.MAX_VALUE);
|
||||
rightEllipsis.setBorder(Stroke.RIGHT);
|
||||
rightEllipsis.visibleProperty().bind(cp.greaterThan(7).and(ip.lessThan(cp.subtract(4))));
|
||||
rightEllipsis.managedProperty().bind(rightEllipsis.visibleProperty());
|
||||
getChildren().add(rightEllipsis);
|
||||
}
|
||||
|
||||
// 后置页
|
||||
for (int i = 0; i < 6; i++) {
|
||||
tb = new PageButton();
|
||||
tb.indexProperty.bind(cp.add(i - 6)); // 动态数值
|
||||
if (i == 5) {
|
||||
// 页数达到 7 时最后一页始终显示
|
||||
tb.visibleProperty().bind(cp.greaterThan(6));
|
||||
} else {
|
||||
// 其他页显示条件:总页数大于 7,激活下标大于总页数 - 5(倒数第四页)
|
||||
tb.visibleProperty().bind(cp.greaterThan(7).and(ip.greaterThan(cp.subtract(5))));
|
||||
}
|
||||
pageButtons.add(tb);
|
||||
getChildren().add(tb);
|
||||
}
|
||||
|
||||
// 下一页
|
||||
next = new IconButton(TimiFXIcon.fromName("ARROW_1_E")).withBackground();
|
||||
next.setMaxHeight(Double.MAX_VALUE);
|
||||
next.getStyleClass().add(CSS.BORDER_N);
|
||||
next.disableProperty().bind(ip.isEqualTo(cp.subtract(1)).or(cp.isEqualTo(0)));
|
||||
|
||||
getStyleClass().add(STYLE_CLASS);
|
||||
setBorder(Stroke.DEFAULT);
|
||||
setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
|
||||
setAlignment(Pos.BOTTOM_CENTER);
|
||||
getChildren().add(next);
|
||||
|
||||
// ---------- 事件 ----------
|
||||
|
||||
new ToggleGroup().getToggles().addAll(pageButtons);
|
||||
for (int i = 0; i < pageButtons.size(); i++) {
|
||||
// 阻止取消选择
|
||||
pageButtons.get(i).addEventFilter(MouseEvent.MOUSE_PRESSED, EVENT_TOGGLE_BUTTON);
|
||||
}
|
||||
|
||||
// 数据变动更新
|
||||
ChangeListener<Number> paramsListener = (obs, o, n) -> {
|
||||
// 步进翻页
|
||||
prevI = ip.get() - 1;
|
||||
nextI = ip.get() + 1;
|
||||
// 重置激活页
|
||||
if (cp.get() - 1 < ip.get()) {
|
||||
ip.set(0);
|
||||
}
|
||||
// 主动选中
|
||||
for (int i = 0; i < pageButtons.size(); i++) {
|
||||
// 分页存在预设页码,只作触发事件用(如前置页的第五第六页),需要主动计算激活的按钮
|
||||
if (ip.get() == pageButtons.get(i).indexProperty.get() && pageButtons.get(i).isVisible()) {
|
||||
pageButtons.get(i).setSelected(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
sp.addListener(paramsListener);
|
||||
ip.addListener(paramsListener);
|
||||
lp.addListener(paramsListener);
|
||||
|
||||
// 步进翻页
|
||||
prev.setOnAction(e -> ip.set(prevI));
|
||||
next.setOnAction(e -> ip.set(nextI));
|
||||
}
|
||||
|
||||
/** 选择上一页 */
|
||||
public void prev() {
|
||||
prev.fire();
|
||||
}
|
||||
|
||||
/** 选择下一页 */
|
||||
public void next() {
|
||||
next.fire();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置激活页下标,取值范围 [0, {@link #getChunk()}]
|
||||
*
|
||||
* @param index 激活页下标
|
||||
*/
|
||||
public void setIndex(int index) {
|
||||
ip.set(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前激活页下标
|
||||
*
|
||||
* @return 当前激活页下标
|
||||
*/
|
||||
public int getIndex() {
|
||||
return ip.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取激活页监听
|
||||
*
|
||||
* @return 激活页监听
|
||||
*/
|
||||
public IntegerProperty indexProperty() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前页面数量
|
||||
*
|
||||
* @return 当前页面数量
|
||||
*/
|
||||
public int getChunk() {
|
||||
return cp.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取页面数量监听
|
||||
*
|
||||
* @return 页面数量监听
|
||||
*/
|
||||
public ReadOnlyIntegerProperty chunkProperty() {
|
||||
return cp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置单页数量,取值范围 [1, Integer.MAX_VALUE]
|
||||
*
|
||||
* @param size 单页数量
|
||||
*/
|
||||
public void setSize(int size) {
|
||||
if (size < 1) {
|
||||
throw new IllegalArgumentException("page size range of [1, Integer.MAX_VALUE]");
|
||||
}
|
||||
sp.set(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前单页数量
|
||||
*
|
||||
* @return 单页数量
|
||||
*/
|
||||
public int getSize() {
|
||||
return sp.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单页数量监听
|
||||
*
|
||||
* @return 单页数量监听
|
||||
*/
|
||||
public IntegerProperty sizeProperty() {
|
||||
return sp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置总数据量,取值范围 [0, Long.MAX_VALUE]
|
||||
*
|
||||
* @param length 总数据量
|
||||
*/
|
||||
public void setLength(long length) {
|
||||
if (length < 0) {
|
||||
throw new IllegalArgumentException("length range of [0, Long.MAX_VALUE]");
|
||||
}
|
||||
lp.set(length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前总数据量
|
||||
*
|
||||
* @return 当前总数据量
|
||||
*/
|
||||
public long getLength() {
|
||||
return lp.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取总数据大小监听
|
||||
*
|
||||
* @return 总数据大小监听
|
||||
*/
|
||||
public LongProperty lengthProperty() {
|
||||
return lp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面按钮,分页组件内部调度
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2022-01-27 01:42
|
||||
*/
|
||||
private class PageButton extends ToggleButton implements TimiFXUI {
|
||||
|
||||
/** 当前页码 */
|
||||
final IntegerProperty indexProperty;
|
||||
|
||||
public PageButton() {
|
||||
indexProperty = new SimpleIntegerProperty();
|
||||
|
||||
getStyleClass().addAll(CSS.BORDER_R, CSS.BG_BUTTON);
|
||||
// 页码即显示内容
|
||||
textProperty().bind(indexProperty.add(1).asString());
|
||||
managedProperty().bind(visibleProperty());
|
||||
// 选中更新激活下标
|
||||
selectedProperty().addListener((obs, o, isSelected) -> {
|
||||
if (isSelected) {
|
||||
XPagination.this.ip.set(indexProperty.get());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user