Я только что создал что-то, что не является JList, поэтому в нем отсутствуют многие функции, но вы можете легко добавить их.
Но вы получите следующее: список (все члены имеют одинаковый размер).) который может легко вместить ~ 2 миллиарда панелей без проблем с памятью или производительностью - см. демонстрационный код.Кроме того, JPanels могут содержать все, что вы хотите, эти компоненты будут работать нормально.
В демонстрации члены JPanel не имеют внутренних JPanels и полностью прозрачны для событий мыши (кроме JButtons, и это хорошо): Слушатель, добавленный в общий контейнер, получает их, как показано в демонстрационной версии.Если вы добавите больше иерархии компонентов, все может стать сложнее, IDK.
В любом случае, эта вещь молниеносна и, прежде всего, выполняет свою работу: JPanels в списке, который вы можете использовать, но также можете выбирать.(Код выбора не встроен, но, как я уже сказал: легко сделать. Демонстрационный код при наведении мыши внутри.)
Демонстрационный класс:
final public class FastPanelListDemo {
private static JFrame window = null;
private static FastPanelList panelList = null;
public static void main(final String[] args) {
SwingUtilities.invokeLater(() -> {
setLookAndFeelDefault();
panelList = new FastPanelList(FastPanelList.FPLOrientation.VERTICAL,
FastPanelListDemo::supplyPanel,
0.1,
0.95,
false,
80,
Integer.MAX_VALUE);
final Container contentPane = panelList.container;
contentPane.setPreferredSize(new Dimension(300, 800));
contentPane.setBackground(Color.GRAY);
window = new JFrame("FastPanelList demo");
window.setContentPane(contentPane);
window.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
window.pack();
window.setLocationRelativeTo(null);
window.setVisible(true);
contentPane.addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseMoved(final MouseEvent e) {
final JPanel itemUnderMouse = panelList.getItemUnderMouse(e);
if (itemUnderMouse != null) {
itemUnderMouse.setBackground(new Color((float) Math.random(),
(float) Math.random(),
(float) Math.random()));
}
}
});
});
}
private static JPanel supplyPanel(final int panelIndex) { // Just supply something that extends JPanel. You can put as much data in as you want. E.g. "boolean isMouseHovering" etc.
final JLabel label = new JLabel("panel " + panelIndex);
label.setHorizontalAlignment(SwingConstants.CENTER);
label.setVerticalAlignment(SwingConstants.CENTER);
final JButton button = new JButton("click me");
button.addActionListener(e -> {
JOptionPane.showMessageDialog(window,
"That was button " + panelIndex + ".",
"* CLICK *",
JOptionPane.INFORMATION_MESSAGE);
});
final JPanel panel = new JPanel(new BorderLayout(0,
0));
panel.setBorder(BorderFactory.createEmptyBorder(10,
10,
10,
10));
panel.setBackground(new Color((float) Math.random(),
(float) Math.random(),
(float) Math.random()));
panel.add(label, BorderLayout.CENTER);
panel.add(button, BorderLayout.EAST);
return panel;
}
private static void setLookAndFeelDefault() {
setLookAndFeel("Windows",
UIManager.getSystemLookAndFeelClassName(),
UIManager.getCrossPlatformLookAndFeelClassName(),
"Windows Classic",
"Nimbus",
"Metal",
"CDE/Motif");
}
/**
* @param intendedLAFIs ANYTHING, but ideally a LookAndFeel name or several. The first value that equalsIgnoreCase
* an installed LookAndFeelInfo.getName() will be used.
*/
private static void setLookAndFeel(final String... intendedLAFIs) {
if (intendedLAFIs != null && intendedLAFIs.length > 0) {
final UIManager.LookAndFeelInfo[] installedLAFIs = UIManager.getInstalledLookAndFeels();
LAFILOOP:
for (String intendedLAFI : intendedLAFIs) {
for (final UIManager.LookAndFeelInfo lafi : UIManager.getInstalledLookAndFeels()) {
if (lafi.getName().equalsIgnoreCase(intendedLAFI)) {
try {
UIManager.setLookAndFeel(lafi.getClassName());
break LAFILOOP;
} catch (Exception e) {
continue LAFILOOP;
}
}
}
}
} else {
throw new IllegalArgumentException("intendedLAFIs is null or empty.");
}
}
}
Класс FastPanelList:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
* FastPanelList v[1, 2019-05-22 14!00 UTC] by Dreamspace President
*/
final public class FastPanelList {
public enum FPLOrientation {
HORIZONTAL(Adjustable.HORIZONTAL),
VERTICAL(Adjustable.VERTICAL);
final public int orientationAsConstant;
FPLOrientation(final int orientationAsConstant) {
this.orientationAsConstant = orientationAsConstant;
}
}
final public FPLOrientation orientation;
final private Function<Integer, JPanel> panelSupplier;
final private double fractionOfExtentToScrollPerArrowClick;
final private double fractionOfExtentToScrollPerTrackClick;
final private boolean hideScrollbarWhenUnnecessary;
final private JScrollBar scrollBar;
final private int scrollBarWidth; // The default width it normally has in any GUI.
final public JPanel container; // The container of it all.
private int panelSize = 0; // The horizontal or vertical extent of each contained panel.
private int panelCount = 0; // The amount of panels, indeed max Integer.MAX_VALUE.
private long contentSize = 0; // The sum total extent of all "contained panels". (They're not really contained, but nobody will see that.)
private long actualScrollPosition = 0; // The true scroll position, think contentSize.
private Dimension lastKnownContainerSize = new Dimension(0, 0);
private Map<Integer, JPanel> knownPanels = new HashMap<>(); // All panels of which some pixels are currently potentially visible are cached here.
/**
* @param orientation Whether horizontal or the more common vertical arrangement.
* @param panelSupplier Your code that supplies the panels as needed on the fly.
* @param fractionOfExtentToScrollPerArrowClick E.g. 0.1 for 10% of the visible area to become hidden/shown when you
* click a scrollbar arrow.
* @param fractionOfExtentToScrollPerTrackClick E.g. 0.95 for 95% of the visible area to become hidden/shown when
* you click in the scrollbar track.
* @param hideScrollbarWhenUnnecessary Guess.
* @param panelSize Can later also be done via setter. (Not tested.)
* @param panelCount dto.
*/
public FastPanelList(final FPLOrientation orientation,
final Function<Integer, JPanel> panelSupplier,
final double fractionOfExtentToScrollPerArrowClick,
final double fractionOfExtentToScrollPerTrackClick,
final boolean hideScrollbarWhenUnnecessary,
final int panelSize,
final int panelCount) {
if (orientation == null) {
throw new IllegalArgumentException("orientation is null.");
}
if (panelSupplier == null) {
throw new IllegalArgumentException("panelSupplier is null.");
}
this.orientation = orientation;
this.panelSupplier = panelSupplier;
this.fractionOfExtentToScrollPerArrowClick = Math.max(0, fractionOfExtentToScrollPerArrowClick);
this.fractionOfExtentToScrollPerTrackClick = Math.max(0, fractionOfExtentToScrollPerTrackClick);
this.hideScrollbarWhenUnnecessary = hideScrollbarWhenUnnecessary;
setPanelSize(panelSize);
setPanelCount(panelCount);
scrollBarWidth = determineScrollBarDefaultWidth();
scrollBar = new JScrollBar(orientation.orientationAsConstant, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE);
scrollBar.addAdjustmentListener(e -> update());
container = new JPanel(null); // NULL: We want to layout everything manually.
container.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(final ComponentEvent e) {
update();
}
});
}
private int determineScrollBarDefaultWidth() { // Called only ONE time.
final JScrollPane dummyForDefaultSize = new JScrollPane(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
dummyForDefaultSize.setPreferredSize(new Dimension(1000, 1000));
dummyForDefaultSize.setSize(dummyForDefaultSize.getPreferredSize());
dummyForDefaultSize.doLayout();
return dummyForDefaultSize.getVerticalScrollBar().getSize().width;
}
/**
* FastPanelList requires each item to have the exact same size. This is where you define it (if you reconsidered
* after your constructor call).
*
* @param panelSize Will become >=1
*/
public void setPanelSize(final int panelSize) {
this.panelSize = Math.max(1, panelSize);
}
/**
* FastPanelList easily manages Integer.MAX_VALUE (about 2 billion) panels with no memory or performance problems.
* You define the amount here. You don't add/remove panels in this thing: Instead, you will be asked to provide
* panels as required depending on screen layout etc.
*
* @param panelCount Will become >=0
*/
public void setPanelCount(final int panelCount) {
this.panelCount = Math.max(0, panelCount);
}
/**
* Clears the internal JPanel cache. Necessary if you want to repopulate the list. Setting the panel count and
* calling update() is not sufficient. (Call update AFTER this method.)
*/
public void clear() {
knownPanels.clear();
}
public JPanel getItemUnderMouse(final MouseEvent e) {
return getItemUnderMouse(e.getX(), e.getY());
}
public JPanel getItemUnderMouse(final int xInComponent,
final int yInComponent) {
final long realPositionUnderMouse = (actualScrollPosition + (orientation == FPLOrientation.HORIZONTAL ? (long) xInComponent : (long) yInComponent));
final int indexUnderMouse = (int) (realPositionUnderMouse / panelSize);
return knownPanels.get(indexUnderMouse);
}
/**
* This layouts the component. This is done automatically when the scrollbar is moved or the container is resized,
* but any other action would require YOU to call this.
*/
public void update() {
container.removeAll();
lastKnownContainerSize = container.getSize();
final int containerSize;
if (orientation == FPLOrientation.HORIZONTAL) {
scrollBar.setLocation(0, lastKnownContainerSize.height - scrollBarWidth);
scrollBar.setSize(lastKnownContainerSize.width, scrollBarWidth);
containerSize = lastKnownContainerSize.width;
} else {
scrollBar.setLocation(lastKnownContainerSize.width - scrollBarWidth, 0);
scrollBar.setSize(scrollBarWidth, lastKnownContainerSize.height);
containerSize = lastKnownContainerSize.height;
}
contentSize = (long) panelCount * (long) panelSize;
final long invisibleStuff = contentSize - containerSize;
actualScrollPosition = Math.max(0, Math.min(invisibleStuff,
(long) (getScrollBarPosRatio() * (invisibleStuff))
));
final int extent;
if (contentSize > 0) {
final double visibleRatio = containerSize / (double) contentSize;
extent = (int) Math.max(0, Math.min(Integer.MAX_VALUE, Integer.MAX_VALUE * visibleRatio));
} else {
extent = Integer.MAX_VALUE;
}
final int unitIncrement = (int) Math.max(1,
Math.min(extent,
extent * fractionOfExtentToScrollPerArrowClick));
final int blockIncrement = (int) Math.max(1,
Math.min(extent,
extent * fractionOfExtentToScrollPerTrackClick));
scrollBar.getModel().setExtent(extent);
scrollBar.setUnitIncrement(unitIncrement);
scrollBar.setBlockIncrement(blockIncrement);
scrollBar.setVisible(!hideScrollbarWhenUnnecessary || extent < Integer.MAX_VALUE);
final Dimension panelSizes = getPanelSize();
long n = actualScrollPosition;
final long endOfScreen = actualScrollPosition + containerSize + panelSize;
final Map<Integer, JPanel> newKnownPanels = new HashMap<>();
while (n < endOfScreen) { // Loop ongoing = need more panels to fill the view.
// Calc index of current panel.
final long panelIndex = n / panelSize;
if (panelIndex > Integer.MAX_VALUE) {
throw new Error();
} else if (panelIndex >= panelCount) {
break;
}
final int panelIndexInt = (int) panelIndex;
// Obtain current panel - if possible from cache, else from external provider (which might likely create it from scratch).
JPanel panel = knownPanels.get(panelIndexInt);
if (panel == null) {
panel = panelSupplier.apply(panelIndexInt);
if (panel == null) {
throw new IllegalArgumentException("panelSupplier returned null for index " + panelIndex);
}
}
newKnownPanels.put(panelIndexInt, panel);
// Set position and size.
final int panelPos = (int) ((panelIndex * panelSize) - actualScrollPosition);
final Point location;
if (orientation == FPLOrientation.HORIZONTAL) {
location = new Point(panelPos, 0);
} else {
location = new Point(0, panelPos);
}
panel.setLocation(location);
panel.setSize(panelSizes);
n += panelSize;
}
knownPanels = newKnownPanels; // Will now contain all panels needed for display. All panels that were in the map, but are no longer needed, are now gone forever.
// Layout.
container.add(scrollBar);
for (JPanel panel : newKnownPanels.values()) {
container.add(panel);
panel.revalidate();
}
container.repaint(); // required
}
/**
* @return the correct width&height a contained JPanel needs to have. Is applied by update() automatically.
*/
public Dimension getPanelSize() {
if (orientation == FPLOrientation.HORIZONTAL) {
return new Dimension(panelSize,
lastKnownContainerSize.height - (scrollBar.isVisible() ? scrollBarWidth : 0));
} else {
return new Dimension(lastKnownContainerSize.width - (scrollBar.isVisible() ? scrollBarWidth : 0),
panelSize);
}
}
/**
* @return 0 to 1, expressing position of scroll bar handle.
*/
public double getScrollBarPosRatio() {
final int scrollRangeSize = Integer.MAX_VALUE - scrollBar.getVisibleAmount(); // Which should really be named getExtent(). Or rather the other way round.
return scrollBar.getValue() / (double) scrollRangeSize;
}
}