Files
timi-fx-ui/src/main/java/com/imyeyu/fx/ui/components/XPagination.java
2026-05-11 21:38:37 +08:00

338 lines
9.1 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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激活下标小于 41 - 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 (PageButton button : pageButtons) {
// 阻止取消选择
button.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 (PageButton pageButton : pageButtons) {
// 分页存在预设页码,只作触发事件用(如前置页的第五第六页),需要主动计算激活的按钮
if (ip.get() == pageButton.indexProperty.get() && pageButton.isVisible()) {
pageButton.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());
}
});
}
}
}