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][>]
*
* XPagination pagination = new XPagination();
* pagination.setSize(10); // 单页数据量
* pagination.setLength(200); // 总数据量
* pagination.indexProperty((obs, o, newIndex) -> {
* // 监听激活下标
* });
* pagination.setIndex(6); // 激活页下标(0 开始,这是第 7 页)
*
*
* @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 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 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 (PageButton button : pageButtons) {
// 阻止取消选择
button.addEventFilter(MouseEvent.MOUSE_PRESSED, EVENT_TOGGLE_BUTTON);
}
// 数据变动更新
ChangeListener 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());
}
});
}
}
}